Как переименовать файл clojure и пространство имен - PullRequest
2 голосов
/ 12 октября 2019

Я фактически переименовал часть файла, но сейчас я пытаюсь выяснить, как переименовать пространство имен. Идея здесь в том, что я хочу перебрать кучу .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"))

IУ меня есть все, что нужно для манипулирования строками, и я пытаюсь понять, как выполнить чтение содержимого файла 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. Это даже правильный подход?

Ответы [ 3 ]

2 голосов
/ 14 октября 2019

если вы используете emacs для разработки clojure, вы можете знать о крутом clj-refactor

Под капотом используется библиотека refactor-nrepl clojure. Таким образом, вы можете использовать его API для программного рефакторинга.

прежде всего добавьте зависимость к вашему проекту. 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/) и абсолютный путь к файлу / каталогу, который нужно переименовать, а библиотека делает все остальное.

Обратите внимание, что рефакторинг затрагивает 2 файла: сам исходный файл и тестовый файл, который имеет ссылку на свое пространство имен. Файл переименовывается, и все playground.core ссылки становятся playground.hardcore (refactor-nrepl пытается заменить необходимые ссылки везде в предоставленном classpath)

он также может переименовывать весь каталог, переписывая ссылки внутри всехвнутренние файлы (и связанные с ними, конечно же, от материнских папок):

(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 голос
/ 12 октября 2019

Вот пример того, как читать исходный код Clojure и манипулировать им с помощью библиотеки tupelo.forest . Вы можете увидеть действующий код в репозитории GitHub.

Сначала настройте тест и проанализируйте источник:

(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

На этом этапе мы загружаем EDNданные в дерево, используя tupelo.forest lib:

          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)))))
0 голосов
/ 29 октября 2019

Подход без библиотек см. мой ответ на другой вопрос .

Короче говоря

(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*, и возвращает их какстрока.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...