我实际上已经将文件部分重命名了,但是现在我想弄清楚如何重命名命名空间。这里的想法是我要遍历目录中的.clj
一堆文件并将其重命名。例如:
文件:some-file-123.clj
内容:
(ns some-file-123
(require [clojure.string :as str]))
(defn some-func [] (println "ima func"))
->
文件:123-some-file.clj
内容:
(ns 123-some-file
(require [clojure.string :as str]))
(defn some-func [] (println "ima func"))
我已经控制了所有的字符串操作内容,我想了解的是如何读取clojure文件内容,识别名称空间并替换它。
到目前为止,我遇到的问题涉及使用clojure.walk
,例如:
(let [file (first (get-some-file-fn))
file-text (read-string (slurp file))]
(clojure.walk/postwalk-demo file-text))
我知道我可以使用clojure.walk/postwalk-replace
进行一些替换,但是我被困在这里。
我怎么
file-text
提取命名空间postwalk-replace
答案 0 :(得分:2)
如果您正在使用emacs进行Clojure开发,您可能会意识到很酷的clj-refactor
引擎盖下它使用refactor-nrepl
clojure库。因此,您可以使用它的api进行程序化重构。
首先将依赖项添加到您的project.clj:
:dependencies [[org.clojure/clojure "1.10.0"]
[refactor-nrepl "2.4.0"]
;; rest deps
]
然后,您可以使用rename-file-or-dir
函数来重命名文件:
唯一的麻烦是,此函数基于类路径在文件上运行,因此要对任意路径进行重构,您需要执行以下操作:
这就是我不相关的示例项目的样子:
(require '[refactor-nrepl.rename-file-or-dir :as r])
;; temporarily redefining the lookup paths for source code
;; (unfortunately there is no setting in the library for that
(with-redefs [refactor-nrepl.core/dirs-on-classpath (constantly
(list (java.io.File. "/home/leetwin/dev/projects/clojure/ooo/src")
(java.io.File. "/home/leetwin/dev/projects/clojure/ooo/test")))]
(r/rename-file-or-dir
"/home/leetwin/dev/projects/clojure/ooo/src/playground/core.clj"
"/home/leetwin/dev/projects/clojure/ooo/src/playground/hardcore.clj"))
;;=> ("/home/leetwin/dev/projects/clojure/ooo/test/playground/core_test.clj"
;; "/home/leetwin/dev/projects/clojure/ooo/src/playground/hardcore.clj")
您提供了项目的源根目录(在我的情况下为...src/
和...test/
)以及要重命名的文件/目录的绝对路径,而lib则完成了其余工作。
请注意,重构会影响2个文件:源文件本身和引用其名称空间的测试文件。重命名该文件,所有playground.core
引用都变为playground.hardcore
(refactor-nrepl尝试在提供的类路径中的所有位置替换所需的引用)
它也可以重命名整个目录,重写所有内部文件(当然还有来自母目录的相关文件)中的引用:
(r/rename-file-or-dir
"/home/leetwin/dev/projects/clojure/ooo/src/playground"
"/home/leetwin/dev/projects/clojure/ooo/src/underground")
我想说这是一种更好的方法,因为它是一个使用广泛的库,旨在成为Clojure的重构工具。
否则,clojure-walk
替换在一定程度上可以解决问题(对于娱乐和教育来说绝对可以)
答案 1 :(得分:1)
这里是如何读取Clojure源代码并对其进行操作using the tupelo.forest
library的示例。您可以在GitHub存储库中see the live code。
首先设置一个测试并解析源:
(dotest
(hid-count-reset)
(with-forest (new-forest)
(let [debug-flg true
edn-str ; Notice that there are 3 forms in the source
(ts/quotes->double
"(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test))
(defn add2 [x y] (+ x y))
(dotest
(is= 5 (spyx (add2 2 3)))
(is= 'abc' (str 'ab' 'c'))) ")
; since `edn/read-string` only returns the next form,
; we wrap all forms in an artifical [:root ...] node
parse-txt (str "[:root " edn-str " ]")
edn-data (edn/read-string parse-txt) ; reads only the first form
这时,我们使用tupelo.forest
lib将EDN数据加载到树中:
root-hid (add-tree-edn edn-data) ; add edn data to a single forest tree
ns-path (only (find-paths root-hid [:** {::tf/value (symbol "ns")}])) ; search for the `ns` symbol`
; ns-path looks like `[1038 1009 1002]`, where 1002 points to the `ns` node
ns-hid (xlast ns-path) ; ns-hid is a pointer to the node with `ns`
ns-parent-hid (xsecond (reverse ns-path)) ; get the parent hid (eg 1009)
ns-parent-khids (hid->kids ns-parent-hid) ; vector with `ns` contains 4 kids, of which `ns` is the first
ns-sym-hid (xsecond ns-parent-khids)] ; symbol `tst.demo.core` is the 2nd kid
(when debug-flg
(newline)
(spyx-pretty (hid->bush root-hid))
(newline)
(spyx (hid->node ns-hid))
(spyx (hid->node ns-parent-hid))
(spyx ns-parent-khids)
(newline)
(spyx (hid->node ns-sym-hid)))
上面的调试打印输出显示了正在发生的事情。这是树结构的“灌木”视图:
(hid->bush root-hid) =>
[{:tag :tupelo.forest/vec, :tupelo.forest/index nil}
[#:tupelo.forest{:value :root, :index 0}]
[{:tag :tupelo.forest/list, :tupelo.forest/index 1}
[#:tupelo.forest{:value ns, :index 0}]
[#:tupelo.forest{:value tst.demo.core, :index 1}]
[{:tag :tupelo.forest/list, :tupelo.forest/index 2}
[#:tupelo.forest{:value :use, :index 0}]
[#:tupelo.forest{:value demo.core, :index 1}]
[#:tupelo.forest{:value tupelo.core, :index 2}]
[#:tupelo.forest{:value tupelo.test, :index 3}]]]
[{:tag :tupelo.forest/list, :tupelo.forest/index 2}
[#:tupelo.forest{:value defn, :index 0}]
[#:tupelo.forest{:value add2, :index 1}]
[{:tag :tupelo.forest/vec, :tupelo.forest/index 2}
[#:tupelo.forest{:value x, :index 0}]
[#:tupelo.forest{:value y, :index 1}]]
[{:tag :tupelo.forest/list, :tupelo.forest/index 3}
[#:tupelo.forest{:value +, :index 0}]
[#:tupelo.forest{:value x, :index 1}]
[#:tupelo.forest{:value y, :index 2}]]]
[{:tag :tupelo.forest/list, :tupelo.forest/index 3}
[#:tupelo.forest{:value dotest, :index 0}]
[{:tag :tupelo.forest/list, :tupelo.forest/index 1}
[#:tupelo.forest{:value is=, :index 0}]
[#:tupelo.forest{:value 5, :index 1}]
[{:tag :tupelo.forest/list, :tupelo.forest/index 2}
[#:tupelo.forest{:value spyx, :index 0}]
[{:tag :tupelo.forest/list, :tupelo.forest/index 1}
[#:tupelo.forest{:value add2, :index 0}]
[#:tupelo.forest{:value 2, :index 1}]
[#:tupelo.forest{:value 3, :index 2}]]]]
[{:tag :tupelo.forest/list, :tupelo.forest/index 2}
[#:tupelo.forest{:value is=, :index 0}]
[#:tupelo.forest{:value "abc", :index 1}]
[{:tag :tupelo.forest/list, :tupelo.forest/index 2}
[#:tupelo.forest{:value str, :index 0}]
[#:tupelo.forest{:value "ab", :index 1}]
[#:tupelo.forest{:value "c", :index 2}]]]]]
和其他调试打印输出:
(hid->node ns-hid) => #:tupelo.forest{:khids [], :value ns, :index 0}
(hid->node ns-parent-hid) => {:tupelo.forest/khids [1002 1003 1008], :tag :tupelo.forest/list, :tupelo.forest/index 1}
ns-parent-khids => [1002 1003 1008]
(hid->node ns-sym-hid) => #:tupelo.forest{:khids [], :value tst.demo.core, :index 1}
然后,我们替换旧的名称空间符号并将表单转换回字符串格式:
; replace the old namespace symbol with a new one
(attrs-merge ns-sym-hid {::tf/value (symbol "something.new.core")})
; find the 3 kids of the `:root` node
(let [root-khids (it-> root-hid
(hid->node it)
(grab ::tf/khids it)
(drop 1 it) ; remove :root tag we added
)
kids-edn (forv [hid root-khids] ; still 3 forms to output
(hid->edn hid))
modified-src (with-out-str ; convert EDN forms to a single string
(doseq [form kids-edn]
(prn form)))
; expected-result is the original edn-str but with the new namespace symbol
expected-result (str/replace edn-str "tst.demo.core" "something.new.core")]
(when debug-flg
(spyx (hid->node ns-sym-hid))
(newline)
(spyx-pretty kids-edn)
(newline)
(println :modified-src \newline modified-src))
并且调试打印输出显示了它的作用:
(hid->node ns-sym-hid) => #:tupelo.forest{:khids [], :value tst.demo.core, :index 1}
(hid->node ns-sym-hid) => #:tupelo.forest{:khids [], :value something.new.core, :index 1}
kids-edn =>
[(ns something.new.core (:use demo.core tupelo.core tupelo.test))
(defn add2 [x y] (+ x y))
(dotest (is= 5 (spyx (add2 2 3))) (is= "abc" (str "ab" "c")))]
:modified-src
(ns something.new.core (:use demo.core tupelo.core tupelo.test))
(defn add2 [x y] (+ x y))
(dotest (is= 5 (spyx (add2 2 3))) (is= "abc" (str "ab" "c")))
单个单元测试验证修改后的源是否符合预期(忽略空格):
(is-nonblank= modified-src expected-result)))))
答案 2 :(得分:0)
有关不带库的方法,请参见my answer至another question。
简而言之
(with-open [reader (java.io.PushbackReader.
(clojure.java.io/reader "src/my/file/ns_path.clj"))]
(loop [[ forms done? ]
[ [] false ]]
(if done?
forms
(recur (try
[(conj forms (read reader)) false]
(catch Exception ex
(println (.getMessage ex))
[forms true]))))))
将从文件中获取表单作为矢量,然后可以将其转换为所需的格式,然后再将其写回到文件中。为此,您可以尝试类似
(run! #(spit "my/source/file/path.clj"
(with-out-str (clojure.pprint/pprint %))
:append true)
my-transformed-forms)
with-out-str
捕获pprint
通常将流传输到*out*
的输出,并将其作为字符串返回。