如何重命名Clojure文件和名称空间

时间:2019-10-12 03:00:56

标签: clojure

我实际上已经将文件部分重命名了,但是现在我想弄清楚如何重命名命名空间。这里的想法是我要遍历目录中的.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进行一些替换,但是我被困在这里。

我怎么

  1. file-text提取命名空间
  2. 使用postwalk-replace
  3. 用任意的新名称替换名称空间
  4. 这是正确的方法吗?

3 个答案:

答案 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 answeranother 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*的输出,并将其作为字符串返回。