Вот пример того, как читать исходный код 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)))))