Fork me on GitHub
#rewrite-clj
<
2021-11-03
>
Noah Bogart13:11:20

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.

Noah Bogart13:11:58

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

Noah Bogart13:11:10

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

lread16:11:23

@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.

Noah Bogart16:11:36

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

lread16:11:32

But what if 8 had no siblings?

Noah Bogart16:11:58

(-> "[8]" z/of-string z/down z/remove ((juxt z/string z/root-string))) ;; => ["[]", "[]"], right?

Noah Bogart16:11:16

if it has no siblings, it goes up one level

Noah Bogart16:11:33

or maybe it stays at an “empty” space within the vector/list

lread16:11:51

I sometimes wonder if a remove-right and a remove-left might be helpful.

lread16:11:17

But maybe not.

lread16:11:10

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.

lread16:11:51

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)"

Noah Bogart16:11:17

huh interesting

lread16:11:25

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.

Noah Bogart16:11:47

yeah, i looked at those but they were a fundamental shift in how to think about it so i didn’t pursue

lread17:11:09

The walk technique might work for you.

Noah Bogart17:11:03

that does indeed! excellent, thank you

Noah Bogart17:11:35

(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))

lread17:11:18

Hey, curious, what is (-> zloc z/down z/right z/right z/end?) doing?

Noah Bogart17:11:05

given (deftest example) , i want to see if it’s empty after the name (“example”)

Noah Bogart17:11:23

down to enter the list, right right to move one past “example”

lread17:11:25

(remember that end? is for the end last node in a depth-first walk)

Noah Bogart17:11:57

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

Noah Bogart17:11:30

in a macro, i’d say (and (= 'deftest (first l)) (= 2 (count l)))

lread18:11:15

@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 
)

Noah Bogart18:11:38

I intend to delete empty deftests even with comments. Otherwise looks right to me!

lread18:11:49

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)

lread18:11:26

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?!

lread18:11:35

Oh and negate that test. So (-> zloc z/down z/right z/right z/end?) becomes (not (-> zloc z/down z/right z/right))

lread18:11:42

Or equivalent.

Noah Bogart18:11:51

interesting, okay, cool

Noah Bogart18:11:08

thank you very much, i appreciate all of the help

lread19:11:26

You are most welcome. Thanks for the interesting questions!