{"id":15632543,"url":"https://github.com/boxed/instar","last_synced_at":"2025-04-06T06:14:32.272Z","repository":{"id":18561046,"uuid":"21763078","full_name":"boxed/instar","owner":"boxed","description":"Simpler and more powerful assoc/dissoc/update-in for both Clojure and ClojureScript","archived":false,"fork":false,"pushed_at":"2015-08-05T09:12:31.000Z","size":434,"stargazers_count":173,"open_issues_count":4,"forks_count":8,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-03-30T05:09:36.949Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/boxed.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-07-12T08:43:38.000Z","updated_at":"2025-03-23T11:04:46.000Z","dependencies_parsed_at":"2022-09-24T12:51:12.686Z","dependency_job_id":null,"html_url":"https://github.com/boxed/instar","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boxed%2Finstar","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boxed%2Finstar/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boxed%2Finstar/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boxed%2Finstar/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/boxed","download_url":"https://codeload.github.com/boxed/instar/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247441063,"owners_count":20939239,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-03T10:44:33.920Z","updated_at":"2025-04-06T06:14:32.254Z","avatar_url":"https://github.com/boxed.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Instar\n\n[![Build Status](https://travis-ci.org/boxed/instar.svg?branch=master)](https://travis-ci.org/boxed/instar)\n[![Examples tested with midje-readme](http://img.shields.io/badge/readme-tested-brightgreen.svg)](https://github.com/boxed/midje-readme)\n[![Dependency Status](https://www.versioneye.com/clojure/instar:instar/badge.svg)](https://www.versioneye.com/clojure/instar:instar)\n\n\n\u003e Instar |ˈɪnstɑː|\n\u003e\n\u003e noun Zoology\n\u003e\n\u003e A phase between two periods of moulting in the development of an insect larva or other invertebrate animal.\n\u003e\n\u003e ORIGIN late 19th cent.: from Latin, literally ‘form, likeness’.\n\nInstar is a library to unify assoc, dissoc and update-in into a coherent and easy to use whole, while also adding wildcard matching on paths. This creates a simple and powerful function for all transformations. There are also functions to extract data based on the same path structure.\n\n## Examples\n\nNested paths unifies assoc-in, update-in and (the still not in the standard lib) dissoc-in:\n\n```clojure\n(def m {:foo {:bar {:baz 1}}})\n\n; Traditional:\n(assoc-in  {} [:foo :bar :baz] 7)      =\u003e {:foo {:bar {:baz 7}}}\n(update-in m  [:foo :bar :baz] inc)    =\u003e {:foo {:bar {:baz 2}}}\n(update-in m  [:foo :bar] dissoc :baz) =\u003e {:foo {:bar {}}}\n\n; With instar:\n(transform {} [:foo :bar :baz] 7)      =\u003e {:foo {:bar {:baz 7}}}\n(transform m  [:foo :bar :baz] inc)    =\u003e {:foo {:bar {:baz 2}}}\n(transform m  [:foo :bar :baz] dissoc) =\u003e {:foo {:bar {}}}\n```\n\nWildcards makes updating multiple values easy:\n\n```clojure\n(transform {:foo {:bar {:baz 1, :qux 4}\n                  :bar2 {:baz 2, :qux 5}}}\n           [:foo * *] inc)\n=\u003e\n{:foo {:bar {:baz 2, :qux 5},\n       :bar2 {:baz 3, :qux 6}}}\n```\n\nBesides the flamboyant match-all asterisk, regular expressions can be used for more focused matches:\n\n```clojure\n(transform {:foo {:bar {:baz 1, :qux 4}\n                  :zip {:baz 2, :qux 5}}}\n           [:foo #\"^ba\" #\"^ba\"] inc)\n=\u003e\n{:foo {:bar {:baz 2, :qux 4},\n       :zip {:baz 2, :qux 5}}}\n```\n\nKey is neither string nor keyword?\n\nRequire a more delicate touch?\n\nClojure functions are treated as match predicates:\n\n```clojure\n(transform {:vector [0 1 2 3 4 5 6]}\n            [:vector odd?] inc)\n=\u003e\n{:vector [0 2 2 4 4 6 6]}\n\n(transform {:map {\"a\" 1, \"ab\" 2, \"abc\" 3}}\n           [:map (comp even? count)] inc)\n=\u003e\n{:map {\"a\" 1, \"ab\", 3, \"abc\", 3}}\n```\n\nAnd the coup de grâce, the combination of all of the above:\n\n```clojure\n(transform {:foo {:bar {:baz 1, :qux 4, :quux 7}}}\n           [:foo * *] inc\n           [:foo keyword? #\"qu+x\"] inc\n           [:foobar] \"hello\"\n           [:foo :bar :baz] dissoc)\n=\u003e\n{:foo {:bar {:qux 6, :quux 9}}, :foobar \"hello\"}\n```\n\nYou can also use instar for getting deep values, either with pairs of [path value] or just the values:\n\n```clojure\n(get-in-paths {:foo {:bar {:baz 1, :qux 4, :quux 7}}}\n              [:foo * *])\n=\u003e\n[[[:foo :bar :baz] 1]\n [[:foo :bar :qux] 4]\n [[:foo :bar :quux] 7]]\n\n\n(get-values-in-paths {:foo {:bar {:baz 1, :qux 4, :quux 7}}}\n                     [:foo * *])\n=\u003e\n[1 4 7]\n```\n\n### Capture groups\n\nCapture can be performed using the functions `%\u003e`and `%%`, which will replace\nthe regular argument to any transforming functions (the data being transformed)\nwith the value captured at that point in the path. Note that multiple\ncaptures can be used, which will then form additional arguments to the function.\n\nThe two capture types differ around whether their enclosed path segment becomes\npart of the transformation path. The non-resolving form is useful for\ncapturing values from siblings outside of the fully resolved path.\n\nUse `%%` for non-resolving capture, and `%\u003e` for the resolving variant, as\ndemonstrated below:\n\n\n```clojure\n(transform {:users [{:name \"Dan\", :age 23}\n                    {:name \"Sam\", :gender :female}]}\n           [:users (%\u003e *) :keys] keys)\n=\u003e\n{:users [{:name \"Dan\", :age 23, :keys '(:name :age)}\n          {:name \"Sam\", :gender :female, :keys '(:name :gender)}]}\n\n\n(:users\n  (transform {:users [{:name \"Dan\", :age 23}\n                      {:name \"Sam\", :gender :female}]\n              :aliases {\"Dan\" [\"Dante\" \"Daniel\"]\n                        \"Sam\" [\"Samantha\", \"Samoth\"]}}\n    [(%% :aliases) :users (%\u003e *) (%% :name)]\n      (fn [aliases user name] (assoc user :aliases (aliases name)))))\n=\u003e\n[{:name \"Dan\", :age 23,         :aliases [\"Dante\" \"Daniel\"]}\n {:name \"Sam\", :gender :female, :aliases [\"Samantha\", \"Samoth\"]}]\n```\n\nNote also that the non-resolving capture can conveniently support multiple segments:\n\n```clojure\n(transform {:a {:b {:c 42}}}\n           [(%% :a :b :c) :d :e :f] vector)\n=\u003e\n{:a {:b {:c 42}}, :d {:e {:f [42]}}}\n```\n\n\n## Installation\n\nAdd the following dependency to your `project.clj` file:\n\n[![Clojars Project](http://clojars.org/instar/latest-version.svg)](http://clojars.org/instar)\n\n## Longer example\n\nThis example is based on an actual use case for [atpshowbot](https://github.com/boxed/atpshowbot)\n\nSay we have the following data structure:\n\n```clojure\n(def big-map\n  {:votes {\"title1\" {:voters #{\"74.125.232.96\" \"74.125.232.95\"}\n                     :author \"nick1\"\n                     :author-ip \"74.125.232.96\"}\n           \"title2\" {:voters #{\"74.125.232.96\" \"74.125.232.95\"}\n                     :author \"nick2\"\n                     :author-ip \"74.125.232.96\"}}\n   :links [[\"link\" \"nick1\" \"74.125.232.96\"]\n           [\"another link\" \"nick2\" \"74.125.232.96\"]]})\n\n```\n\nWe'd like to send this to a browser, but it's pretty bad to send the users IP\naddresses. We still want to know how many have voted though and if the user has already voted.\nThis is the transformation to do that:\n\n```clojure\n(transform big-map\n  [:votes (%\u003e *) :votes] #(count (:voters %))\n  [:votes (%\u003e *) :did-vote] #(contains? (:voters %) \"74.125.232.96\")\n  [:votes * :voters] dissoc\n  [:votes * :author-ip] dissoc\n  [:links] #(for [[x y z] %] [x y]))\n\n=\u003e\n\n{:votes {\"title1\" {:did-vote true,\n                   :votes 2,\n                   :author \"nick1\"},\n         \"title2\" {:did-vote true,\n                   :votes 2,\n                   :author \"nick2\"}},\n :links [[\"link\" \"nick1\"]\n         [\"another link\" \"nick2\"]]}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fboxed%2Finstar","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fboxed%2Finstar","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fboxed%2Finstar/lists"}