├── todo.txt ├── .gitignore ├── Makefile ├── project.clj ├── src └── zippo │ └── core.cljc ├── test └── zippo │ └── core_test.cljc ├── README.md └── LICENSE /todo.txt: -------------------------------------------------------------------------------- 1 | - cljs support 2 | - cljs tests 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | profiles.clj 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | /.lein-* 10 | /.nrepl-port 11 | /.prepl-port 12 | .hgignore 13 | .hg/ 14 | 15 | node_modules/ 16 | package-lock.json 17 | package.json 18 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: clean test test-js 3 | 4 | repl: 5 | lein repl 6 | 7 | lint: 8 | clj-kondo --lint src --lint test 9 | 10 | release: 11 | lein release 12 | 13 | clean: 14 | rm -rf target 15 | 16 | .PHONY: test 17 | test: 18 | lein test 19 | 20 | test-js: 21 | lein with-profile +cljs cljsbuild once 22 | node target/tests.js 23 | 24 | toc-install: 25 | npm install --save markdown-toc 26 | 27 | toc-build: 28 | node_modules/.bin/markdown-toc -i README.md 29 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject com.github.igrishaev/zippo "0.1.5-SNAPSHOT" 2 | 3 | :description 4 | "Additions to the standard clojure.zip package." 5 | 6 | :url 7 | "https://github.com/igrishaev/zippo" 8 | 9 | :license 10 | {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" 11 | :url "https://www.eclipse.org/legal/epl-2.0/"} 12 | 13 | :deploy-repositories 14 | {"releases" {:url "https://repo.clojars.org" :creds :gpg}} 15 | 16 | :release-tasks 17 | [["vcs" "assert-committed"] 18 | ["test"] 19 | ["change" "version" "leiningen.release/bump-version" "release"] 20 | ["vcs" "commit"] 21 | ["vcs" "tag" "--no-sign"] 22 | ["deploy"] 23 | ["change" "version" "leiningen.release/bump-version"] 24 | ["vcs" "commit"] 25 | ["vcs" "push"]] 26 | 27 | :dependencies 28 | [] 29 | 30 | :profiles 31 | {:dev 32 | {:dependencies 33 | [[org.clojure/clojure "1.10.1"]] 34 | 35 | :global-vars 36 | {*warn-on-reflection* true 37 | *assert* true}} 38 | 39 | :cljs 40 | {:cljsbuild 41 | {:builds 42 | [{:source-paths ["src" "test"] 43 | :compiler {:output-to "target/tests.js" 44 | :output-dir "target" 45 | :main zippo.core-test 46 | :target :nodejs}}]} 47 | 48 | :plugins 49 | [[lein-cljsbuild "1.1.8"]] 50 | 51 | :dependencies 52 | [[org.clojure/clojurescript "1.10.891"]]}}) 53 | -------------------------------------------------------------------------------- /src/zippo/core.cljc: -------------------------------------------------------------------------------- 1 | (ns zippo.core 2 | (:require 3 | [clojure.zip :as zip])) 4 | 5 | 6 | (defn loc-seq 7 | "Get a lazy, finite seq of locations." 8 | [loc] 9 | (->> loc 10 | (iterate zip/next) 11 | (take-while (complement zip/end?)))) 12 | 13 | 14 | (defn ->loc-pred 15 | "Turn a node predicate into a location predicate." 16 | [node-pred] 17 | (fn [loc] 18 | (-> loc zip/node node-pred))) 19 | 20 | 21 | (defn loc-find 22 | "Find the first location matches a predicate." 23 | [loc loc-pred] 24 | (->> loc 25 | (loc-seq) 26 | (filter loc-pred) 27 | (first))) 28 | 29 | 30 | (defn loc-find-all 31 | "Find all the locations that match a predicate." 32 | [loc loc-pred] 33 | (->> loc 34 | (loc-seq) 35 | (filter loc-pred))) 36 | 37 | 38 | (defn loc-update 39 | "Update locations that match the `loc-pred` function 40 | with the `loc-fn` functions and the rest arguments. 41 | Returns the last (end) location." 42 | [loc loc-pred loc-fn & args] 43 | (loop [loc loc] 44 | (if (zip/end? loc) 45 | loc 46 | (if (loc-pred loc) 47 | (recur (zip/next (apply loc-fn loc args))) 48 | (recur (zip/next loc)))))) 49 | 50 | 51 | (defn loc-update-all 52 | "Update all the locations with the `loc-fn` and the rest 53 | arguments. Returns the last (end) location." 54 | [loc loc-fn & args] 55 | (loop [loc loc] 56 | (if (zip/end? loc) 57 | loc 58 | (recur (zip/next (apply loc-fn loc args)))))) 59 | 60 | 61 | (defn node-update 62 | "Like `loc-update` but acts on nodes. Updates all the nodes 63 | that match `node-pred` with the `node-fn` function 64 | and the rest arguments." 65 | [loc node-pred node-fn & args] 66 | (apply loc-update 67 | loc 68 | (->loc-pred node-pred) 69 | zip/edit 70 | node-fn 71 | args)) 72 | 73 | 74 | (defn loc-children 75 | "Return all the children locations." 76 | [loc] 77 | (when-let [loc-child (zip/down loc)] 78 | (->> loc-child 79 | (iterate zip/right) 80 | (take-while some?)))) 81 | 82 | 83 | (defn locs-children 84 | "For a seq of locations, return their concatenated children." 85 | [locs] 86 | (mapcat loc-children locs)) 87 | 88 | 89 | (defn loc-layers 90 | "For a given location, return a lazy seq of its 'layers', 91 | e.g. children, the children of children and so on." 92 | [loc] 93 | (->> [loc] 94 | (iterate locs-children) 95 | (take-while seq))) 96 | 97 | 98 | (defn- -locs-seq-breadth [locs] 99 | (when (seq locs) 100 | (lazy-seq 101 | (concat locs 102 | (-locs-seq-breadth (locs-children locs)))))) 103 | 104 | 105 | (defn loc-seq-breadth 106 | "Return a lazy seq of locations in breadth-first direction 107 | (left to right, down, left to right and so on)." 108 | [loc] 109 | (-locs-seq-breadth [loc])) 110 | 111 | 112 | (defn- -lookup-until [direction loc loc-pred] 113 | (->> loc 114 | (iterate direction) 115 | (take-while some?) 116 | (rest) 117 | (filter loc-pred) 118 | (first))) 119 | 120 | 121 | (defn lookup-up 122 | "Go up until a location matches a predicate." 123 | [loc loc-pred] 124 | (-lookup-until zip/up loc loc-pred)) 125 | 126 | 127 | (defn lookup-left 128 | "Go left until a location matches a predicate." 129 | [loc loc-pred] 130 | (-lookup-until zip/left loc loc-pred)) 131 | 132 | 133 | (defn lookup-right 134 | "Go right until a location matches a predicate." 135 | [loc loc-pred] 136 | (-lookup-until zip/right loc loc-pred)) 137 | 138 | 139 | (defn lookup-down 140 | "Go down until a location matches a predicate." 141 | [loc loc-pred] 142 | (-lookup-until zip/down loc loc-pred)) 143 | 144 | 145 | (defn ->map-entry [k v] 146 | #?(:clj (new clojure.lang.MapEntry k v) 147 | :cljs (new cljs.core.MapEntry k v nil))) 148 | 149 | 150 | (defn coll-make-node 151 | [node children] 152 | (cond 153 | 154 | ;; MapEntry doesn't support meta 155 | (map-entry? node) 156 | (let [[k v] children] 157 | (->map-entry k v)) 158 | 159 | :else 160 | (with-meta 161 | (cond 162 | 163 | (vector? node) 164 | (vec children) 165 | 166 | (set? node) 167 | (set children) 168 | 169 | (map? node) 170 | (persistent! 171 | (reduce ;; into {} doesn't work 172 | (fn [acc! [k v]] 173 | (assoc! acc! k v)) 174 | (transient {}) 175 | children)) 176 | 177 | :else 178 | children) 179 | 180 | (meta node)))) 181 | 182 | 183 | (defn coll-zip 184 | "A zipper to navigate through any (nested) collection." 185 | [root] 186 | (zip/zipper coll? 187 | seq 188 | coll-make-node 189 | root)) 190 | -------------------------------------------------------------------------------- /test/zippo/core_test.cljc: -------------------------------------------------------------------------------- 1 | (ns zippo.core-test 2 | (:require 3 | [clojure.test :refer [deftest is testing run-tests]] 4 | [clojure.zip :as zip] 5 | [zippo.core :as zippo])) 6 | 7 | 8 | (def z-vec 9 | [1 [2 3] [[4]]]) 10 | 11 | 12 | (def z 13 | (zip/vector-zip z-vec)) 14 | 15 | 16 | (deftest test-loc-seq 17 | 18 | (let [locs (zippo/loc-seq z)] 19 | 20 | (is (= 8 (count locs))) 21 | 22 | (is (= [z-vec 23 | 1 24 | [2 3] 25 | 2 26 | 3 27 | [[4]] 28 | [4] 29 | 4] 30 | 31 | (mapv zip/node locs))))) 32 | 33 | 34 | (deftest test-loc-find 35 | 36 | (testing "found" 37 | 38 | (let [loc (zippo/loc-find 39 | z 40 | (fn [loc] 41 | (-> loc zip/node (= 3))))] 42 | 43 | (is (= 3 (zip/node loc))))) 44 | 45 | (testing "not found" 46 | 47 | (let [loc (zippo/loc-find 48 | z 49 | (fn [loc] 50 | (-> loc zip/node (= 99))))] 51 | 52 | (is (nil? loc)))) 53 | 54 | (testing "loc pred" 55 | 56 | (let [loc (zippo/loc-find 57 | z 58 | (zippo/->loc-pred 59 | (fn [x] 60 | (= x 4))))] 61 | 62 | (is (= 4 (zip/node loc)))))) 63 | 64 | 65 | (deftest test-loc-find-all 66 | 67 | (let [locs (zippo/loc-find-all 68 | z 69 | (zippo/->loc-pred (every-pred int? even?)))] 70 | 71 | (is (= [2 4] 72 | (mapv zip/node locs))))) 73 | 74 | 75 | (deftest test-loc-update-simple 76 | 77 | (let [loc 78 | (zippo/loc-update 79 | z 80 | (zippo/->loc-pred (every-pred int? even?)) 81 | zip/edit * 2)] 82 | 83 | (is (= [1 [4 3] [[8]]] 84 | (zip/root loc))))) 85 | 86 | 87 | (deftest test-loc-update-append-child 88 | 89 | (let [loc 90 | (zippo/loc-update 91 | z 92 | (fn [loc] 93 | (-> loc zip/node (= [2 3]))) 94 | zip/append-child 95 | :A)] 96 | 97 | (is (= [1 [2 3 :A] [[4]]] 98 | (zip/root loc))))) 99 | 100 | 101 | (deftest test-loc-update-remove 102 | 103 | (let [loc 104 | (zippo/loc-update 105 | z 106 | (fn [loc] 107 | (-> loc zip/node (= [2 3]))) 108 | zip/remove)] 109 | 110 | (is (= [1 [[4]]] 111 | (zip/root loc))))) 112 | 113 | 114 | (deftest test-loc-update-all 115 | (let [loc 116 | (zippo/loc-update 117 | z 118 | (zippo/->loc-pred int?) 119 | zip/edit 120 | inc)] 121 | 122 | (is (= [2 [3 4] [[5]]] 123 | (zip/root loc))))) 124 | 125 | 126 | (deftest test-node-update 127 | (let [loc 128 | (zippo/node-update 129 | z 130 | int? 131 | inc)] 132 | (is (= [2 [3 4] [[5]]] 133 | (zip/root loc))))) 134 | 135 | 136 | (deftest test-loc-layers 137 | (let [layers 138 | (zippo/loc-layers z)] 139 | 140 | (is (= '(([1 [2 3] [[4]]]) 141 | (1 [2 3] [[4]]) 142 | (2 3 [4]) 143 | (4)) 144 | (for [layer layers] 145 | (for [loc layer] 146 | (zip/node loc))))))) 147 | 148 | 149 | (deftest test-loc-seq-breadth 150 | (let [locs 151 | (zippo/loc-seq-breadth z)] 152 | (is (= [[1 [2 3] [[4]]] 153 | 1 154 | [2 3] 155 | [[4]] 156 | 2 157 | 3 158 | [4] 159 | 4] 160 | (mapv zip/node locs))))) 161 | 162 | 163 | (deftest test-lookup-up 164 | 165 | (let [loc 166 | (zip/vector-zip [:a [:b [:c [:d]]] :e]) 167 | 168 | loc-d 169 | (zippo/loc-find loc 170 | (zippo/->loc-pred 171 | (fn [node] 172 | (= node :d)))) 173 | 174 | loc-b 175 | (zippo/lookup-up loc-d 176 | (zippo/->loc-pred 177 | (fn [node] 178 | (and (vector? node) 179 | (= :b (first node)))))) 180 | 181 | loc-not-found 182 | (zippo/lookup-up loc-d 183 | (zippo/->loc-pred 184 | (fn [node] 185 | (= node 42))))] 186 | 187 | (is (= :d (zip/node loc-d))) 188 | 189 | (is (= [:b [:c [:d]]] (zip/node loc-b))) 190 | 191 | (is (nil? loc-not-found)))) 192 | 193 | 194 | (def sample 195 | [{:foo 1} 196 | #{'foo 'bar 'hello} 197 | (list 1 2 3 {:aa [1 2 {:haha true}]})]) 198 | 199 | 200 | (deftest test-coll-zip 201 | (let [loc 202 | (-> sample 203 | zippo/coll-zip 204 | (zippo/loc-find 205 | (fn [loc] 206 | (-> loc zip/node (= {:haha true}))))) 207 | 208 | loc* 209 | (zip/edit loc assoc :extra 42)] 210 | 211 | (is (= (zip/root loc*) 212 | '[{:foo 1} 213 | #{bar hello foo} 214 | (1 2 3 {:aa [1 2 {:haha true 215 | :extra 42}]})])))) 216 | 217 | 218 | (deftest test-coll-build-node 219 | 220 | (is (= (zippo/coll-make-node [42] '(1 2 3 4)) 221 | [1 2 3 4])) 222 | 223 | (is (= (zippo/coll-make-node #{42} '(1 2 3 4 3)) 224 | #{1 2 3 4})) 225 | 226 | (is (= (zippo/coll-make-node 227 | {:some 'map} 228 | '((:key1 "a") ["key2" 2])) 229 | {:key1 "a" "key2" 2})) 230 | 231 | (let [entry 232 | (zippo/coll-make-node 233 | (-> {:some 'map} first) 234 | '(:new-key "new-val"))] 235 | 236 | (is (map-entry? entry)) 237 | (is (= entry [:new-key "new-val"]))) 238 | 239 | (is (= (zippo/coll-make-node 240 | (repeat 0 2) 241 | (repeat 3 2)) 242 | '(2 2 2)))) 243 | 244 | 245 | #?(:cljs 246 | (do 247 | 248 | (defn -main [& _] 249 | (enable-console-print!) 250 | (run-tests)) 251 | 252 | (set! *main-cli-fn* -main))) 253 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zippo 2 | 3 | Small additions to the standard `clojure.zip` package. 4 | 5 | **ToC** 6 | 7 | 8 | 9 | - [Why](#why) 10 | - [Installation](#installation) 11 | - [Usage & examples](#usage--examples) 12 | * [A finite seq of locations](#a-finite-seq-of-locations) 13 | * [Finding locations](#finding-locations) 14 | * [Updating a zipper](#updating-a-zipper) 15 | * [Slicing a zipper by layers](#slicing-a-zipper-by-layers) 16 | * [Breadth-first seq of locations](#breadth-first-seq-of-locations) 17 | * [Lookups](#lookups) 18 | * [A universal collection zipper](#a-universal-collection-zipper) 19 | * [Also See](#also-see) 20 | - [ClojureScript support](#clojurescript-support) 21 | 22 | 23 | 24 | ## Why 25 | 26 | The `clojure.zip` package is a masterpiece yet misses some utility 27 | functions. For example, finding locations, bulk updates, lookups, breadth-first 28 | traversing and so on. This library brings some bits of missing functionality. 29 | 30 | ## Installation 31 | 32 | Lein: 33 | 34 | ```clojure 35 | [com.github.igrishaev/zippo "0.1.4"] 36 | ``` 37 | 38 | Deps.edn 39 | 40 | ```clojure 41 | {com.github.igrishaev/zippo {:mvn/version "0.1.4"}} 42 | ``` 43 | 44 | ## Usage & examples 45 | 46 | First, import both Zippo and `clojure.zip`: 47 | 48 | ~~~clojure 49 | (ns zippo.core-test 50 | (:require 51 | [clojure.zip :as zip] 52 | [zippo.core :as zippo])) 53 | ~~~ 54 | 55 | Declare a zipper: 56 | 57 | ~~~clojure 58 | (def z 59 | (zip/vector-zip [1 [2 3] [[4]]])) 60 | ~~~ 61 | 62 | Now check out the following Zippo functions. 63 | 64 | ### A finite seq of locations 65 | 66 | The `loc-seq` funtion takes a location and returns a lazy seq of locations 67 | untill it reaches the end: 68 | 69 | ~~~clojure 70 | (let [locs (zippo/loc-seq z)] 71 | (mapv zip/node locs)) 72 | 73 | ;; get a vector of notes to reduce the output 74 | [[1 [2 3] [[4]]] 75 | 1 76 | [2 3] 77 | 2 78 | 3 79 | [[4]] 80 | [4] 81 | 4] 82 | ~~~ 83 | 84 | This is quite useful to traverse a zipper without keeping in mind the ending 85 | condition (`zip/end?`). 86 | 87 | ### Finding locations 88 | 89 | The `loc-find` function looks for the first location that matches a predicate: 90 | 91 | ~~~clojure 92 | (let [loc (zippo/loc-find 93 | z 94 | (fn [loc] 95 | (-> loc zip/node (= 3))))] 96 | 97 | (is (= 3 (zip/node loc)))) 98 | ~~~ 99 | 100 | Above, we found a location which node equals 3. 101 | 102 | The `loc-find-all` function finds all the locatins that match the predicate: 103 | 104 | ~~~clojure 105 | (let [locs (zippo/loc-find-all 106 | z 107 | (zippo/->loc-pred (every-pred int? even?)))] 108 | 109 | (is (= [2 4] 110 | (mapv zip/node locs)))) 111 | ~~~ 112 | 113 | Since the predicate accepts a location, you can check its children, siblings and 114 | so on. For example, check if a location belongs to a special kind of parent. 115 | 116 | However, most of the time you're interested in a value (node) rather than a 117 | location. The `->loc-pred` function converts a node predicate, which accepts a 118 | node, into a location predicate. In the example above, the line 119 | 120 | ~~~clojure 121 | (zippo/->loc-pred (every-pred int? even?)) 122 | ~~~ 123 | 124 | makes a location predicate which node is an even integer. 125 | 126 | ### Updating a zipper 127 | 128 | Zippo offers some functions to update a zipper. 129 | 130 | The `loc-update` one takes a location predicate, an update function and the rest 131 | arguments. Here is how you douple all the even numbers in a nested vector: 132 | 133 | ~~~clojure 134 | (let [loc 135 | (zippo/loc-update 136 | z 137 | (zippo/->loc-pred (every-pred int? even?)) 138 | zip/edit * 2)] 139 | 140 | (is (= [1 [4 3] [[8]]] 141 | (zip/root loc)))) 142 | ~~~ 143 | 144 | For the updating function, one may use `zip/append-child` to append a child, 145 | `zip/remove` to drop the entire location and so on: 146 | 147 | ~~~clojure 148 | (let [loc 149 | (zippo/loc-update 150 | z 151 | (fn [loc] 152 | (-> loc zip/node (= [2 3]))) 153 | zip/append-child 154 | :A)] 155 | 156 | (is (= [1 [2 3 :A] [[4]]] 157 | (zip/root loc)))) 158 | ~~~ 159 | 160 | The `node-update` function is similar but acts on nodes. Instead of `loc-pred` 161 | and `loc-fn`, it accepts `node-pred` and `node-fn` what operate on nodes. 162 | 163 | ~~~clojure 164 | (let [loc 165 | (zippo/node-update 166 | z 167 | int? 168 | inc)] 169 | (is (= [2 [3 4] [[5]]] 170 | (zip/root loc)))) 171 | ~~~ 172 | 173 | ### Slicing a zipper by layers 174 | 175 | Sometimes, you need to slice a zipper on layers. This is what is better seen on 176 | a chart: 177 | 178 | ~~~ 179 | +---ROOT---+ ;; layer 1 180 | | | 181 | +-A-+ +-B-+ ;; layer 2 182 | | | | | | | 183 | X Y Z J H K ;; layer 3 184 | ~~~ 185 | 186 | - Layer 1 is `[Root]`; 187 | - Layer 1 is `[A B]`; 188 | - Layer 3 is `[X Y Z J H K]` 189 | 190 | The `loc-layers` function takes a location and builds a lazy seq of layers. The 191 | first layer is the given location, then its children, the children of children 192 | and so on. 193 | 194 | ~~~clojure 195 | (let [layers 196 | (zippo/loc-layers z)] 197 | 198 | (is (= '(([1 [2 3] [[4]]]) 199 | (1 [2 3] [[4]]) 200 | (2 3 [4]) 201 | (4)) 202 | (for [layer layers] 203 | (for [loc layer] 204 | (zip/node loc)))))) 205 | ~~~ 206 | 207 | ### Breadth-first seq of locations 208 | 209 | [depth-first]: https://en.wikipedia.org/wiki/Depth-first_search 210 | 211 | The `clojure.zip` package uses [depth-first method][depth-first] of traversing a 212 | tree. Let's number the items: 213 | 214 | ~~~ 215 | +-----ROOT[1]----+ 216 | | | 217 | +----A[2]---+ +---B[6]--+ 218 | | | | | | | 219 | X[3] Y[4] Z[5] J[7] H[8] K[9] 220 | ~~~ 221 | 222 | This sometimes may end up with an infinity loop when you generate children 223 | on the fly. 224 | 225 | The `loc-seq-breadth` functions offers the opposite way of traversing a zipper: 226 | 227 | ~~~ 228 | +-----ROOT[1]----+ 229 | | | 230 | +----A[2]---+ +---B[3]--+ 231 | | | | | | | 232 | X[4] Y[5] Z[6] J[7] H[8] K[9] 233 | ~~~ 234 | 235 | This is useful to solve some special tasks related to zippers. 236 | 237 | ### Lookups 238 | 239 | When working with zippers, you often need such functionality as "go 240 | up/left/right until meet something". For example, from a given location, go up 241 | until a parent has a special attribute. Zippo offers four functions for that, 242 | namely `lookup-up`, `lookup-left`, `lookup-right`, and `lookup-down.` All of 243 | them take a location and a predicate: 244 | 245 | ~~~clojure 246 | (let [loc 247 | (zip/vector-zip [:a [:b [:c [:d]]] :e]) 248 | 249 | loc-d 250 | (zippo/loc-find loc 251 | (zippo/->loc-pred 252 | (fn [node] 253 | (= node :d)))) 254 | 255 | loc-b 256 | (zippo/lookup-up loc-d 257 | (zippo/->loc-pred 258 | (fn [node] 259 | (and (vector? node) 260 | (= :b (first node))))))] 261 | 262 | (is (= :d (zip/node loc-d))) 263 | 264 | (is (= [:b [:c [:d]]] (zip/node loc-b)))) 265 | ~~~ 266 | 267 | In the example above, first we find the `:d` location. From there, we go up 268 | until we meet `[:b [:c [:d]]]`. If there is no such a location, the result will 269 | be nil. 270 | 271 | ### A universal collection zipper 272 | 273 | The `coll-zip` function builds a zipper that navigates through all the known 274 | collections types, e.g. vectors, maps, map entries, lazy collections and so 275 | on. Unlike the standard `zip/vector-zip` and `zip/seq-zip`, it works with any 276 | combination of vectors and map which is quite useful in production. A brief 277 | example: 278 | 279 | ```clojure 280 | (def sample 281 | [{:foo 1} 282 | #{'foo 'bar 'hello} 283 | (list 1 2 3 {:aa [1 2 {:haha true}]})]) 284 | 285 | (->> sample 286 | coll-zip 287 | loc-seq 288 | (map zip/node)) 289 | 290 | ( 291 | {:foo 1} 292 | [:foo 1] 293 | :foo 294 | 1 295 | #{bar hello foo} 296 | bar 297 | hello 298 | foo 299 | (1 2 3 {:aa [1 2 {:haha true}]}) 300 | 1 301 | 2 302 | 3 303 | {:aa [1 2 {:haha true}]} 304 | [:aa [1 2 {:haha true}]] 305 | :aa 306 | [1 2 {:haha true}] 307 | 1 308 | 2 309 | {:haha true} 310 | [:haha true] 311 | :haha 312 | true) 313 | ``` 314 | 315 | The `coll-zip` zipper carries a detailed implementation of the `make-node` 316 | function. It takes into account the type of the node and properly builds a new 317 | one from the children. It also preserves the metadata. 318 | 319 | ### Also See 320 | 321 | [zippers-guide]: https://grishaev.me/en/clojure-zippers/ 322 | 323 | The code from this library was used for [Clojure Zippers manual][zippers-guide] 324 | -- the complete guide to zippers in Clojure from the very scratch. 325 | 326 | ## ClojureScript support 327 | 328 | Since 1.3, the library supports ClojureScript as well. At least 1.9.542 version 329 | of ClojureScript compiler is required as the library relies on the 330 | [MapEntry][MapEntry] type and the [map-entry?][map-entry?] function. 331 | 332 | [MapEntry]: https://cljs.github.io/api/cljs.core/MapEntry 333 | [map-entry?]: https://cljs.github.io/api/cljs.core/map-entryQMARK 334 | 335 | © 2022 Ivan Grishaev 336 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 2.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 4 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION 5 | OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial content 12 | Distributed under this Agreement, and 13 | 14 | b) in the case of each subsequent Contributor: 15 | i) changes to the Program, and 16 | ii) additions to the Program; 17 | where such changes and/or additions to the Program originate from 18 | and are Distributed by that particular Contributor. A Contribution 19 | "originates" from a Contributor if it was added to the Program by 20 | such Contributor itself or anyone acting on such Contributor's behalf. 21 | Contributions do not include changes or additions to the Program that 22 | are not Modified Works. 23 | 24 | "Contributor" means any person or entity that Distributes the Program. 25 | 26 | "Licensed Patents" mean patent claims licensable by a Contributor which 27 | are necessarily infringed by the use or sale of its Contribution alone 28 | or when combined with the Program. 29 | 30 | "Program" means the Contributions Distributed in accordance with this 31 | Agreement. 32 | 33 | "Recipient" means anyone who receives the Program under this Agreement 34 | or any Secondary License (as applicable), including Contributors. 35 | 36 | "Derivative Works" shall mean any work, whether in Source Code or other 37 | form, that is based on (or derived from) the Program and for which the 38 | editorial revisions, annotations, elaborations, or other modifications 39 | represent, as a whole, an original work of authorship. 40 | 41 | "Modified Works" shall mean any work in Source Code or other form that 42 | results from an addition to, deletion from, or modification of the 43 | contents of the Program, including, for purposes of clarity any new file 44 | in Source Code form that contains any contents of the Program. Modified 45 | Works shall not include works that contain only declarations, 46 | interfaces, types, classes, structures, or files of the Program solely 47 | in each case in order to link to, bind by name, or subclass the Program 48 | or Modified Works thereof. 49 | 50 | "Distribute" means the acts of a) distributing or b) making available 51 | in any manner that enables the transfer of a copy. 52 | 53 | "Source Code" means the form of a Program preferred for making 54 | modifications, including but not limited to software source code, 55 | documentation source, and configuration files. 56 | 57 | "Secondary License" means either the GNU General Public License, 58 | Version 2.0, or any later versions of that license, including any 59 | exceptions or additional permissions as identified by the initial 60 | Contributor. 61 | 62 | 2. GRANT OF RIGHTS 63 | 64 | a) Subject to the terms of this Agreement, each Contributor hereby 65 | grants Recipient a non-exclusive, worldwide, royalty-free copyright 66 | license to reproduce, prepare Derivative Works of, publicly display, 67 | publicly perform, Distribute and sublicense the Contribution of such 68 | Contributor, if any, and such Derivative Works. 69 | 70 | b) Subject to the terms of this Agreement, each Contributor hereby 71 | grants Recipient a non-exclusive, worldwide, royalty-free patent 72 | license under Licensed Patents to make, use, sell, offer to sell, 73 | import and otherwise transfer the Contribution of such Contributor, 74 | if any, in Source Code or other form. This patent license shall 75 | apply to the combination of the Contribution and the Program if, at 76 | the time the Contribution is added by the Contributor, such addition 77 | of the Contribution causes such combination to be covered by the 78 | Licensed Patents. The patent license shall not apply to any other 79 | combinations which include the Contribution. No hardware per se is 80 | licensed hereunder. 81 | 82 | c) Recipient understands that although each Contributor grants the 83 | licenses to its Contributions set forth herein, no assurances are 84 | provided by any Contributor that the Program does not infringe the 85 | patent or other intellectual property rights of any other entity. 86 | Each Contributor disclaims any liability to Recipient for claims 87 | brought by any other entity based on infringement of intellectual 88 | property rights or otherwise. As a condition to exercising the 89 | rights and licenses granted hereunder, each Recipient hereby 90 | assumes sole responsibility to secure any other intellectual 91 | property rights needed, if any. For example, if a third party 92 | patent license is required to allow Recipient to Distribute the 93 | Program, it is Recipient's responsibility to acquire that license 94 | before distributing the Program. 95 | 96 | d) Each Contributor represents that to its knowledge it has 97 | sufficient copyright rights in its Contribution, if any, to grant 98 | the copyright license set forth in this Agreement. 99 | 100 | e) Notwithstanding the terms of any Secondary License, no 101 | Contributor makes additional grants to any Recipient (other than 102 | those set forth in this Agreement) as a result of such Recipient's 103 | receipt of the Program under the terms of a Secondary License 104 | (if permitted under the terms of Section 3). 105 | 106 | 3. REQUIREMENTS 107 | 108 | 3.1 If a Contributor Distributes the Program in any form, then: 109 | 110 | a) the Program must also be made available as Source Code, in 111 | accordance with section 3.2, and the Contributor must accompany 112 | the Program with a statement that the Source Code for the Program 113 | is available under this Agreement, and informs Recipients how to 114 | obtain it in a reasonable manner on or through a medium customarily 115 | used for software exchange; and 116 | 117 | b) the Contributor may Distribute the Program under a license 118 | different than this Agreement, provided that such license: 119 | i) effectively disclaims on behalf of all other Contributors all 120 | warranties and conditions, express and implied, including 121 | warranties or conditions of title and non-infringement, and 122 | implied warranties or conditions of merchantability and fitness 123 | for a particular purpose; 124 | 125 | ii) effectively excludes on behalf of all other Contributors all 126 | liability for damages, including direct, indirect, special, 127 | incidental and consequential damages, such as lost profits; 128 | 129 | iii) does not attempt to limit or alter the recipients' rights 130 | in the Source Code under section 3.2; and 131 | 132 | iv) requires any subsequent distribution of the Program by any 133 | party to be under a license that satisfies the requirements 134 | of this section 3. 135 | 136 | 3.2 When the Program is Distributed as Source Code: 137 | 138 | a) it must be made available under this Agreement, or if the 139 | Program (i) is combined with other material in a separate file or 140 | files made available under a Secondary License, and (ii) the initial 141 | Contributor attached to the Source Code the notice described in 142 | Exhibit A of this Agreement, then the Program may be made available 143 | under the terms of such Secondary Licenses, and 144 | 145 | b) a copy of this Agreement must be included with each copy of 146 | the Program. 147 | 148 | 3.3 Contributors may not remove or alter any copyright, patent, 149 | trademark, attribution notices, disclaimers of warranty, or limitations 150 | of liability ("notices") contained within the Program from any copy of 151 | the Program which they Distribute, provided that Contributors may add 152 | their own appropriate notices. 153 | 154 | 4. COMMERCIAL DISTRIBUTION 155 | 156 | Commercial distributors of software may accept certain responsibilities 157 | with respect to end users, business partners and the like. While this 158 | license is intended to facilitate the commercial use of the Program, 159 | the Contributor who includes the Program in a commercial product 160 | offering should do so in a manner which does not create potential 161 | liability for other Contributors. Therefore, if a Contributor includes 162 | the Program in a commercial product offering, such Contributor 163 | ("Commercial Contributor") hereby agrees to defend and indemnify every 164 | other Contributor ("Indemnified Contributor") against any losses, 165 | damages and costs (collectively "Losses") arising from claims, lawsuits 166 | and other legal actions brought by a third party against the Indemnified 167 | Contributor to the extent caused by the acts or omissions of such 168 | Commercial Contributor in connection with its distribution of the Program 169 | in a commercial product offering. The obligations in this section do not 170 | apply to any claims or Losses relating to any actual or alleged 171 | intellectual property infringement. In order to qualify, an Indemnified 172 | Contributor must: a) promptly notify the Commercial Contributor in 173 | writing of such claim, and b) allow the Commercial Contributor to control, 174 | and cooperate with the Commercial Contributor in, the defense and any 175 | related settlement negotiations. The Indemnified Contributor may 176 | participate in any such claim at its own expense. 177 | 178 | For example, a Contributor might include the Program in a commercial 179 | product offering, Product X. That Contributor is then a Commercial 180 | Contributor. If that Commercial Contributor then makes performance 181 | claims, or offers warranties related to Product X, those performance 182 | claims and warranties are such Commercial Contributor's responsibility 183 | alone. Under this section, the Commercial Contributor would have to 184 | defend claims against the other Contributors related to those performance 185 | claims and warranties, and if a court requires any other Contributor to 186 | pay any damages as a result, the Commercial Contributor must pay 187 | those damages. 188 | 189 | 5. NO WARRANTY 190 | 191 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 192 | PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" 193 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 194 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF 195 | TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 196 | PURPOSE. Each Recipient is solely responsible for determining the 197 | appropriateness of using and distributing the Program and assumes all 198 | risks associated with its exercise of rights under this Agreement, 199 | including but not limited to the risks and costs of program errors, 200 | compliance with applicable laws, damage to or loss of data, programs 201 | or equipment, and unavailability or interruption of operations. 202 | 203 | 6. DISCLAIMER OF LIABILITY 204 | 205 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 206 | PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS 207 | SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 208 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST 209 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 210 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 211 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 212 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE 213 | POSSIBILITY OF SUCH DAMAGES. 214 | 215 | 7. GENERAL 216 | 217 | If any provision of this Agreement is invalid or unenforceable under 218 | applicable law, it shall not affect the validity or enforceability of 219 | the remainder of the terms of this Agreement, and without further 220 | action by the parties hereto, such provision shall be reformed to the 221 | minimum extent necessary to make such provision valid and enforceable. 222 | 223 | If Recipient institutes patent litigation against any entity 224 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 225 | Program itself (excluding combinations of the Program with other software 226 | or hardware) infringes such Recipient's patent(s), then such Recipient's 227 | rights granted under Section 2(b) shall terminate as of the date such 228 | litigation is filed. 229 | 230 | All Recipient's rights under this Agreement shall terminate if it 231 | fails to comply with any of the material terms or conditions of this 232 | Agreement and does not cure such failure in a reasonable period of 233 | time after becoming aware of such noncompliance. If all Recipient's 234 | rights under this Agreement terminate, Recipient agrees to cease use 235 | and distribution of the Program as soon as reasonably practicable. 236 | However, Recipient's obligations under this Agreement and any licenses 237 | granted by Recipient relating to the Program shall continue and survive. 238 | 239 | Everyone is permitted to copy and distribute copies of this Agreement, 240 | but in order to avoid inconsistency the Agreement is copyrighted and 241 | may only be modified in the following manner. The Agreement Steward 242 | reserves the right to publish new versions (including revisions) of 243 | this Agreement from time to time. No one other than the Agreement 244 | Steward has the right to modify this Agreement. The Eclipse Foundation 245 | is the initial Agreement Steward. The Eclipse Foundation may assign the 246 | responsibility to serve as the Agreement Steward to a suitable separate 247 | entity. Each new version of the Agreement will be given a distinguishing 248 | version number. The Program (including Contributions) may always be 249 | Distributed subject to the version of the Agreement under which it was 250 | received. In addition, after a new version of the Agreement is published, 251 | Contributor may elect to Distribute the Program (including its 252 | Contributions) under the new version. 253 | 254 | Except as expressly stated in Sections 2(a) and 2(b) above, Recipient 255 | receives no rights or licenses to the intellectual property of any 256 | Contributor under this Agreement, whether expressly, by implication, 257 | estoppel or otherwise. All rights in the Program not expressly granted 258 | under this Agreement are reserved. Nothing in this Agreement is intended 259 | to be enforceable by any entity that is not a Contributor or Recipient. 260 | No third-party beneficiary rights are created under this Agreement. 261 | 262 | Exhibit A - Form of Secondary Licenses Notice 263 | 264 | "This Source Code may also be made available under the following 265 | Secondary Licenses when the conditions for such availability set forth 266 | in the Eclipse Public License, v. 2.0 are satisfied: GNU General Public 267 | License as published by the Free Software Foundation, either version 2 268 | of the License, or (at your option) any later version, with the GNU 269 | Classpath Exception which is available at 270 | https://www.gnu.org/software/classpath/license.html." 271 | 272 | Simply including a copy of this Agreement, including this Exhibit A 273 | is not sufficient to license the Source Code under Secondary Licenses. 274 | 275 | If it is not possible or desirable to put the notice in a particular 276 | file, then You may include the notice in a location (such as a LICENSE 277 | file in a relevant directory) where a recipient would be likely to 278 | look for such a notice. 279 | 280 | You may add additional accurate notices of copyright ownership. 281 | --------------------------------------------------------------------------------