[
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Checkout code\n      uses: actions/checkout@v4\n\n    - name: Set up Java\n      uses: actions/setup-java@v4\n      with:\n        distribution: 'temurin'\n        java-version: '8'\n\n    - name: Install Leiningen\n      run: |\n        wget https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein\n        chmod +x lein\n        sudo mv lein /usr/local/bin/\n        lein version\n\n    - name: Set up Node.js\n      uses: actions/setup-node@v4\n      with:\n        node-version: '20'\n\n    - name: Cache Leiningen dependencies\n      uses: actions/cache@v4\n      with:\n        path: ~/.m2/repository\n        key: ${{ runner.os }}-lein-${{ hashFiles('**/project.clj') }}\n        restore-keys: |\n          ${{ runner.os }}-lein-\n\n    - name: Run tests\n      run: lein test-all\n"
  },
  {
    "path": ".gitignore",
    "content": "pom.xml\npom.xml.asc\n*jar\n/lib/\n/classes/\n/target/\n/doc/\n.lein-deps-sum\n.lein-repl-history\n.nrepl-port\n.idea\n*.iml\n*.lein-failures\n/out/\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "0.5.0\n-----\n* 'in' distinguishes between absence of value (empty list returned) and nil value (nil returned)\n\n0.4.0\n-----\n* Support ClojureScript.\n* Upgrade to Clojure 1.7 to use reader conditionals in the tests.\n\n0.3.1\n-----\n* `xth` accepts a default value if the index is out of bounds.\n\n0.3.0\n-----\n\n* Removed `delete` on map entry lenses.\n* `in` does nothing if the path does not exist (unlike `update-in`).\n* Added `indexed` and `conditionally` lenses.\n* `view-single` throws an error if there are no foci.\n\n0.2.0\n-----\n\n* `view` renamed to `view-single`, and `view-all` renamed to `view`.\n* Confined deletion to entries in a map, as it's dangerous for sets and seqs.\n\n0.1.0\n-----\n\n* First version.\n"
  },
  {
    "path": "LICENCE.txt",
    "content": "Open Source Initiative OSI - The MIT License:Licensing\n\nThe MIT License\n\nCopyright (c) 2014, Chris Ford (christophertford at gmail)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Traversy\n\n[![CI](https://github.com/ctford/traversy/workflows/CI/badge.svg)](https://github.com/ctford/traversy/actions)\n\n[![Clojars Project](http://clojars.org/traversy/latest-version.svg)](http://clojars.org/traversy)\n\nAn experimental encoding of multilenses in Clojure.\n\n## What are multilenses?\n\nSimply put, multilenses are generalisations of `sequence` and `update-in`. Traversy's `view` and `update`\naccept a lens that determines how values are extracted or updated.\n\n`update-in` provides a way to apply a function within a nested map:\n\n```clojure\n(-> {:x 2 :y 4} (update-in [:x] inc))\n=> {:x 3 :y 4}\n ```\n \nThis works great so long as the value you want to update can be addressed by a single path. However,\nthere is no function for updating every value in a map in clojure.core. Here's how it looks with the\n`all-values` lens:\n\n```clojure\n(require ['traversy.lens :refer :all])\n    \n(-> {:x 2 :y 4} (update all-values inc))\n=> {:x 3 :y 5}\n```\n\nThe same lens can also be used for viewing the foci:\n\n```clojure\n(-> {:x 2 :y 4} (view all-values))\n=> (2 4)\n```\n\nLenses can be easily composed, so it's easy to build one that suits your particular data structure:\n\n```clojure\n(-> [{:x 1 :y 2} {:x 2 :y 7}] (update (*> each all-values) inc))\n=> ({:x 2 :y 3} {:x 3 :y 8})\n```\n\nAnd as viewing also composes:\n\n```clojure\n(-> [{:x 1 :y 2} {:x 2 :y 7}] (view (*> each all-values)))\n=> (1 2 2 7)\n```\n\nAs lenses are first class, once you have one that suits your needs, you can name it and put it in a var.\n\n## Usage\n\nSee the [examples](test/traversy/test/lens.cljc).\n\nThere is [API documentation](https://ctford.github.io/traversy/traversy.lens.html), which describes the operations and provided lenses. This was generated by [Codox](https://github.com/weavejester/codox).\n\n## Background\n\nAt the 2014 Clojure eXchange [I gave a talk about Lenses in general, and Traversy\nspecifically](https://skillsmatter.com/skillscasts/6034-journey-through-the-looking-glass).\n\n## Laws\n\nLenses follow some rules that make them behave intuitively. The first two rules are the [Traversal Laws](http://hackage.haskell.org/package/lens-2.3/docs/Control-Lens-Traversal.html#t:Traversal).\nThe final rule governs the relationship between `update` and `view`.\n\nAn `update` has no effect if passed the `identity` function:\n\n```clojure\n(-> x (update l identity)) === x\n```\n\nFusing two updates together is the same as applying them separately:\n\n```clojure\n(-> x (update l f1) (update l f2)) === (-> (update l (comp f2 f1)))\n```\n\n`update` then `view` is the same as `view` then `map`:\n\n```clojure\n(-> x (update l f) (view l x)) === (->> x (view l) (map f))\n```\n\nThese should hold for any lens `l` that applies to a data structure `x`.\n\nThe second rule can be violated when the foci of a lens change after an `update`. An example of\nthis is when `only` is used with a predicate and function that interact.\n\nThese two expressions should have the same value, but as incrementing an odd number makes it even,\nthe second update in the first example has no targets:\n```clojure\n(-> [1 2 3] (update (only odd?) inc) (update (only odd?) inc)) => [2 2 4]\n(-> [1 2 3] (update (only odd?) (comp inc inc))) => [3 2 5]\n```\n\nCareful when doing this - and please document any lenses that have this behaviour as unstable. Traversy\ncomes with three unstable lenses: `only`, `maybe` and `conditionally`.\n\n## FAQs\n\n### Aren't these just degenerate Lenses?\n\nYes! In fact, they're degenerate\n[Traversals](http://hackage.haskell.org/package/lens-2.3/docs/Control-Lens-Traversal.html), with the `Foldable` and\n`Functor` instances and without the generality of traversing using arbitrary `Applicatives`.\n\n\n### Will updates preserve the structure of the target?\n\nYes. Whether you focus on a map, a set, a vector or a sequence, the structure of the target will remain\nthe same after an update.\n\n### Can I compose these Lenses with ordinary function composition?\n\nNo. Unlike [Haskell Lenses](http://hackage.haskell.org/package/lens), these are not represented as functions.\nYou can, however, use `combine` (variadic form `*>`) and `both` (variadic form `+>`) to compose lenses.\n\n### Can I use Traversy with ClojureScript?\n\nYup!\n\n### How do I run the tests?\n\nClojure: `lein test`\n\nClojureScript: `lein test-cljs` (you'll need phantomjs)\n\nboth: `lein test-all`\n\n### Is this stable enough to use in production?\n\nTraversy is in production use on the project it originated from, but the API may yet change.\n"
  },
  {
    "path": "project.clj",
    "content": "(defproject traversy \"0.5.0\"\n            :description \"Multilenses for Clojure.\"\n            :url \"https://github.com/ctford/traversy\"\n            :min-lein-version \"2.1.2\"\n            :license {:name \"MIT\"\n                      :url  \"http://opensource.org/licenses/MIT\"}\n            :dependencies [[org.clojure/clojure \"1.8.0\"]\n                           [org.clojure/clojurescript \"1.9.229\"]]\n            :profiles {:dev {:plugins    [[com.jakemccrary/lein-test-refresh \"0.10.0\"]\n                                          [codox \"0.8.10\"]\n                                          [lein-cljsbuild \"1.1.4\"]\n                                          [lein-doo \"0.1.7\"]]\n                             :dependencies [[smidjen \"0.2.1\"]]}}\n            :codox {:src-dir-uri \"http://github.com/ctford/traversy/blob/0.3.0/\"}\n            :cljsbuild {:builds        {:test {:source-paths [\"src\" \"test\"]\n                                               :compiler     {:output-to     \"target/cljs/testable.js\"\n                                                              :main          traversy.test-runner\n                                                              :target        :nodejs\n                                                              :optimizations :none}}}}\n            :aliases {\"test-clj\" [\"test\" \"traversy.test.lens\"] ;; Travis version of lein doesn't support reader conditionals yet\n                      \"test-cljs\" [\"doo\" \"node\" \"test\" \"once\"]\n                      \"auto-cljs\" [\"doo\" \"node\" \"test\" \"auto\"]\n                      \"test-all\" [\"do\" \"test-clj,\" \"test-cljs\"]})\n"
  },
  {
    "path": "src/traversy/lens.cljc",
    "content": "(ns traversy.lens\n  (:refer-clojure :exclude [update]))\n\n(defn lens\n  \"Construct a lens from a focus function and an fmap function:\n  (focus x) => a sequence of foci\n  (fmap f x) => an updated x\"\n  [focus fmap]\n  {:focus focus :fmap fmap})\n\n(defn view\n  \"Return a seq of the lens' foci.\"\n  [x lens]\n  ((:focus lens) x))\n\n(defn view-single\n  \"Return the sole focus, throwing an error if there are other or no foci.\"\n  [x lens]\n  (let [[focus & _ :as foci] (view x lens)\n        quantity (count foci)]\n    (assert (= 1 quantity)\n            (str \"Found \" quantity \" foci, but expected exactly 1.\"))\n    focus))\n\n(defn update\n  \"Apply f to the foci of x, as specified by lens.\"\n  [x lens f]\n  ((:fmap lens) f x))\n\n(defn put\n  \"When supplied as the f to update, sets all the foci to x.\"\n  [x]\n  (constantly x))\n\n(defn ^:no-doc fapply [f x] (f x))\n\n(def it\n  \"The identity lens (under 'combine').\"\n  (lens list fapply))\n\n(defn ^:no-doc fconst [f x] x)\n\n(def nothing\n  \"The null lens. The identity under 'both'.\"\n  (lens (constantly []) fconst))\n\n(defn ^:no-doc zero [x]\n  (or (empty x) []))\n\n(defn ^:no-doc map-conj [f x] (->> x (map f) (reduce conj (zero x))))\n\n(def each\n  \"A lens from collection -> item.\"\n  (lens sequence map-conj))\n\n(def ^:no-doc index (partial map vector (range)))\n(defn ^:no-doc findexed [f x] (map (comp second f) (index x)))\n\n(def indexed\n  \"A lens from sequence -> index/item pair.\"\n  (lens index findexed))\n\n(defn ^:no-doc fnth [n f x]\n  (concat (take n x) [(f (nth x n))] (drop (inc n) x)))\n\n(defn xth\n  \"A lens from collection -> nth item.\"\n  ([n]\n   (lens (comp list #(nth % n)) (partial fnth n)))\n  ([n not-found]\n   (lens (comp list #(nth % n not-found)) (partial fnth n))))\n\n(defn ^:no-doc fapply-in [path f x]\n  (if (not= (get-in x path ::not-found) ::not-found)\n    (update-in x path f)\n    x))\n\n(defn ^:no-doc focus-in [path not-found x]\n  (let [v (get-in x path not-found)]\n    (if (= v ::not-found)\n      (list)\n      (list v))))\n\n(defn in\n  \"A lens from map -> value at path.\"\n  ([path]\n   (in path ::not-found))\n  ([path not-found]\n   (lens (partial focus-in path not-found) (partial fapply-in path))))\n\n(defn combine\n  \"Combine two lenses to form a new lens.\"\n  [outer inner]\n  (lens\n    (fn [x] (mapcat #(view % inner) (view x outer)))\n    (fn [f x] (update x outer #(update % inner f)))))\n\n(defn *>\n  \"Combine lenses to form a new lens.\"\n  [& lenses]\n  (reduce combine it lenses))\n\n(defn ^:no-doc fwhen [applies? f x] (if (applies? x) (f x) x))\n\n(defn conditionally\n  \"A lens to a conditional value, based on a predicate.\n\n  This lens is unstable if the predicate interacts with an update.\"\n  [applies?]\n  (lens (fn [x] (if (applies? x) [x] []))\n        (partial fwhen applies?)))\n\n(defn only\n  \"A lens from collection -> applicable items, based on a predicate.\n\n  This lens is unstable if the predicate interacts with an update.\"\n  [applies?]\n  (*> each (conditionally applies?)))\n\n(def maybe\n  \"A lens to an optional value.\n\n  This lens is unstable if an update converts nil to another value. \"\n  (conditionally (complement nil?)))\n\n(defn both\n  \"Combine two lenses in parallel to form a new lens.\"\n  [one another]\n  (lens\n    (fn [x] (concat (view x one) (view x another)))\n    (fn [f x] (-> x (update one f) (update another f)))))\n\n(defn +>\n  \"Combine lenses in parallel to form a new lens.\"\n  [& lenses]\n  (reduce both nothing lenses))\n\n(def all-entries\n  \"A lens from map -> each entry.\"\n  each)\n\n(def all-values\n  \"A lens from map -> each value.\"\n  (*> all-entries (in [1])))\n\n(def all-keys\n  \"A lens from map -> each key.\"\n  (*> all-entries (in [0])))\n\n(defn select-entries\n  \"A lens from map -> the entries corresponding to ks.\"\n  [ks]\n  (only (fn [[k v]] ((set ks) k))))\n"
  },
  {
    "path": "test/traversy/test/lens.cljc",
    "content": "(ns traversy.test.lens\n  (:refer-clojure :exclude [update])\n  (:require [traversy.lens :refer [view-single view update it nothing each only in update\n                                   indexed all-entries all-values all-keys select-entries\n                                   conditionally put xth combine both *> +> maybe]]\n            [smidjen.core #?(:clj :refer :cljs :refer-macros) [fact facts]]))\n\n(fact \"The 'it' lens is the identity.\"\n      (-> 9 (view-single it)) => 9\n      (-> 9 (view it)) => [9]\n      (-> 9 (update it inc)) => 10)\n\n(fact \"The 'nothing' lens doesn't have a focus.\"\n      (-> 9 (view nothing)) => '()\n      (-> 9 (update nothing inc)) => 9)\n\n(fact \"Trying to 'view-single' a lens that doesn't have exactly one focus throws an error.\"\n      (-> [9 10] (view-single each)) => (throws #?(:clj AssertionError :cljs js/Error))\n      (-> [] (view-single each)) => (throws #?(:clj AssertionError :cljs js/Error)))\n\n(fact \"Using 'view-single' with a multi-focus lens that happens to only have a single focus is fine.\"\n      (-> [9 10] (view-single (only even?))) => 10)\n\n(fact \"The 'in' lens focuses into a map based on a path.\"\n      (-> {} (view (in [:foo]))) => []\n      (-> {} (update (in [:foo]) str)) => {}\n      (-> {:foo 1} (view-single (in [:foo]))) => 1\n      (-> {:foo 1} (view (in [:foo]))) => [1]\n      (-> {:foo 1} (update (in [:foo]) inc)) => {:foo 2}\n      (-> {:foo nil} (update (in [:foo]) str)) => {:foo \"\"}\n      (-> {:foo 1} (view-single (in [:bar] \"not-found\"))) => \"not-found\"\n      (-> {:foo 1} (view-single (in [:bar]))) => (throws #?(:clj AssertionError :cljs js/Error)))\n\n(fact \"Unlike 'update-in', 'in' does nothing if the specified path does not exist.\"\n      (-> {} (update (in [:foo]) identity)) => {})\n\n(fact \"The 'each' lens focuses on each item in a sequence.\"\n      (-> [1 2 3] (view each)) => [1 2 3]\n      (-> [] (view each)) => '()\n      (-> [1 2 3] (update each inc)) => #(and (= % [2 3 4]) (vector? %)))\n\n(fact \"The 'each' lens focuses on each element in a set.\"\n      (-> #{1 2 3} (view each) set) => #{1 2 3}\n      (-> #{} (view each)) => '()\n      (-> #{1 2 3} (update each inc) set) => #{2 3 4})\n\n(fact \"The 'each' lens focuses on the entries of a map.\"\n      (-> {:foo 3 :bar 4} (view each) set) => #{[:foo 3] [:bar 4]}\n      (-> {} (view each)) => '()\n      (-> {:foo 3 :bar 4} (update each (fn [[k v]] [v k]))) => {3 :foo 4 :bar})\n\n(fact \"The 'indexed' lens focuses on indexed pairs in a sequence.\"\n      (-> [1 2 3] (view indexed)) => [[0 1] [1 2] [2 3]]\n      (-> [1 2 3] (update indexed (fn [[i v]] [i (+ i v)]))) => [1 3 5])\n\n(fact \"The 'all-entries' lens focuses on the entries of a map.\"\n      (-> {:foo 3 :bar 4} (view all-entries) set) => #{[:foo 3] [:bar 4]}\n      (-> {} (view all-entries)) => '()\n      (-> {:foo 3 :bar 4} (update all-entries (fn [[k v]] [v k]))) => {3 :foo 4 :bar})\n\n(fact \"The 'all-values' lens focuses on the values of a map.\"\n      (-> {:foo 1 :bar 2} (view all-values) set) => #{1 2}\n      (-> {:foo 1 :bar 2} (update all-values inc)) => {:foo 2 :bar 3})\n\n(fact \"The 'all-keys' lens focuses on the keys of a map.\"\n      (-> {:foo 1 :bar 2} (view all-keys) set) => #{:foo :bar}\n      (-> {:foo 1 :bar 2} (update all-keys {:foo :frag :bar :barp})) => {:frag 1 :barp 2})\n\n(fact \"The 'conditionally' lens focuses only on foci that match a condition.\"\n      (-> 1 (view (conditionally odd?))) => [1]\n      (-> 1 (view (conditionally even?))) => '()\n      (-> {:foo 1 :bar 2} (view (*> (+> (in [:foo]) (in [:bar])) (conditionally odd?)))) => [1]\n      (-> 1 (update (conditionally odd?) inc)) => 2\n      (-> 1 (update (conditionally even?) inc)) => 1)\n\n(fact \"The 'maybe' lens focuses only on foci that are present.\"\n      (-> {:foo 1} (view (*> (in [:foo]) maybe))) => [1]\n      (-> {:foo 1} (view (*> (in [:bar]) maybe))) => '()\n      (-> {} (view (*> (in [:bar]) maybe))) => '()\n      (-> {:foo 1} (view (*> (+> (in [:foo]) (in [:bar])) maybe))) => [1]\n      (-> 1 (update maybe inc)) => 2\n      (-> nil (update maybe inc)) => nil?)\n\n(fact \"The 'only' lens focuses on the items in a sequence matching a condition.\"\n      (-> [1 2 3] (view (only even?))) => [2]\n      (-> [1 2 3] (update (only even?) inc)) => [1 3 3]\n      (-> #{1 2 3} (update (only even?) inc)) => #{1 3})\n\n(fact \"The 'select-entries' lens focuses on entries of a map specified by key.\"\n      (-> {:foo 3 :bar 4 :baz 5} (view (select-entries [:foo :bar])) set) => #{[:foo 3] [:bar 4]}\n      (-> {:foo 3 :bar 4 :baz 5} (update (select-entries [:foo :bar]) (fn [[k v]] [v k]))) => {3 :foo 4 :bar :baz 5})\n\n(fact \"put sets the value at all the foci of a lens.\"\n      (-> [1 2 3] (update (only even?) (put 7))) => [1 7 3]\n      (-> #{1 2 3} (update each (put 7))) => #{7}\n      (-> {:foo 3 :bar 4} (update (select-entries [:foo]) (put [:baz 7]))) => {:bar 4 :baz 7})\n\n(fact \"The 'xth' lens focuses on the nth item of a sequence.\"\n      (-> [2 3 4] (view-single (xth 1))) => 3\n      (-> [2 3 4] (view (xth 1))) => [3]\n      (-> [2 3 4] (update (xth 1) inc)) => [2 4 4]\n      (-> [2 3 4] (view-single (xth 4 \"not found\"))) => \"not found\"\n      (-> [2 3 4] (view (xth 4 \"not found\"))) => [\"not found\"])\n\n(fact \"We can 'combine' single-focus lenses.\"\n      (-> {:foo {:bar 9}} (view-single (combine (in [:foo]) (in [:bar])))) => 9\n      (-> {:foo {:bar 9}} (view (combine (in [:foo]) (in [:bar])))) => [9]\n      (-> {:foo {:bar 9}} (update (combine (in [:foo]) (in [:bar])) inc)) => {:foo {:bar 10}})\n\n(fact \"We can 'combine' multiple-focus lenses with single-focus lenses.\"\n      (-> [{:foo 1} {:foo 2}] (view (combine each (in [:foo])))) => [1 2]\n      (-> [{:foo 1} {:foo 2}] (update (combine each (in [:foo])) inc)) => [{:foo 2} {:foo 3}])\n\n(fact \"We can 'combine' multiple-focus lenses with multiple-focus lenses.\"\n      (-> [[1 2] [3]] (view (combine each each))) => [1 2 3]\n      (-> [[1 2] [3]] (update (combine each each) inc)) => [[2 3] [4]])\n\n(fact \"We can combine single-focus lenses with multiple-focus lenses.\"\n      (-> {:foo [1 2]} (view (combine (in [:foo]) each))) => [1 2]\n      (-> {:foo [1 2]} (update (combine (in [:foo]) each) inc)) => {:foo [2 3]})\n\n(fact \"We can combine n lenses with '*>'.\"\n      (-> {:foo {:bar {:baz 9}}} (view-single (*> (in [:foo]) (in [:bar]) (in [:baz])))) => 9\n      (-> {:foo {:bar {:baz 9}}} (view (*> (in [:foo]) (in [:bar]) (in [:baz])))) => [9]\n      (-> {:foo {:bar {:baz 9}}} (update (*> (in [:foo]) (in [:bar]) (in [:baz])) inc)) => {:foo {:bar {:baz 10}}})\n\n(fact \"We can combine lenses in parallel with 'both'.\"\n      (-> {:foo 8 :bar 9} (view (both (in [:foo]) (in [:bar])))) => [8 9]\n      (-> {:foo 8 :bar 9} (update (both (in [:foo]) (in [:bar])) inc)) => {:foo 9 :bar 10})\n\n(fact \"We can combine lenses in parallel with '+>'.\"\n      (-> {:foo 8 :bar 9 :baz 10} (view (+> (in [:foo]) (in [:bar]) (in [:baz])))) => [8 9 10]\n      (-> {:foo 8 :bar 9 :baz 10} (update (+> (in [:foo]) (in [:bar]) (in [:baz])) inc)) => {:foo 9 :bar 10 :baz 11})\n"
  },
  {
    "path": "test/traversy/test_runner.cljs",
    "content": "(ns traversy.test-runner\n  (:require [doo.runner :refer-macros [doo-all-tests]]\n            [traversy.test.lens]))\n\n(doo-all-tests #\"traversy\\.test\\..*\")\n"
  }
]