here’s a question that i’m struggling to answer: i am iterating over the above changed files, hoping to remove all of the deftest blocks that are empty. when I call z/remove when the zloc is at the <list: (deftest blahtest)> node, it changes the zloc to the deepest token immediately above/before, meaning I have to traverse all the way back to the level of the deftests every time I call it. if I go too far, then i’m doing a lot of extra work as I then have to iterate over all of the forms above the zloc again.
that means I have to special case my find-root-deftest to also look for ns forms because sometimes the empty deftest is the first test in the file and when I remove it, i am placed at <token: :all> which is in <vector: [clojure.test :refer :all]> which is in <list: (:require … [clojure.test :refer :all])> which is in <list: (ns game.cards.example (:require … ))> which is of course not a deftest lol
i would expect that the zloc doesn’t “change” when removing something, that it would be like I called z/right (or maybe z/left like we’re stepping back to reassess). given that I can’t just call z/right when at the deepest node behind the removed zloc makes me do a lot of extra work to get back to where I was, with as far as I can tell, no visible benefit
@nbtheduke, yeah z/remove can be a bit tricky. The behavior matches that of clojure.zip/remove.
The location does kinda have to change tho, right? The location you z/removed is now gone.
I think maybe (?) you are talking about this kind of case:
(-> "([1 [2 [3]]] 8 9)"
z/of-string
z/down
z/right
z/remove
((juxt z/string z/root-string)))
;; => ["3" "([1 [2 [3]]] 9)"]
After deleting 8 you are now at located at a nested 3.right, good example. that’s the issue i’m running into. i’d love for a z/remove that ends up either pointing to [1 [2 [3]] or 9
But what if 8 had no siblings?
(-> "[8]" z/of-string z/down z/remove ((juxt z/string z/root-string))) ;; => ["[]", "[]"], right?
if it has no siblings, it goes up one level
or maybe it stays at an “empty” space within the vector/list
I sometimes wonder if a remove-right and a remove-left might be helpful.
But maybe not.
When removing things I will sometimes resort to using a sub zipper. I dig down and remove what I want but don’t lose my spot.
Other times a walk can be handy
(-> "([1 [2 [3]]] 8 9 3 10)"
z/of-string
z/up
(z/prewalk #(= "3" (z/string %)) z/remove)
z/root-string)
;; => "([1 [2 []]] 8 9 10)"huh interesting
There are also some kill function in the paredit API that I have not experimented with much yet. I don’t think they fit your use case though.
yeah, i looked at those but they were a fundamental shift in how to think about it so i didn’t pursue
The walk technique might work for you.
that does indeed! excellent, thank you
Cool!
(defn find-root-deftest [zloc]
(if (or (-> zloc z/down deftest?)
(-> zloc z/down z/string (= "ns")))
zloc
(recur (z/up zloc))))
(defn clean-deftest [zloc]
(if (and (deftest? (z/down zloc))
(-> zloc z/down z/right z/right z/end?))
(-> zloc z/remove find-root-deftest)
zloc))
(defn clean-file [zloc]
(let [zloc (clean-deftest zloc)]
(if (z/end? (z/right zloc))
zloc
(recur (z/right zloc)))))
to
(defn clean-file [zloc]
(z/prewalk
(z/up zloc)
(fn [zloc]
(and (-> zloc z/down deftest?)
(-> zloc z/down z/right z/right z/end?)))
z/remove))
chefs_kiss
Hey, curious, what is (-> zloc z/down z/right z/right z/end?) doing?
given (deftest example) , i want to see if it’s empty after the name (“example”)
down to enter the list, right right to move one past “example”
(remember that end? is for the end last node in a depth-first walk)
if there’s a better way to check that the list is only 2 non-whitespace, non-comment elements long, i’m all for it
in a macro, i’d say (and (= 'deftest (first l)) (= 2 (count l)))
@nbtheduke would something like this work?
(-> "(deftest empty)
(deftest empty2
)
(deftest has-stuff-so-keep 1)
(deftest with-comment
;; don't delete me, I have a comment!
)
(;; don't delete me either!
deftest with-comment2
)
(deftest ;; comment
with-comment3
)
"
z/of-string
z/up
(z/prewalk (fn [zloc]
(and
(z/list? zloc)
(= "deftest" (-> zloc z/down z/string))
;; no sibs?
(not (-> zloc z/down z/right z/right))
;; no comments? (we use * here because comments are otherwise skipped)
(not (-> zloc z/down*
(z/find z/right* #(n/comment? (z/node %)))))))
z/remove)
z/print-root)
Ouputs:
(deftest has-stuff-so-keep 1)
(deftest with-comment
;; don't delete me, I have a comment!
)
(;; don't delete me either!
deftest with-comment2
)
(deftest ;; comment
with-comment3
)I intend to delete empty deftests even with comments. Otherwise looks right to me!
Ah! Ok, simpler then:
(-> "(deftest empty)
(deftest empty2
)
(deftest has-stuff-so-keep 1)
(deftest with-comment
;; don't delete me, I have a comment!
)
(;; don't delete me either!
deftest with-comment2
)
(deftest ;; comment
with-comment3
)
"
z/of-string
z/up
(z/prewalk (fn [zloc]
(and
(z/list? zloc)
(= "deftest" (-> zloc z/down z/string))
;; no sibs?
(not (-> zloc z/down z/right z/right))))
z/remove)
z/print-root)
Outputs:
(deftest has-stuff-so-keep 1)So z/end? just happened to work for you because (z/end? nil) returns true! So basically your original was just fine. Just omit the z/end?!
Oh and negate that test. So (-> zloc z/down z/right z/right z/end?) becomes (not (-> zloc z/down z/right z/right))
Or equivalent.
interesting, okay, cool
thank you very much, i appreciate all of the help
You are most welcome. Thanks for the interesting questions!