Fork me on GitHub
#rewrite-clj
<
2021-09-25
>
yogthos01:09:08

I tried insert-newline and it didn’t do what I expected, I’ll post an example in the morning

yogthos14:09:34

here's what I've got as a working exaple

(defn require-exists? [requires require]
  (boolean (some #{require} requires)))

(defn append-requires-test [zloc requires]
  (let [zloc-ns         (z/find-value zloc z/next 'ns)
        zloc-require    (z/up (z/find-value zloc-ns z/next :require))
        updated-require (reduce
                          (fn [zloc child]
                            (if (require-exists? (z/sexpr zloc) child)
                              zloc
                              (-> zloc
                                  (z/insert-newline-right)
                                  (z/append-child child))))
                          zloc-require
                          requires)]
    (loop [z-loc updated-require]
      (if-let [parent (z/up z-loc)]
        (recur parent)
        z-loc))))

(let [zloc (z/of-string "(ns foo.core\n  (:require\n    [clojure.tools.logging :as log]\n    [integrant.core :as ig]))")
      requires [['foo :as 'bar]
                ['bar :as 'baz]]]
  (println (append-requires-test zloc requires)))
and can't figure out the right way to get new lines inserted here before each new require is appended

lread22:09:06

@yogthos, I think I might be able to help here. To benefit lurkers, I’ll keep your same data, but tweak the code so we can move slowly. First I’ll require the namespaces I’ll be using:

(require '[rewrite-clj.zip :as z] '[rewrite-clj.node :as n])
Next I’ll assign your test input to a var:
(def s "(ns foo.core\n  (:require\n    [clojure.tools.logging :as log]\n    [integrant.core :as ig]))")
Let’s pause here, to show what your test input looks like before doing any transforms:
(println s)
Gives us:
(ns foo.core
  (:require
    [clojure.tools.logging :as log]
    [integrant.core :as ig]))
I’ll paste your existing code here for reference:
(defn require-exists? [requires require]
  (boolean (some #{require} requires)))

(defn append-requires-test [zloc requires]
  (let [zloc-ns         (z/find-value zloc z/next 'ns)
        zloc-require    (z/up (z/find-value zloc-ns z/next :require))
        updated-require (reduce
                          (fn [zloc child]
                            (if (require-exists? (z/sexpr zloc) child)
                              zloc
                              (-> zloc
                                  (z/insert-newline-right)
                                  (z/append-child child))))
                          zloc-require
                          requires)]
    (loop [z-loc updated-require]
      (if-let [parent (z/up z-loc)]
        (recur parent)
        z-loc))))
But I’ll slightly tweak your test-case transform code to print the resulting code rather than the rewrite-clj node, and wrap it in a fn just so I can call it again later:
(defn transform-test [s]
  (let [zloc (z/of-string s)
        requires [['foo :as 'bar]
                  ['bar :as 'baz]]]
    (z/print-root (append-requires-test zloc requires))))
When I run:
(tranform-test s)
I get your problematic output: the new requires are added, but newlines are not where you want them.
(ns foo.core
  (:require
    [clojure.tools.logging :as log]
    [integrant.core :as ig] [foo :as bar] [bar :as baz])

)
What gives? Well z/insert-newline-right in append-requires-test is operating on the (:require ...) node, and those newlines are being inserted to the right of that node. There are many ways to work with rewrite-clj zippers, but here’s one way to tweak your append-requires-test fn:
(defn append-requires-test [zloc requires]
  (let [zloc-ns         (z/find-value zloc z/next 'ns)
        zloc-require    (z/up (z/find-value zloc-ns z/next :require))
        updated-require (reduce
                         (fn [zloc child]
                           (if (require-exists? (z/sexpr zloc) child)
                             zloc
                             (-> zloc
                                 ;; change #1: I might replace this line:
                                 ;; (z/insert-newline-right)
                                 ;; with this line:
                                 (z/append-child (n/newline-node "\n"))
                                 (z/append-child child))))
                         zloc-require
                         requires)]
    (loop [z-loc updated-require]
      (if-let [parent (z/up z-loc)]
        (recur parent)
        z-loc))))
Now if I re-run:
(tranform-test s)
I get the following:
(ns foo.core
  (:require
    [clojure.tools.logging :as log]
    [integrant.core :as ig] 
[foo :as bar] 
[bar :as baz]))
This is closer to what you probably want, but doesn’t handle indentation. Here’s what I might try to insert those extra indentation spaces:
(defn append-requires-test [zloc requires]
  (let [zloc-ns         (z/find-value zloc z/next 'ns)
        zloc-require    (z/up (z/find-value zloc-ns z/next :require))
        updated-require (reduce
                         (fn [zloc child]
                           (if (require-exists? (z/sexpr zloc) child)
                             zloc
                             (-> zloc
                                 ;; change #1: I might replace this line:
                                 ;; (z/insert-newline-right)
                                 ;; with this line:
                                 (z/append-child (n/newline-node "\n"))
                                 ;; change #2: and now indent to first existing require
                                 (z/append-child* (n/spaces (-> zloc z/down z/node meta :col)))
                                 (z/append-child child))))
                         zloc-require
                         requires)]
    (loop [z-loc updated-require]
      (if-let [parent (z/up z-loc)]
        (recur parent)
        z-loc))))
If I rerun:
(tranform-test s)
I get:
(ns foo.core
  (:require
    [clojure.tools.logging :as log]
    [integrant.core :as ig] 
    [foo :as bar] 
    [bar :as baz]))
I used the * version of append-child when inserting spaces because I don’t want rewrite-clj to try to be whitespace-helpful in this specific case. Note that the :col metadata on rewrite-clj nodes will not be updated after transforms, if you find you need such, take a look at https://cljdoc.org/d/rewrite-clj/rewrite-clj/1.0.682-alpha/doc/user-guide#position-tracking. And beware, my indentation handling here is illustrative and probably naive. If you find it is not handling your existing inputs, you might consider doing your transform, then running the resulting code through a formatter like cljfmt, cljstyle or zprint. Does that help? Lemme know!