[Git][clojure-team/medley-clojure][upstream] New upstream version 1.10.0
Jérôme Charaoui (@lavamind)
gitlab at salsa.debian.org
Thu Jul 30 05:21:56 BST 2026
Jérôme Charaoui pushed to branch upstream at Debian Clojure Maintainers / medley-clojure
Commits:
9f623c85 by Jérôme Charaoui at 2026-07-29T22:15:02-04:00
New upstream version 1.10.0
- - - - -
11 changed files:
- + .github/workflows/test.yml
- .gitignore
- + CONTRIBUTING.md
- + LICENSE.txt
- README.md
- − circle.yml
- + deps-clr.edn
- + deps.edn
- project.clj
- src/medley/core.cljc
- test/medley/core_test.cljc
Changes:
=====================================
.github/workflows/test.yml
=====================================
@@ -0,0 +1,40 @@
+name: Tests
+on: [push, pull_request]
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout at v6
+
+ - name: Prepare java
+ uses: actions/setup-java at v5
+ with:
+ distribution: 'zulu'
+ java-version: '8'
+
+ - name: Prepare dotnet
+ uses: xt0rted/setup-dotnet at v1.5.0
+
+ - name: Prepare Clojure CLR
+ run: |
+ dotnet tool install --global Clojure.Main --version 1.12.2
+ dotnet tool install --global Clojure.Cljr --version 0.1.0-alpha11
+
+ - name: Install clojure tools
+ uses: DeLaGuardo/setup-clojure at 13.6.0
+ with:
+ lein: 2.12.0
+
+ - name: Cache clojure dependencies
+ uses: actions/cache at v5
+ with:
+ path: ~/.m2/repository
+ key: cljdeps-${{ hashFiles('project.clj') }}
+ restore-keys: cljdeps-
+
+ - name: Run clj and cljs tests
+ run: lein test-all
+
+ - name: Run cljr tests
+ run: cljr -X:test
=====================================
.gitignore
=====================================
@@ -8,3 +8,5 @@ pom.xml.asc
*.class
/.lein-*
/.nrepl-port
+/.cpcache
+/.clj-kondo
=====================================
CONTRIBUTING.md
=====================================
@@ -0,0 +1,32 @@
+# Contributing Guidelines
+
+**Do** follow [the seven rules of a great Git commit message][1].
+
+**Do** follow [the Clojure Style Guide][2].
+
+**Do** include tests for your change when appropriate.
+
+**Do** ensure that the CI checks pass.
+
+**Do** squash the commits in your PR to remove corrections
+irrelevant to the code history, once the PR has been reviewed.
+
+**Do** feel free to pester the project maintainers about the PR if it
+hasn't been responded to. Sometimes notifications can be missed.
+
+**Don't** include more than one feature or fix in a single PR.
+
+**Don't** include changes unrelated to the purpose of the PR. This
+includes changing the project version number, adding lines to the
+`.gitignore` file, or changing the indentation or formatting.
+
+**Don't** open a new PR if changes are requested. Just push to the
+same branch and the PR will be updated.
+
+**Don't** overuse vertical whitespace; avoid multiple sequential blank
+lines.
+
+**Don't** docstring private vars or functions.
+
+[1]: https://chris.beams.io/posts/git-commit/#seven-rules
+[2]: https://github.com/bbatsov/clojure-style-guide
=====================================
LICENSE.txt
=====================================
@@ -0,0 +1,204 @@
+Eclipse Public License - v 1.0
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
+LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
+CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+ a) in the case of the initial Contributor, the initial code and
+ documentation distributed under this Agreement, and
+ b) in the case of each subsequent Contributor:
+ i) changes to the Program, and
+ ii) additions to the Program;
+
+where such changes and/or additions to the Program originate from and are
+distributed by that particular Contributor. A Contribution 'originates' from a
+Contributor if it was added to the Program by such Contributor itself or
+anyone acting on such Contributor's behalf. Contributions do not include
+additions to the Program which: (i) are separate modules of software
+distributed in conjunction with the Program under their own license agreement,
+and (ii) are not derivative works of the Program.
+"Contributor" means any person or entity that distributes the Program.
+
+"Licensed Patents" mean patent claims licensable by a Contributor which are
+necessarily infringed by the use or sale of its Contribution alone or when
+combined with the Program.
+
+"Program" means the Contributions distributed in accordance with this
+Agreement.
+
+"Recipient" means anyone who receives the Program under this Agreement,
+including all Contributors.
+
+2. GRANT OF RIGHTS
+
+ a) Subject to the terms of this Agreement, each Contributor hereby grants
+ Recipient a non-exclusive, worldwide, royalty-free copyright license to
+ reproduce, prepare derivative works of, publicly display, publicly
+ perform, distribute and sublicense the Contribution of such Contributor,
+ if any, and such derivative works, in source code and object code form.
+
+ b) Subject to the terms of this Agreement, each Contributor hereby grants
+ Recipient a non-exclusive, worldwide, royalty-free patent license under
+ Licensed Patents to make, use, sell, offer to sell, import and otherwise
+ transfer the Contribution of such Contributor, if any, in source code and
+ object code form. This patent license shall apply to the combination of
+ the Contribution and the Program if, at the time the Contribution is
+ added by the Contributor, such addition of the Contribution causes such
+ combination to be covered by the Licensed Patents. The patent license
+ shall not apply to any other combinations which include the Contribution.
+ No hardware per se is licensed hereunder.
+
+ c) Recipient understands that although each Contributor grants the
+ licenses to its Contributions set forth herein, no assurances are
+ provided by any Contributor that the Program does not infringe the patent
+ or other intellectual property rights of any other entity. Each
+ Contributor disclaims any liability to Recipient for claims brought by
+ any other entity based on infringement of intellectual property rights or
+ otherwise. As a condition to exercising the rights and licenses granted
+ hereunder, each Recipient hereby assumes sole responsibility to secure
+ any other intellectual property rights needed, if any. For example, if a
+ third party patent license is required to allow Recipient to distribute
+ the Program, it is Recipient's responsibility to acquire that license
+ before distributing the Program.
+
+ d) Each Contributor represents that to its knowledge it has sufficient
+ copyright rights in its Contribution, if any, to grant the copyright
+ license set forth in this Agreement.
+
+3. REQUIREMENTS
+A Contributor may choose to distribute the Program in object code form under
+its own license agreement, provided that:
+
+ a) it complies with the terms and conditions of this Agreement; and
+
+ b) its license agreement:
+ i) effectively disclaims on behalf of all Contributors all
+ warranties and conditions, express and implied, including warranties
+ or conditions of title and non-infringement, and implied warranties
+ or conditions of merchantability and fitness for a particular
+ purpose;
+ ii) effectively excludes on behalf of all Contributors all liability
+ for damages, including direct, indirect, special, incidental and
+ consequential damages, such as lost profits;
+ iii) states that any provisions which differ from this Agreement are
+ offered by that Contributor alone and not by any other party; and
+ iv) states that source code for the Program is available from such
+ Contributor, and informs licensees how to obtain it in a reasonable
+ manner on or through a medium customarily used for software
+ exchange.
+
+When the Program is made available in source code form:
+
+ a) it must be made available under this Agreement; and
+
+ b) a copy of this Agreement must be included with each copy of the
+ Program.
+Contributors may not remove or alter any copyright notices contained within
+the Program.
+
+Each Contributor must identify itself as the originator of its Contribution,
+if any, in a manner that reasonably allows subsequent Recipients to identify
+the originator of the Contribution.
+
+4. COMMERCIAL DISTRIBUTION
+Commercial distributors of software may accept certain responsibilities with
+respect to end users, business partners and the like. While this license is
+intended to facilitate the commercial use of the Program, the Contributor who
+includes the Program in a commercial product offering should do so in a manner
+which does not create potential liability for other Contributors. Therefore,
+if a Contributor includes the Program in a commercial product offering, such
+Contributor ("Commercial Contributor") hereby agrees to defend and indemnify
+every other Contributor ("Indemnified Contributor") against any losses,
+damages and costs (collectively "Losses") arising from claims, lawsuits and
+other legal actions brought by a third party against the Indemnified
+Contributor to the extent caused by the acts or omissions of such Commercial
+Contributor in connection with its distribution of the Program in a commercial
+product offering. The obligations in this section do not apply to any claims
+or Losses relating to any actual or alleged intellectual property
+infringement. In order to qualify, an Indemnified Contributor must: a)
+promptly notify the Commercial Contributor in writing of such claim, and b)
+allow the Commercial Contributor to control, and cooperate with the Commercial
+Contributor in, the defense and any related settlement negotiations. The
+Indemnified Contributor may participate in any such claim at its own expense.
+
+For example, a Contributor might include the Program in a commercial product
+offering, Product X. That Contributor is then a Commercial Contributor. If
+that Commercial Contributor then makes performance claims, or offers
+warranties related to Product X, those performance claims and warranties are
+such Commercial Contributor's responsibility alone. Under this section, the
+Commercial Contributor would have to defend claims against the other
+Contributors related to those performance claims and warranties, and if a
+court requires any other Contributor to pay any damages as a result, the
+Commercial Contributor must pay those damages.
+
+5. NO WARRANTY
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
+IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each
+Recipient is solely responsible for determining the appropriateness of using
+and distributing the Program and assumes all risks associated with its
+exercise of rights under this Agreement , including but not limited to the
+risks and costs of program errors, compliance with applicable laws, damage to
+or loss of data, programs or equipment, and unavailability or interruption of
+operations.
+
+6. DISCLAIMER OF LIABILITY
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
+CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
+LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
+EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGES.
+
+7. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under
+applicable law, it shall not affect the validity or enforceability of the
+remainder of the terms of this Agreement, and without further action by the
+parties hereto, such provision shall be reformed to the minimum extent
+necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Program itself
+(excluding combinations of the Program with other software or hardware)
+infringes such Recipient's patent(s), then such Recipient's rights granted
+under Section 2(b) shall terminate as of the date such litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it fails to
+comply with any of the material terms or conditions of this Agreement and does
+not cure such failure in a reasonable period of time after becoming aware of
+such noncompliance. If all Recipient's rights under this Agreement terminate,
+Recipient agrees to cease use and distribution of the Program as soon as
+reasonably practicable. However, Recipient's obligations under this Agreement
+and any licenses granted by Recipient relating to the Program shall continue
+and survive.
+
+Everyone is permitted to copy and distribute copies of this Agreement, but in
+order to avoid inconsistency the Agreement is copyrighted and may only be
+modified in the following manner. The Agreement Steward reserves the right to
+publish new versions (including revisions) of this Agreement from time to
+time. No one other than the Agreement Steward has the right to modify this
+Agreement. The Eclipse Foundation is the initial Agreement Steward. The
+Eclipse Foundation may assign the responsibility to serve as the Agreement
+Steward to a suitable separate entity. Each new version of the Agreement will
+be given a distinguishing version number. The Program (including
+Contributions) may always be distributed subject to the version of the
+Agreement under which it was received. In addition, after a new version of the
+Agreement is published, Contributor may elect to distribute the Program
+(including its Contributions) under the new version. Except as expressly
+stated in Sections 2(a) and 2(b) above, Recipient receives no rights or
+licenses to the intellectual property of any Contributor under this Agreement,
+whether expressly, by implication, estoppel or otherwise. All rights in the
+Program not expressly granted under this Agreement are reserved.
+
+This Agreement is governed by the laws of the State of New York and the
+intellectual property laws of the United States of America. No party to this
+Agreement will bring a legal action under this Agreement more than one year
+after the cause of action arose. Each party waives its rights to a jury trial
+in any resulting litigation.
=====================================
README.md
=====================================
@@ -1,6 +1,4 @@
-# Medley
-
-[](https://circleci.com/gh/weavejester/medley/tree/master)
+# Medley [](https://github.com/weavejester/medley/actions/workflows/test.yml)
Medley is a lightweight Clojure/ClojureScript library of useful,
*mostly* pure functions that are "missing" from clojure.core.
@@ -13,11 +11,23 @@ and limits itself to a small set of general-purpose functions.
## Installation
-To install, add the following to your project `:dependencies`:
+Add the following dependency to your deps.edn file:
+
+ dev.weavejester/medley {:mvn/version "1.10.0"}
+
+Or to your Leiningen project file:
+
+ [dev.weavejester/medley "1.10.0"]
- [medley "1.0.0"]
+Or to your deps-clr.edn file:
-## Breaking Changes
+ io.github.weavejester/medley {:git/tag "1.10.0" :git/sha "FIXME"}
+
+## Documentation
+
+* [API Docs](http://weavejester.github.io/medley/medley.core.html)
+
+## Pre-1.0 Breaking Changes
In 0.7.0 the minimum Clojure version was changed from 1.5.1 to 1.7.0
to take advantage of [reader conditionals][]. The `update` function
@@ -30,13 +40,9 @@ a version prior to 0.6.0, please update your usage of `greatest` and
[reader conditionals]: http://dev.clojure.org/display/design/Reader+Conditionals
-## Documentation
-
-* [API Docs](http://weavejester.github.io/medley/medley.core.html)
-
## License
-Copyright © 2017 James Reeves
+Copyright © 2026 James Reeves
Distributed under the Eclipse Public License either version 1.0 or (at
your option) any later version.
=====================================
circle.yml deleted
=====================================
@@ -1,7 +0,0 @@
-general:
- branches:
- ignore:
- - gh-pages
-test:
- override:
- - lein test-all
=====================================
deps-clr.edn
=====================================
@@ -0,0 +1,6 @@
+{:paths ["src"]
+ :aliases
+ {:test {:extra-paths ["test"]
+ :extra-deps {io.github.dmiller/test-runner {:git/tag "v0.5.1clr" :git/sha "814e06f"}}
+ :exec-fn cognitect.test-runner.api/test
+ :exec-args {:dirs ["test"]}}}}
=====================================
deps.edn
=====================================
@@ -0,0 +1 @@
+{:deps {org.clojure/clojure {:mvn/version "1.9.0"}}}
=====================================
project.clj
=====================================
@@ -1,12 +1,12 @@
-(defproject medley "1.0.0"
+(defproject dev.weavejester/medley "1.10.0"
:description "A lightweight library of useful, mostly pure functions"
:url "https://github.com/weavejester/medley"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
- :dependencies [[org.clojure/clojure "1.7.0"]]
- :plugins [[lein-codox "0.10.3"]
- [lein-cljsbuild "1.1.2"]
- [lein-doo "0.1.6"]]
+ :dependencies [[org.clojure/clojure "1.9.0"]]
+ :plugins [[lein-codox "0.10.8"]
+ [lein-cljsbuild "1.1.8"]
+ [lein-doo "0.1.11"]]
:codox
{:output-path "codox"
:metadata {:doc/format :markdown}
@@ -22,12 +22,13 @@
:doo {:paths {:rhino "lein run -m org.mozilla.javascript.tools.shell.Main"}}
:aliases
{"test-cljs" ["doo" "rhino" "test" "once"]
- "test-clj" ["with-profile" "default:+1.8:+1.9" "test"]
+ "test-clj" ["with-profile" "default:+1.10:+1.11:+1.12" "test"]
"test-all" ["do" ["test-clj"] ["test-cljs"]]}
:profiles
- {:provided {:dependencies [[org.clojure/clojurescript "1.7.228"]]}
- :test {:dependencies [[org.mozilla/rhino "1.7.7"]]}
- :dev {:dependencies [[criterium "0.4.3"]]
+ {:provided {:dependencies [[org.clojure/clojurescript "1.10.439"]]}
+ :test {:dependencies [[org.mozilla/rhino "1.9.1"]]}
+ :dev {:dependencies [[criterium "0.4.6"]]
:jvm-opts ^:replace {}}
- :1.8 {:dependencies [[org.clojure/clojure "1.8.0"]]}
- :1.9 {:dependencies [[org.clojure/clojure "1.9.0-alpha15"]]}})
+ :1.10 {:dependencies [[org.clojure/clojure "1.10.0"]]}
+ :1.11 {:dependencies [[org.clojure/clojure "1.11.2"]]}
+ :1.12 {:dependencies [[org.clojure/clojure "1.12.4"]]}})
=====================================
src/medley/core.cljc
=====================================
@@ -1,10 +1,12 @@
(ns medley.core
"A small collection of useful, mostly pure functions that might not look out
of place in the clojure.core namespace."
- (:refer-clojure :exclude [boolean? ex-cause ex-message uuid uuid? random-uuid]))
+ (:refer-clojure :exclude [abs boolean? ex-cause ex-message random-uuid regexp?
+ uuid uuid?]))
(defn find-first
- "Finds the first item in a collection that matches a predicate."
+ "Finds the first item in a collection that matches a predicate. Returns a
+ transducer when no collection is provided."
([pred]
(fn [rf]
(fn
@@ -15,48 +17,96 @@
(ensure-reduced (rf result x))
result)))))
([pred coll]
- (reduce (fn [_ x] (if (pred x) (reduced x))) nil coll)))
+ (reduce (fn [_ x] (when (pred x) (reduced x))) nil coll)))
(defn dissoc-in
- "Dissociate a value in a nested assocative structure, identified by a sequence
+ "Dissociate a value in a nested associative structure, identified by a sequence
of keys. Any collections left empty by the operation will be dissociated from
their containing structures."
- [m ks]
- (if-let [[k & ks] (seq ks)]
- (if (seq ks)
- (let [v (dissoc-in (get m k) ks)]
- (if (empty? v)
- (dissoc m k)
- (assoc m k v)))
- (dissoc m k))
- m))
+ ([m ks]
+ (if-let [[k & ks] (seq ks)]
+ (if (seq ks)
+ (let [v (dissoc-in (get m k) ks)]
+ (if (empty? v)
+ (dissoc m k)
+ (assoc m k v)))
+ (dissoc m k))
+ m))
+ ([m ks & kss]
+ (if-let [[ks' & kss] (seq kss)]
+ (recur (dissoc-in m ks) ks' kss)
+ (dissoc-in m ks))))
+
+(defn- editable? [coll]
+ #?(:cljs (satisfies? cljs.core/IEditableCollection coll)
+ :default (instance? clojure.lang.IEditableCollection coll)))
+
+(defn- assoc-some-transient! [m k v]
+ (if (nil? v) m (assoc! m k v)))
(defn assoc-some
- "Associates a key with a value in a map, if and only if the value is not nil."
+ "Associates a key k, with a value v in a map m, if and only if v is not nil."
([m k v]
(if (nil? v) m (assoc m k v)))
([m k v & kvs]
- (reduce (fn [m [k v]] (assoc-some m k v))
- (assoc-some m k v)
- (partition 2 kvs))))
-
-(defn- editable? [coll]
- #?(:clj (instance? clojure.lang.IEditableCollection coll)
- :cljs (satisfies? cljs.core.IEditableCollection coll)))
+ (if (editable? m)
+ (loop [acc (assoc-some-transient! (transient (or m {})) k v)
+ kvs kvs]
+ (if (next kvs)
+ (recur (assoc-some-transient! acc (first kvs) (second kvs)) (nnext kvs))
+ (if (zero? (count acc))
+ m
+ (with-meta (persistent! acc) (meta m)))))
+ (loop [acc (assoc-some m k v)
+ kvs kvs]
+ (if (next kvs)
+ (recur (assoc-some acc (first kvs) (second kvs)) (nnext kvs))
+ acc)))))
+
+(defn update-existing
+ "Updates a value in a map given a key and a function, if and only if the key
+ exists in the map. See: `clojure.core/update`."
+ {:arglists '([m k f & args])
+ :added "1.1.0"}
+ ([m k f]
+ (if-let [kv (find m k)] (assoc m k (f (val kv))) m))
+ ([m k f x]
+ (if-let [kv (find m k)] (assoc m k (f (val kv) x)) m))
+ ([m k f x y]
+ (if-let [kv (find m k)] (assoc m k (f (val kv) x y)) m))
+ ([m k f x y z]
+ (if-let [kv (find m k)] (assoc m k (f (val kv) x y z)) m))
+ ([m k f x y z & more]
+ (if-let [kv (find m k)] (assoc m k (apply f (val kv) x y z more)) m)))
+
+(defn update-existing-in
+ "Updates a value in a nested associative structure, if and only if the key
+ path exists. See: `clojure.core/update-in`."
+ {:added "1.3.0"}
+ [m ks f & args]
+ (let [up (fn up [m ks f args]
+ (let [[k & ks] ks]
+ (if-let [kv (find m k)]
+ (if ks
+ (assoc m k (up (val kv) ks f args))
+ (assoc m k (apply f (val kv) args)))
+ m)))]
+ (up m ks f args)))
(defn- reduce-map [f coll]
- (if (editable? coll)
- (persistent! (reduce-kv (f assoc!) (transient (empty coll)) coll))
- (reduce-kv (f assoc) (empty coll) coll)))
+ (let [coll' (if (record? coll) (into {} coll) coll)]
+ (if (editable? coll')
+ (persistent! (reduce-kv (f assoc!) (transient (empty coll')) coll'))
+ (reduce-kv (f assoc) (empty coll') coll'))))
(defn map-entry
"Create a map entry for a key and value pair."
[k v]
- #?(:clj (clojure.lang.MapEntry. k v)
- :cljs [k v]))
+ #?(:cljs (cljs.core/MapEntry. k v nil)
+ :default (clojure.lang.MapEntry. k v)))
(defn map-kv
- "Maps a function over the key/value pairs of an associate collection. Expects
+ "Maps a function over the key/value pairs of an associative collection. Expects
a function that takes two arguments, the key and value, and returns the new
key and value as a collection of two elements."
[f coll]
@@ -68,9 +118,33 @@
(reduce-map (fn [xf] (fn [m k v] (xf m (f k) v))) coll))
(defn map-vals
- "Maps a function over the values of an associative collection."
+ "Maps a function over the values of one or more associative collections.
+ The function should accept number-of-colls arguments. Any keys which are not
+ shared among all collections are ignored."
+ ([f coll]
+ (reduce-map (fn [xf] (fn [m k v] (xf m k (f v)))) coll))
+ ([f c1 & colls]
+ (reduce-map
+ (fn [xf]
+ (fn [m k v]
+ (if (every? #(contains? % k) colls)
+ (xf m k (apply f v (map #(get % k) colls)))
+ m)))
+ c1)))
+
+(defn map-kv-keys
+ "Maps a function over the key/value pairs of an associative collection, using
+ the return of the function as the new key."
+ {:added "1.2.0"}
[f coll]
- (reduce-map (fn [xf] (fn [m k v] (xf m k (f v)))) coll))
+ (reduce-map (fn [xf] (fn [m k v] (xf m (f k v) v))) coll))
+
+(defn map-kv-vals
+ "Maps a function over the key/value pairs of an associative collection, using
+ the return of the function as the new value."
+ {:added "1.2.0"}
+ [f coll]
+ (reduce-map (fn [xf] (fn [m k v] (xf m k (f k v)))) coll))
(defn filter-kv
"Returns a new associative collection of the items in coll for which
@@ -110,21 +184,21 @@
(defn queue
"Creates an empty persistent queue, or one populated with a collection."
- ([] #?(:clj clojure.lang.PersistentQueue/EMPTY
- :cljs cljs.core.PersistentQueue.EMPTY))
+ ([] #?(:cljs cljs.core/PersistentQueue.EMPTY
+ :default clojure.lang.PersistentQueue/EMPTY))
([coll] (into (queue) coll)))
(defn queue?
"Returns true if x implements clojure.lang.PersistentQueue."
[x]
- (instance? #?(:clj clojure.lang.PersistentQueue
- :cljs cljs.core.PersistentQueue) x))
+ (instance? #?(:cljs cljs.core/PersistentQueue
+ :default clojure.lang.PersistentQueue) x))
(defn boolean?
"Returns true if x is a boolean."
[x]
- #?(:clj (instance? Boolean x)
- :cljs (or (true? x) (false? x))))
+ #?(:cljs (or (true? x) (false? x))
+ :default (instance? Boolean x)))
(defn least
"Return the least argument (as defined by the compare function) in O(n) time."
@@ -134,6 +208,26 @@
([a b] (if (neg? (compare a b)) a b))
([a b & more] (reduce least (least a b) more)))
+(defn least-by
+ "Return the argument for which (keyfn x) is least. Determined by the compare
+ function in O(n) time. Prefer `clojure.core/min-key` if keyfn returns numbers."
+ {:arglists '([keyfn & xs])
+ :added "1.6.0"}
+ ([_] nil)
+ ([_ x] x)
+ ([keyfn x y] (if (neg? (compare (keyfn x) (keyfn y))) x y))
+ ([keyfn x y & more]
+ (let [kx (keyfn x) ky (keyfn y)
+ [v kv] (if (neg? (compare kx ky)) [x kx] [y ky])]
+ (loop [v v kv kv more more]
+ (if more
+ (let [w (first more)
+ kw (keyfn w)]
+ (if (pos? (compare kw kv))
+ (recur v kv (next more))
+ (recur w kw (next more))))
+ v)))))
+
(defn greatest
"Find the greatest argument (as defined by the compare function) in O(n) time."
{:arglists '([& xs])}
@@ -142,6 +236,57 @@
([a b] (if (pos? (compare a b)) a b))
([a b & more] (reduce greatest (greatest a b) more)))
+(defn greatest-by
+ "Return the argument for which (keyfn x) is greatest. Determined by the compare
+ function in O(n) time. Prefer `clojure.core/max-key` if keyfn returns numbers."
+ {:arglists '([keyfn & xs])
+ :added "1.6.0"}
+ ([_] nil)
+ ([_ x] x)
+ ([keyfn x y] (if (pos? (compare (keyfn x) (keyfn y))) x y))
+ ([keyfn x y & more]
+ (let [kx (keyfn x) ky (keyfn y)
+ [v kv] (if (pos? (compare kx ky)) [x kx] [y ky])]
+ (loop [v v kv kv more more]
+ (if more
+ (let [w (first more)
+ kw (keyfn w)]
+ (if (neg? (compare kw kv))
+ (recur v kv (next more))
+ (recur w kw (next more))))
+ v)))))
+
+(defn join
+ "Lazily concatenates a collection of collections into a flat sequence."
+ {:added "1.1.0"}
+ [colls]
+ (lazy-seq
+ (when-let [s (seq colls)]
+ (concat (first s) (join (rest s))))))
+
+(defn deep-merge
+ "Recursively merges maps together. If all the maps supplied have nested maps
+ under the same keys, these nested maps are merged. Otherwise the value is
+ overwritten, as in `clojure.core/merge`."
+ {:arglists '([& maps])
+ :added "1.1.0"}
+ ([])
+ ([a] a)
+ ([a b]
+ (when (or a b)
+ (letfn [(merge-entry [m e]
+ (let [k (key e)
+ v' (val e)]
+ (if (contains? m k)
+ (assoc m k (let [v (get m k)]
+ (if (and (map? v) (map? v'))
+ (deep-merge v v')
+ v')))
+ (assoc m k v'))))]
+ (reduce merge-entry (or a {}) (seq b)))))
+ ([a b & more]
+ (reduce deep-merge (or a {}) (cons b more))))
+
(defn mapply
"Applies a function f to the argument list formed by concatenating
everything but the last element of args with the last element of
@@ -151,6 +296,54 @@
([f m] (apply f (apply concat m)))
([f a & args] (apply f a (apply concat (butlast args) (last args)))))
+(defn collate-by
+ "Similar to `clojure.core/group-by`, this groups values in a collection,
+ coll, based on the return value of a function, keyf applied to each element.
+
+ Unlike `group-by`, the values of the map are constructed via an initf and
+ collatef function. The initf function is applied to the first element
+ matched by keyf, and defaults to the identity function. The collatef function
+ takes the result of initf and the next keyed element, and produces a new
+ value.
+
+ To put this in context, the `group-by` function can be defined as:
+
+ (defn group-by [f coll]
+ (collate-by f conj vector coll))
+
+ While the `medley.core/index-by` function can be (and is) defined as:
+
+ (defn index-by [f coll]
+ (collate-by f (fn [_ x] x) coll))"
+ {:added "1.8.0"}
+ ([keyf collatef coll]
+ (collate-by keyf collatef identity coll))
+ ([keyf collatef initf coll]
+ (persistent!
+ (reduce (fn [m v]
+ (let [k (keyf v)]
+ (assoc! m k #?(:cljs
+ (if (contains? m k)
+ (collatef (get m k) v)
+ (initf v))
+ :default
+ (if-let [kv (find m k)]
+ (collatef (val kv) v)
+ (initf v))))))
+ (transient {})
+ coll))))
+
+(defn index-by
+ "Returns a map of the elements of coll keyed by the result of f on each
+ element. The value at each key will be the last element in coll associated
+ with that key. This function is similar to `clojure.core/group-by`, except
+ that elements with the same key are overwritten, rather than added to a
+ vector of values."
+ {:added "1.2.0"}
+ [f coll]
+ #_(persistent! (reduce #(assoc! %1 (f %2) %2) (transient {}) coll))
+ (collate-by f (fn [_ x] x) coll))
+
(defn interleave-all
"Returns a lazy seq of the first item in each coll, then the second, etc.
Unlike `clojure.core/interleave`, the returned seq contains all items in the
@@ -166,13 +359,14 @@
(or s1 s2)))))
([c1 c2 & colls]
(lazy-seq
- (let [ss (remove nil? (map seq (conj colls c2 c1)))]
- (if (seq ss)
+ (let [ss (keep seq (conj colls c2 c1))]
+ (when (seq ss)
(concat (map first ss) (apply interleave-all (map rest ss))))))))
(defn distinct-by
"Returns a lazy sequence of the elements of coll, removing any elements that
- return duplicate values when passed to a function f."
+ return duplicate values when passed to a function f. Returns a stateful
+ transducer when no collection is provided."
([f]
(fn [rf]
(let [seen (volatile! #{})]
@@ -199,7 +393,8 @@
(defn dedupe-by
"Returns a lazy sequence of the elements of coll, removing any **consecutive**
- elements that return duplicate values when passed to a function f."
+ elements that return duplicate values when passed to a function f. Returns a
+ stateful transducer when no collection is provided."
([f]
(fn [rf]
(let [pv (volatile! ::none)]
@@ -218,7 +413,8 @@
(defn take-upto
"Returns a lazy sequence of successive items from coll up to and including
- the first item for which `(pred item)` returns true."
+ the first item for which `(pred item)` returns true. Returns a transducer
+ when no collection is provided."
([pred]
(fn [rf]
(fn
@@ -233,11 +429,12 @@
(lazy-seq
(when-let [s (seq coll)]
(let [x (first s)]
- (cons x (if-not (pred x) (take-upto pred (rest s)))))))))
+ (cons x (when-not (pred x) (take-upto pred (rest s)))))))))
(defn drop-upto
"Returns a lazy sequence of the items in coll starting *after* the first item
- for which `(pred item)` returns true."
+ for which `(pred item)` returns true. Returns a stateful transducer when no
+ collection is provided."
([pred]
(fn [rf]
(let [dv (volatile! true)]
@@ -251,9 +448,111 @@
([pred coll]
(rest (drop-while (complement pred) coll))))
+(defn partition-between
+ "Applies pred to successive values in coll, splitting it each time `(pred
+ prev-item item)` returns logical true. Returns a lazy seq of partitions.
+ Returns a stateful transducer when no collection is provided."
+ {:added "1.7.0"}
+ ([pred]
+ (fn [rf]
+ (let [part #?(:clj (java.util.ArrayList.)
+ :cljr (System.Collections.ArrayList.)
+ :cljs (array-list))
+ prev (volatile! ::none)]
+ (fn
+ ([] (rf))
+ ([result]
+ (rf (if #?(:cljr (zero? (.-Count part))
+ :default (.isEmpty part))
+ result
+ (let [v (vec (#?(:cljr .ToArray :default .toArray) part))]
+ (#?(:cljr .Clear :default .clear) part)
+ (unreduced (rf result v))))))
+ ([result input]
+ (let [p @prev]
+ (vreset! prev input)
+ (if (or (#?(:cljs keyword-identical? :default identical?) p ::none)
+ (not (pred p input)))
+ (do (#?(:cljr .Add :default .add) part input)
+ result)
+ (let [v (vec (#?(:cljr .ToArray :default .toArray) part))]
+ (#?(:cljr .Clear :default .clear) part)
+ (let [ret (rf result v)]
+ (when-not (reduced? ret)
+ (#?(:cljr .Add :default .add) part input))
+ ret)))))))))
+ ([pred coll]
+ (lazy-seq
+ (letfn [(take-part [prev coll]
+ (lazy-seq
+ (when-let [[x & xs] (seq coll)]
+ (when-not (pred prev x)
+ (cons x (take-part x xs))))))]
+ (when-let [[x & xs] (seq coll)]
+ (let [run (take-part x xs)]
+ (cons (cons x run)
+ (partition-between pred
+ (lazy-seq (drop (count run) xs))))))))))
+
+(defn partition-after
+ "Returns a lazy sequence of partitions, splitting after `(pred item)` returns
+ true. Returns a stateful transducer when no collection is provided."
+ {:added "1.5.0"}
+ ([pred]
+ (partition-between (fn [x _] (pred x))))
+ ([pred coll]
+ (partition-between (fn [x _] (pred x)) coll)))
+
+(defn partition-before
+ "Returns a lazy sequence of partitions, splitting before `(pred item)` returns
+ true. Returns a stateful transducer when no collection is provided."
+ {:added "1.5.0"}
+ ([pred]
+ (partition-between (fn [_ x] (pred x))))
+ ([pred coll]
+ (partition-between (fn [_ x] (pred x)) coll)))
+
+(defn window
+ "A sliding window, returning a lazy sequence of partitions, containing each
+ element and n-1 preceeding elements, when present. Therefore partitions at the
+ start may contain fewer items than the rest. Returns a stateful transducer
+ when no collection is provided. For a sliding window containing each element
+ and n-1 _following_ elements, use `clojure.core/partition` with a `step` size
+ of 1."
+ {:added "1.9.0"}
+ ([n]
+ (fn [rf]
+ (let [part #?(:clj (java.util.ArrayList. n)
+ :cljr (System.Collections.ArrayList.)
+ :cljs (array))]
+ (fn
+ ([] (rf))
+ ([result] (rf result))
+ ([result x]
+ #?(:clj (.add part x) :cljr (.Add part x) :cljs (.push part x))
+ (when (< n #?(:clj (.size part)
+ :cljr (.Count part)
+ :cljs (.-length part)))
+ #?(:clj (.remove part 0)
+ :cljr (.RemoveAt part 0)
+ :cljs (.shift part)))
+ (rf result (vec #?(:clj (.toArray part)
+ :cljr (.ToArray part)
+ :cljs (.slice part)))))))))
+ ([n coll]
+ (letfn [(part [part-n coll]
+ (let [run (doall (take part-n coll))]
+ (lazy-seq
+ (when (== part-n (count run))
+ (cons run
+ (part (min n (inc part-n))
+ (if (== n part-n) (rest coll) coll)))))))]
+ (part (min 1 n) coll))))
+
(defn indexed
"Returns an ordered, lazy sequence of vectors `[index item]`, where item is a
- value in coll, and index its position starting from zero."
+ value in coll, and index its position starting from zero. Returns a stateful
+ transducer when no collection is provided."
([]
(fn [rf]
(let [i (volatile! -1)]
@@ -265,6 +564,75 @@
([coll]
(map-indexed vector coll)))
+(defn insert-nth
+ "Returns a lazy sequence of the items in coll, with a new item inserted at
+ the supplied index, followed by all subsequent items of the collection. Runs
+ in O(n) time. Returns a stateful transducer when no collection is provided."
+ {:added "1.2.0"}
+ ([index item]
+ (fn [rf]
+ (let [idx (volatile! (inc index))]
+ (fn
+ ([] (rf))
+ ([result]
+ (if (= @idx 1)
+ (rf (rf result item))
+ (rf result)))
+ ([result x]
+ (if (zero? (vswap! idx dec))
+ (rf (rf result item) x)
+ (rf result x)))))))
+ ([index item coll]
+ (lazy-seq
+ (if (zero? index)
+ (cons item coll)
+ (when (seq coll)
+ (cons (first coll) (insert-nth (dec index) item (rest coll))))))))
+
+(defn remove-nth
+ "Returns a lazy sequence of the items in coll, except for the item at the
+ supplied index. Runs in O(n) time. Returns a stateful transducer when no
+ collection is provided."
+ {:added "1.2.0"}
+ ([index]
+ (fn [rf]
+ (let [idx (volatile! (inc index))]
+ (fn
+ ([] (rf))
+ ([result] (rf result))
+ ([result x]
+ (if (zero? (vswap! idx dec))
+ result
+ (rf result x)))))))
+ ([index coll]
+ (lazy-seq
+ (if (zero? index)
+ (rest coll)
+ (when (seq coll)
+ (cons (first coll) (remove-nth (dec index) (rest coll))))))))
+
+(defn replace-nth
+ "Returns a lazy sequence of the items in coll, with a new item replacing the
+ item at the supplied index. Runs in O(n) time. Returns a stateful transducer
+ when no collection is provided."
+ {:added "1.2.0"}
+ ([index item]
+ (fn [rf]
+ (let [idx (volatile! (inc index))]
+ (fn
+ ([] (rf))
+ ([result] (rf result))
+ ([result x]
+ (if (zero? (vswap! idx dec))
+ (rf result item)
+ (rf result x)))))))
+ ([index item coll]
+ (lazy-seq
+ (if (zero? index)
+ (cons item (rest coll))
+ (when (seq coll)
+ (cons (first coll) (replace-nth (dec index) item (rest coll))))))))
+
(defn abs
"Returns the absolute value of a number."
[x]
@@ -276,14 +644,16 @@
This function therefore acts like an atomic `deref` then `swap!`."
{:arglists '([atom f & args])}
([atom f]
- #?(:clj (loop []
- (let [value @atom]
- (if (compare-and-set! atom value (f value))
- value
- (recur))))
- :cljs (let [value @atom]
- (reset! atom (f value))
- value)))
+ #?(:cljs
+ (let [value @atom]
+ (reset! atom (f value))
+ value)
+ :default
+ (loop []
+ (let [value @atom]
+ (if (compare-and-set! atom value (f value))
+ value
+ (recur))))))
([atom f & args]
(deref-swap! atom #(apply f % args))))
@@ -299,26 +669,31 @@
Clojure as well as ClojureScript."
[ex]
#?(:clj (when (instance? Throwable ex) (.getMessage ^Throwable ex))
+ :cljr (when (instance? Exception ex) (.-Message ^Exception ex))
:cljs (cljs.core/ex-message ex)))
(defn ex-cause
"Returns the cause attached to the given ExceptionInfo/Throwable object. For
- all other types returns nil. Same as `cljs.core/ex-clause` except it works for
+ all other types returns nil. Same as `cljs.core/ex-cause` except it works for
Clojure as well as ClojureScript."
[ex]
#?(:clj (when (instance? Throwable ex) (.getCause ^Throwable ex))
+ :cljr (when (instance? Exception ex) (.-InnerException ^Exception ex))
:cljs (cljs.core/ex-cause ex)))
(defn uuid?
"Returns true if the value is a UUID."
[x]
- (instance? #?(:clj java.util.UUID :cljs cljs.core.UUID) x))
+ (instance? #?(:clj java.util.UUID
+ :cljr System.Guid
+ :cljs cljs.core/UUID) x))
(defn uuid
"Returns a UUID generated from the supplied string. Same as `cljs.core/uuid`
in ClojureScript, while in Clojure it returns a `java.util.UUID` object."
[s]
#?(:clj (java.util.UUID/fromString s)
+ :cljr (System.Guid. s)
:cljs (cljs.core/uuid s)))
(defn random-uuid
@@ -326,4 +701,81 @@
for Clojure as well as ClojureScript."
[]
#?(:clj (java.util.UUID/randomUUID)
+ :cljr (System.Guid/NewGuid)
:cljs (cljs.core/random-uuid)))
+
+(defn regexp?
+ "Returns true if the value is a regular expression."
+ {:added "1.4.0"}
+ [x]
+ (instance? #?(:clj java.util.regex.Pattern
+ :cljr System.Text.RegularExpressions.Regex
+ :cljs js/RegExp) x))
+
+(defn index-of
+ "Returns the index of the first occurrence of the item in the sequential
+ collection coll, or nil if not found."
+ {:added "1.9.0"}
+ #?(:clj [^java.util.List coll item]
+ :cljr [^System.Collections.IEnumerable coll item]
+ :default [coll item])
+ (when (some? coll)
+ (let [index (#?(:cljr .IndexOf :default .indexOf) coll item)]
+ (when-not (neg? index) index))))
+
+(defn find-in
+ "Similar to `clojure.core/find`, except that it finds a key/value pair in an
+ nested associate structure `m`, given a sequence of keys `ks`. See also:
+ `clojure.core/get-in`."
+ {:added "1.9.0"}
+ [m ks]
+ (if (next ks)
+ (-> (get-in m (butlast ks)) (find (last ks)))
+ (find m (first ks))))
+
+(letfn [(first* [s v] (if s (first s) v))]
+ (defn map-padded
+ "Similar to `clojure.core/map`, except that it runs until all colls are
+ exhausted, using `val` as the missing value for each exhausted coll."
+ {:added "1.10.0"}
+ #_{:clj-kondo/ignore [:unused-binding]}
+ ([f val c1] (map f c1))
+ ([f val c1 c2]
+ (lazy-seq
+ (let [s1 (seq c1) s2 (seq c2)]
+ (when (or s1 s2)
+ (cons (f (first* s1 val) (first* s2 val))
+ (map-padded f val (rest s1) (rest s2)))))))
+ ([f val c1 c2 c3]
+ (lazy-seq
+ (let [s1 (seq c1) s2 (seq c2) s3 (seq c3)]
+ (when (or s1 s2 s3)
+ (cons (f (first* s1 val) (first* s2 val) (first* s3 val))
+ (map-padded f val (rest s1) (rest s2) (rest s3)))))))
+ ([f val c1 c2 c3 & colls]
+ (let [step (fn step [cs]
+ (lazy-seq
+ (let [ss (mapv seq cs)]
+ (when (some identity ss)
+ (cons (apply f (mapv #(first* % val) ss))
+ (step (mapv rest ss)))))))]
+ (step (conj colls c3 c2 c1))))))
+
+(defn sequence-padded
+ "Similar to `clojure.core/sequence`, except that it runs until all colls are
+ exhausted, using `val` as the missing value for each exhausted coll."
+ {:added "1.10.0"}
+ #_{:clj-kondo/ignore [:unused-binding]}
+ ([xform val c1] (sequence xform c1))
+ ([xform val c1 & colls]
+ (let [f (xform conj)
+ step (fn step [cs]
+ (lazy-seq
+ (let [ss (mapv seq cs)]
+ (if (some identity ss)
+ (let [res (apply f nil (mapv #(if % (first %) val) ss))]
+ (cond (reduced? res) (f (deref res))
+ (nil? res) (step (mapv rest ss))
+ :else (concat res (lazy-seq (step (mapv rest ss))))))
+ (f nil)))))]
+ (step (conj colls c1)))))
=====================================
test/medley/core_test.cljc
=====================================
@@ -1,7 +1,8 @@
(ns medley.core-test
- #?(:clj (:import [clojure.lang ArityException]))
- (:require #?(:clj [clojure.test :refer :all]
- :cljs [cljs.test :refer-macros [deftest is testing]])
+ #?@(:cljs []
+ :default [(:import [clojure.lang ArityException])])
+ (:require #?(:cljs [cljs.test :refer-macros [deftest is testing]]
+ :default [clojure.test :refer :all])
[medley.core :as m]))
(deftest test-find-first
@@ -19,13 +20,45 @@
{}))
(is (= (m/dissoc-in {:a {:b {:c 1} :d 2}} [:a :b :c])
{:a {:d 2}}))
+ (is (= (m/dissoc-in {:a {:b {:c 1} :d 2} :b {:c {:d 2 :e 3}}} [:a :b :c] [:b :c :d])
+ {:a {:d 2} :b {:c {:e 3}}}))
(is (= (m/dissoc-in {:a 1} [])
{:a 1})))
(deftest test-assoc-some
(is (= (m/assoc-some {:a 1} :b 2) {:a 1 :b 2}))
(is (= (m/assoc-some {:a 1} :b nil) {:a 1}))
- (is (= (m/assoc-some {:a 1} :b 2 :c nil :d 3) {:a 1 :b 2 :d 3})))
+ (is (= (m/assoc-some {:a 1} :b 2 :c nil :d 3) {:a 1 :b 2 :d 3}))
+ (is (= (m/assoc-some nil :a 1) {:a 1}))
+ (is (= (m/assoc-some nil :a 1 :b 2) {:a 1 :b 2}))
+ (is (nil? (m/assoc-some nil :a nil)))
+ (is (nil? (m/assoc-some nil :a nil :b nil)))
+ (let [input (with-meta {:a 1} {:m 42})]
+ (is (= {:m 42} (meta (m/assoc-some input :b 2 :c nil :d 3))))))
+
+(deftest test-update-existing
+ (is (= (m/update-existing {:a 1} :a inc) {:a 2}))
+ (is (= (m/update-existing {:a 1 :b 2} :a inc) {:a 2 :b 2}))
+ (is (= (m/update-existing {:b 2} :a inc) {:b 2}))
+ (is (= (m/update-existing {:a nil} :a str) {:a ""}))
+ (is (= (m/update-existing {} :a str) {})))
+
+(deftest test-update-existing-in
+ (is (= (m/update-existing-in {:a 1} [:a] inc) {:a 2}))
+ (is (= (m/update-existing-in {:a 1 :b 2} [:a] inc) {:a 2 :b 2}))
+ (is (= (m/update-existing-in {:b 2} [:a] inc) {:b 2}))
+ (is (= (m/update-existing-in {:a nil} [:a] str) {:a ""}))
+ (is (= (m/update-existing-in {} [:a] str) {}))
+ (is (= (m/update-existing-in {:a [:b {:c 42} :d]} [:a 1 :c] inc)
+ {:a [:b {:c 43} :d]}))
+ (is (= (m/update-existing-in {:a [:b {:c 42} :d]} [:a 1 :c] + 7)
+ {:a [:b {:c 49} :d]}))
+ (is (= (m/update-existing-in {:a [:b {:c 42} :d]} [:a 1 :c] + 3 4)
+ {:a [:b {:c 49} :d]}))
+ (is (= (m/update-existing-in {:a [:b {:c 42} :d]} [:a 1 :c] + 3 3 1)
+ {:a [:b {:c 49} :d]}))
+ (is (= (m/update-existing-in {:a [:b {:c 42} :d]} [:a 1 :c] vector 9 10 11 12 13 14)
+ {:a [:b {:c [42 9 10 11 12 13 14]} :d]})))
(deftest test-map-entry
(is (= (key (m/map-entry :a 1)) :a))
@@ -35,25 +68,62 @@
(is (= (type (m/map-entry :a 1))
(type (first {:a 1})))))
+(defrecord MyRecord [x])
+
(deftest test-map-kv
(is (= (m/map-kv (fn [k v] [(name k) (inc v)]) {:a 1 :b 2})
{"a" 2 "b" 3}))
(is (= (m/map-kv (fn [k v] [(name k) (inc v)]) (sorted-map :a 1 :b 2))
{"a" 2 "b" 3}))
(is (= (m/map-kv (fn [k v] (m/map-entry (name k) (inc v))) {:a 1 :b 2})
- {"a" 2 "b" 3})))
+ {"a" 2 "b" 3}))
+ (testing "map-kv with record"
+ (is (= (m/map-kv (fn [k v] (m/map-entry (name k) (inc v))) (->MyRecord 1)) {"x" 2}))))
(deftest test-map-keys
(is (= (m/map-keys name {:a 1 :b 2})
{"a" 1 "b" 2}))
(is (= (m/map-keys name (sorted-map :a 1 :b 2))
- (sorted-map "a" 1 "b" 2))))
+ (sorted-map "a" 1 "b" 2)))
+ (testing "map-keys with record"
+ (is (= (m/map-keys name (->MyRecord 1)) {"x" 1}))))
(deftest test-map-vals
(is (= (m/map-vals inc {:a 1 :b 2})
{:a 2 :b 3}))
(is (= (m/map-vals inc (sorted-map :a 1 :b 2))
- (sorted-map :a 2 :b 3))))
+ (sorted-map :a 2 :b 3)))
+ (testing "map-vals with record"
+ (is (= (m/map-vals inc (->MyRecord 1)) {:x 2})))
+ (testing "multiple collections"
+ (is (= (m/map-vals + {:a 1 :b 2 :c 3} {:a 4 :c 5 :d 6})
+ {:a 5, :c 8}))
+ (is (= (m/map-vals min
+ (sorted-map :z 10 :y 8 :x 4)
+ {:x 7, :y 14, :z 13}
+ {:x 11, :y 6, :z 9}
+ {:x 19, :y 3, :z 2}
+ {:x 4, :y 0, :z 16}
+ {:x 17, :y 14, :z 13})
+ (sorted-map :x 4 :y 0 :z 2)))
+ (is (= (m/map-vals #(%1 %2) {:a nil? :b some?} {:b nil})
+ {:b false}))))
+
+(deftest test-map-kv-keys
+ (is (= (m/map-kv-keys + {1 2, 2 4})
+ {3 2, 6 4}))
+ (is (= (m/map-kv-keys + (sorted-map 1 2, 2 4))
+ (sorted-map 3 2, 6 4)))
+ (is (= (m/map-kv-keys str (->MyRecord 1))
+ {":x1" 1})))
+
+(deftest test-map-kv-vals
+ (is (= (m/map-kv-vals + {1 2, 2 4})
+ {1 3, 2 6}))
+ (is (= (m/map-kv-vals + (sorted-map 1 2, 2 4))
+ (sorted-map 1 3, 2 6)))
+ (is (= (m/map-kv-vals str (->MyRecord 1))
+ {:x ":x1"})))
(deftest test-filter-kv
(is (= (m/filter-kv (fn [k v] (and (keyword? k) (number? v))) {"a" 1 :b 2 :c "d"})
@@ -93,17 +163,17 @@
(deftest test-queue
(testing "empty"
- #?(:clj (is (instance? clojure.lang.PersistentQueue (m/queue)))
- :cljs (is (instance? cljs.core.PersistentQueue (m/queue))))
+ #?(:cljs (is (instance? cljs.core.PersistentQueue (m/queue)))
+ :default (is (instance? clojure.lang.PersistentQueue (m/queue))))
(is (empty? (m/queue))))
(testing "not empty"
- #?(:clj (is (instance? clojure.lang.PersistentQueue (m/queue [1 2 3])))
- :cljs (is (instance? cljs.core.PersistentQueue (m/queue [1 2 3]))))
+ #?(:cljs (is (instance? cljs.core.PersistentQueue (m/queue [1 2 3])))
+ :default (is (instance? clojure.lang.PersistentQueue (m/queue [1 2 3]))))
(is (= (first (m/queue [1 2 3])) 1))))
(deftest test-queue?
- #?(:clj (is (m/queue? clojure.lang.PersistentQueue/EMPTY))
- :cljs (is (m/queue? cljs.core.PersistentQueue.EMPTY)))
+ #?(:cljs (is (m/queue? cljs.core.PersistentQueue.EMPTY))
+ :default (is (m/queue? clojure.lang.PersistentQueue/EMPTY)))
(is (not (m/queue? []))))
(deftest test-boolean?
@@ -119,12 +189,49 @@
(is (= (m/least "a" "b") "a"))
(is (= (m/least 3 2 5 -1 0 2) -1)))
+(deftest test-least-by
+ (is (= (m/least-by :a) nil))
+ (is (= (m/least-by :a {:a 42}) {:a 42}))
+ (is (= (m/least-by :a {:a 3} {:a 1} {:a 2}) {:a 1}))
+ (is (= (m/least-by :a {:a 3 :b 1} {:a 2 :b 2} {:a 2 :b 3}) {:a 2 :b 3}))
+ (is (= (m/least-by last "foo" "baa" "baz" "qux") "baa")))
+
(deftest test-greatest
(is (= (m/greatest) nil))
(is (= (m/greatest "a") "a"))
(is (= (m/greatest "a" "b") "b"))
(is (= (m/greatest 3 2 5 -1 0 2) 5)))
+(deftest test-greatest-by
+ (is (= (m/greatest-by :a) nil))
+ (is (= (m/greatest-by :a {:a 42}) {:a 42}))
+ (is (= (m/greatest-by :a {:a 3} {:a 1} {:a 2}) {:a 3}))
+ (is (= (m/greatest-by :a {:a 2 :b 1} {:a 3 :b 2} {:a 3 :b 3}) {:a 3 :b 3}))
+ (is (= (m/greatest-by last "foo" "baa" "baz" "qux") "baz")))
+
+(deftest test-join
+ (is (= (m/join [[1 2] [] [3] [4 5 6]]) [1 2 3 4 5 6]))
+ (is (= (m/join (sorted-map :x 1, :y 2, :z 3)) [:x 1 :y 2 :z 3]))
+ (let [a (atom 0)
+ s (m/join (iterate #(do (swap! a inc) (range (inc (count %)))) ()))]
+ (is (= (first s) 0))
+ (is (= @a 1))
+ (is (= (second s) 0))
+ (is (= @a 2))))
+
+(deftest test-deep-merge
+ (is (= (m/deep-merge) nil))
+ (is (= (m/deep-merge {:a 1}) {:a 1}))
+ (is (= (m/deep-merge {:a 1} nil) {:a 1}))
+ (is (= (m/deep-merge {:a 1} {:a 2 :b 3}) {:a 2 :b 3}))
+ (is (= (m/deep-merge {:a {:b 1 :c 2}} {:a {:b 2 :d 3}}) {:a {:b 2 :c 2 :d 3}}))
+ (is (= (m/deep-merge {:a {:b 1}} {:a 1}) {:a 1}))
+ (is (= (m/deep-merge {:a 1} {:b 2} {:b 3 :c 4}) {:a 1 :b 3 :c 4}))
+ (is (= (m/deep-merge {:a {:b {:c {:d 1}}}} {:a {:b {:c {:e 2}}}}) {:a {:b {:c {:d 1 :e 2}}}}))
+ (is (= (m/deep-merge {:a {:b [1 2]}} {:a {:b [3 4]}}) {:a {:b [3 4]}}))
+ (is (= (m/deep-merge (->MyRecord 1) {:x 2}) (->MyRecord 2)))
+ (is (= (m/deep-merge {:a (->MyRecord 1)} {:a {:x 2 :y 3}}) {:a (map->MyRecord {:x 2 :y 3})})))
+
(deftest test-mapply
(letfn [(foo [& {:keys [bar]}] bar)]
(is (= (m/mapply foo {}) nil))
@@ -135,9 +242,31 @@
(is (= (m/mapply foo 0 {:baz 1}) [0 1]))
(is (= (m/mapply foo 0 {:spam 1}) [0 nil]))
(is (= (m/mapply foo 0 nil) [0 nil]))
- #?@(:clj [(is (thrown? ArityException (m/mapply foo {})))
- (is (thrown? IllegalArgumentException (m/mapply foo 0)))]
- :cljs [(is (thrown? js/Error (m/mapply foo 0)))])))
+ (is (thrown? #?(:clj IllegalArgumentException
+ :cljr ArgumentException
+ :cljs js/Error) (m/mapply foo 0)))
+ #?(:cljs (is (= (m/mapply foo {}) [nil nil]))
+ :default (is (thrown? ArityException (m/mapply foo {}))))))
+
+(deftest test-collate-by
+ (is (= (m/collate-by identity conj vector [1 2 2 3 3])
+ {1 [1], 2 [2 2], 3 [3 3]}))
+ (is (= (m/collate-by identity conj hash-set [1 2 2 3 3])
+ {1 #{1}, 2 #{2}, 3 #{3}}))
+ (is (= (m/collate-by first conj list ["foo" "bar" "baz"])
+ {\f '("foo"), \b '("baz" "bar")}))
+ (is (= (m/collate-by first (fn [_ x] x) ["foo" "bar" "baz"])
+ {\f "foo", \b "baz"}))
+ (is (= (m/collate-by even? + [1 2 3 4 5 6])
+ {true 12, false 9}))
+ (is (= (m/collate-by first (fn [_ x] x) []) {}))
+ (is (= (m/collate-by first conj vector []) {})))
+
+(deftest test-index-by
+ (is (= (m/index-by identity [1 2 3]) {1 1, 2 2, 3 3}))
+ (is (= (m/index-by inc [1 2 3]) {2 1, 3 2, 4 3}))
+ (is (= (m/index-by first ["foo" "bar" "baz"]) {\f "foo", \b "baz"}))
+ (is (= (m/index-by first []) {})))
(deftest test-interleave-all
(is (= (m/interleave-all []) []))
@@ -209,6 +338,91 @@
(is (= (into [] (m/drop-upto zero?) [0 1 2 3 4 5 6]) [1 2 3 4 5 6]))
(is (= (into [] (m/drop-upto zero?) [1 2 3 4 5 6 7]) []))))
+(deftest test-partition-after
+ (testing "sequences"
+ (is (= (m/partition-after identity nil) '()))
+ (is (= (m/partition-after even? [1 3 5 6 8 9 10 11]) '((1 3 5 6) (8) (9 10) (11))))
+ (is (= (m/partition-after even? [2 4 6 8 10]) '((2) (4) (6) (8) (10))))
+ (is (= (m/partition-after even? [1 3 5 7 9]) '((1 3 5 7 9)))))
+
+ (testing "transducers"
+ (is (= (transduce (m/partition-after identity) conj nil) []))
+ (is (= (transduce (m/partition-after even?) conj [1 3 5 6 8 9 10 11])
+ [[1 3 5 6] [8] [9 10] [11]]))
+ (is (= (sequence (m/partition-after even?) [2 4 6 8 10]) '([2] [4] [6] [8] [10])))
+ (is (= (into [] (m/partition-after even?) [1 3 5 7 9]) [[1 3 5 7 9]]))
+ (is (= (transduce (m/partition-after even?)
+ (completing (fn [coll x]
+ (cond-> (conj coll x) (= [8] x) reduced)))
+ []
+ [1 3 5 6 8 9])
+ [[1 3 5 6] [8]]))))
+
+(deftest test-partition-before
+ (testing "sequences"
+ (is (= (m/partition-before identity nil) '()))
+ (is (= (m/partition-before even? [1 3 5 6 8 9 10 11]) '((1 3 5) (6) (8 9) (10 11))))
+ (is (= (m/partition-before even? [2 4 6 8 10]) '((2) (4) (6) (8) (10))))
+ (is (= (m/partition-before even? [1 3 5 7 9]) '((1 3 5 7 9)))))
+
+ (testing "transducers"
+ (is (= (transduce (m/partition-before identity) conj nil) []))
+ (is (= (transduce (m/partition-before even?) conj [1 3 5 6 8 9 10 11])
+ [[1 3 5] [6] [8 9] [10 11]]))
+ (is (= (sequence (m/partition-before even?) [2 4 6 8 10]) '([2] [4] [6] [8] [10])))
+ (is (= (into [] (m/partition-before even?) [1 3 5 7 9]) [[1 3 5 7 9]]))
+ (is (= (transduce (m/partition-before even?)
+ (completing (fn [coll x]
+ (cond-> (conj coll x) (= [6] x) reduced)))
+ []
+ [1 3 5 6 8 9])
+ [[1 3 5] [6]]))))
+
+(deftest test-partition-between
+ (testing "sequences"
+ (is (= (m/partition-between = nil) '()))
+ (is (= (m/partition-between < [1]) '((1))))
+ (is (= (m/partition-between < [1 1 2 2 1 1 3 3 3])
+ '((1 1) (2 2 1 1) (3 3 3))))
+ (is (= (m/partition-between < [1 2 3 4]) '((1) (2) (3) (4))))
+ (is (= (m/partition-between < [4 3 2 1]) '((4 3 2 1)))))
+
+ (testing "transducers"
+ (is (= (transduce (m/partition-between <) conj nil) []))
+ (is (= (transduce (m/partition-between <) conj [1]) [[1]]))
+ (is (= (transduce (m/partition-between <) conj [1 1 2 2 1 1 3 3 3])
+ [[1 1] [2 2 1 1] [3 3 3]]))
+ (is (= (sequence (m/partition-between <) [1 2 3 4]) '([1] [2] [3] [4])))
+ (is (= (into [] (m/partition-between <) [4 3 2 1]) [[4 3 2 1]]))
+ (is (= (transduce (m/partition-between <)
+ (completing (fn [coll x]
+ (cond-> (conj coll x) (= [8] x) reduced)))
+ []
+ [1 2 3 2 8 9])
+ [[1] [2] [3 2] [8]]))))
+
+(deftest test-window
+ (testing "sequences"
+ (is (= (m/window 2 nil) '()))
+ (is (= (m/window 2 [:a]) '((:a))))
+ (is (= (m/window 2 [:a :b]) '((:a) (:a :b))))
+ (is (= (m/window 2 [:a :b :c]) '((:a) (:a :b) (:b :c))))
+ (is (= (take 3 (m/window 0 [:a :b :c])) '(() () ())))
+ (is (= (m/window 10 [:a :b :c]) '((:a) (:a :b) (:a :b :c)))))
+ (testing "transducers"
+ (is (= (transduce (m/window 2) conj nil) []))
+ (is (= (transduce (m/window 2) conj [:a]) [[:a]]))
+ (is (= (transduce (m/window 2) conj [:a :b]) [[:a] [:a :b]]))
+ (is (= (transduce (m/window 2) conj [:a :b :c]) [[:a] [:a :b] [:b :c]]))
+ (is (= (transduce (m/window 0) conj [:a :b :c]) [[] [] []]))
+ (is (= (transduce (m/window 10) conj [:a :b :c]) [[:a] [:a :b] [:a :b :c]]))
+ (is (= (transduce (m/window 2)
+ (completing (fn [coll x]
+ (cond-> (conj coll x) (= [:a :b] x) reduced)))
+ []
+ [:a :b :c :d])
+ [[:a] [:a :b]]))))
+
(deftest test-indexed
(testing "sequences"
(is (= (m/indexed [:a :b :c :d])
@@ -222,15 +436,51 @@
(is (= (into [] (m/indexed) [])
[]))))
+(deftest test-insert-nth
+ (testing "sequences"
+ (is (= (m/insert-nth 0 :a [1 2 3 4]) [:a 1 2 3 4]))
+ (is (= (m/insert-nth 1 :a [1 2 3 4]) [1 :a 2 3 4]))
+ (is (= (m/insert-nth 3 :a [1 2 3 4]) [1 2 3 :a 4]))
+ (is (= (m/insert-nth 4 :a [1 2 3 4]) [1 2 3 4 :a])))
+
+ (testing "transducers"
+ (is (= (into [] (m/insert-nth 0 :a) [1 2 3 4]) [:a 1 2 3 4]))
+ (is (= (into [] (m/insert-nth 1 :a) [1 2 3 4]) [1 :a 2 3 4]))
+ (is (= (into [] (m/insert-nth 3 :a) [1 2 3 4]) [1 2 3 :a 4]))
+ (is (= (into [] (m/insert-nth 4 :a) [1 2 3 4]) [1 2 3 4 :a]))))
+
+(deftest test-remove-nth
+ (testing "sequences"
+ (is (= (m/remove-nth 0 [1 2 3 4]) [2 3 4]))
+ (is (= (m/remove-nth 1 [1 2 3 4]) [1 3 4]))
+ (is (= (m/remove-nth 3 [1 2 3 4]) [1 2 3])))
+
+ (testing "transducers"
+ (is (= (into [] (m/remove-nth 0) [1 2 3 4]) [2 3 4]))
+ (is (= (into [] (m/remove-nth 1) [1 2 3 4]) [1 3 4]))
+ (is (= (into [] (m/remove-nth 3) [1 2 3 4]) [1 2 3]))))
+
+(deftest test-replace-nth
+ (testing "sequences"
+ (is (= (m/replace-nth 0 :a [1 2 3 4]) [:a 2 3 4]))
+ (is (= (m/replace-nth 1 :a [1 2 3 4]) [1 :a 3 4]))
+ (is (= (m/replace-nth 3 :a [1 2 3 4]) [1 2 3 :a])))
+
+ (testing "transducers"
+ (is (= (into [] (m/replace-nth 0 :a) [1 2 3 4]) [:a 2 3 4]))
+ (is (= (into [] (m/replace-nth 1 :a) [1 2 3 4]) [1 :a 3 4]))
+ (is (= (into [] (m/replace-nth 3 :a) [1 2 3 4]) [1 2 3 :a]))))
+
(deftest test-abs
(is (= (m/abs -3) 3))
(is (= (m/abs 2) 2))
(is (= (m/abs -2.1) 2.1))
(is (= (m/abs 1.8) 1.8))
- #?@(:clj [(is (= (m/abs -1/3) 1/3))
- (is (= (m/abs 1/2) 1/2))
- (is (= (m/abs 3N) 3N))
- (is (= (m/abs -4N) 4N))]))
+ #?@(:cljs []
+ :default [(is (= (m/abs -1/3) 1/3))
+ (is (= (m/abs 1/2) 1/2))
+ (is (= (m/abs 3N) 3N))
+ (is (= (m/abs -4N) 4N))]))
(deftest test-deref-swap!
(let [a (atom 0)]
@@ -248,12 +498,14 @@
(deftest test-ex-message
(is (= (m/ex-message (ex-info "foo" {})) "foo"))
- (is (= (m/ex-message (new #?(:clj Exception :cljs js/Error) "bar")) "bar")))
+ (let [ex (new #?(:cljs js/Error :default Exception) "bar")]
+ (is (= (m/ex-message ex) "bar"))))
(deftest test-ex-cause
- (let [cause (new #?(:clj Exception :cljs js/Error) "foo")]
+ (let [cause (new #?(:cljs js/Error :default Exception) "foo")]
(is (= (m/ex-cause (ex-info "foo" {} cause)) cause))
- #?(:clj (is (= (m/ex-cause (Exception. "foo" cause)) cause)))))
+ #?@(:cljs []
+ :default [(is (= (m/ex-cause (Exception. "foo" cause)) cause))])))
(deftest test-uuid?
(let [x #uuid "d1a4adfa-d9cf-4aa5-9f05-a15365d1bfa6"]
@@ -264,12 +516,111 @@
(deftest test-uuid
(let [x (m/uuid "d1a4adfa-d9cf-4aa5-9f05-a15365d1bfa6")]
- (is (instance? #?(:clj java.util.UUID :cljs cljs.core.UUID) x))
+ (is (instance? #?(:clj java.util.UUID
+ :cljr System.Guid
+ :cljs cljs.core.UUID) x))
(is (= x #uuid "d1a4adfa-d9cf-4aa5-9f05-a15365d1bfa6"))))
(deftest test-random-uuid
(let [x (m/random-uuid)
y (m/random-uuid)]
- (is (instance? #?(:clj java.util.UUID :cljs cljs.core.UUID) x))
- (is (instance? #?(:clj java.util.UUID :cljs cljs.core.UUID) y))
+ (is (instance? #?(:clj java.util.UUID
+ :cljr System.Guid
+ :cljs cljs.core.UUID) x))
+ (is (instance? #?(:clj java.util.UUID
+ :cljr System.Guid
+ :cljs cljs.core.UUID) y))
(is (not= x y))))
+
+(deftest test-regexp?
+ (is (m/regexp? #"x"))
+ (is (not (m/regexp? "x")))
+ (is (not (m/regexp? nil))))
+
+(deftest test-index-of
+ (is (nil? (m/index-of nil :a)))
+ (is (nil? (m/index-of [] :a)))
+ (is (nil? (m/index-of '() :a)))
+
+ (is (nil? (m/index-of [:a] :b)))
+ (is (nil? (m/index-of '(:a) :b)))
+
+ (is (= 1 (m/index-of [:a :b :c :d] :b)))
+ (is (= 2 (m/index-of '(:a :b :c :d) :c)))
+
+ (is (= 1 (m/index-of (range 0 10) 1)))
+ (is (= 1 (m/index-of (map str [:a :b]) ":b"))))
+
+(deftest test-find-in
+ (is (nil? (m/find-in {} [:a])))
+ (is (nil? (m/find-in {} [:a :b])))
+ (is (nil? (m/find-in {:a {}} [:a :b])))
+ (is (= [:a 1] (m/find-in {:a 1} [:a])))
+ (is (= [:b 2] (m/find-in {:a {:b 2}} [:a :b])))
+ (is (= [:b {:c 3}] (m/find-in {:a {:b {:c 3}}} [:a :b])))
+ (is (= [:c 3] (m/find-in {:a {:b {:c 3}}} [:a :b :c]))))
+
+(deftest test-map-padded
+ (is (= (map + (range 3) (range 4) (range 5) (range 10))
+ [0 4 8]
+ (take 3 (m/map-padded + 0 (range 3) (range 4) (range 5) (range 10)))))
+ (is (= [0 4 8 19 28 35 36 37 38 39]
+ (m/map-padded + 10 (range 3) (range 4) (range 5) (range 10))))
+ (is (= ()
+ (m/map-padded + 10 () () ())))
+ (testing "laziness"
+ (let [state (volatile! [])]
+ (is (= [0 4 8 19 28]
+ (take 5 (m/map-padded (fn [a b c d]
+ (vswap! state conj [a b c d])
+ (+ a b c d))
+ 10
+ (range 3) (range 4) (range 5) (range 10)))))
+ (is (= [[0 0 0 0] [1 1 1 1] [2 2 2 2] [10 3 3 3] [10 10 4 4]]
+ @state))))
+ (testing "handles sequences with nils"
+ (is (= [[nil 0 0 0] [nil 1 1 1] [nil 2 2 2]
+ [:missing 3 3 3]
+ [:missing :missing 4 4]
+ [:missing :missing :missing 5]]
+ (take 6 (m/map-padded vector :missing [nil nil nil] (range 4) (range 5) (range 10)))))))
+
+(deftest test-sequence-padded
+ (is (= (map + (range 3) (range 4) (range 5) (range 10))
+ [0 4 8]
+ (take 3 (m/sequence-padded (map +) 0 (range 3) (range 4) (range 5) (range 10)))))
+ (is (= [0 4 8 19 28 35 36 37 38 39]
+ (m/sequence-padded (map +) 10 (range 3) (range 4) (range 5) (range 10))))
+ (is (= ()
+ (m/sequence-padded (map +) 10 () () ())))
+ (testing "laziness"
+ (let [state (volatile! [])]
+ (is (= [0 4 8 19 28]
+ (take 5 (m/sequence-padded
+ (map (fn [a b c d]
+ (vswap! state conj [a b c d])
+ (+ a b c d)))
+ 10
+ (range 3) (range 4) (range 5) (range 10)))))
+ (is (= [[0 0 0 0] [1 1 1 1] [2 2 2 2] [10 3 3 3] [10 10 4 4]]
+ @state))))
+ (testing "handles sequences with nils"
+ (is (= [[nil 0 0 0] [nil 1 1 1] [nil 2 2 2]
+ [:missing 3 3 3]
+ [:missing :missing 4 4]
+ [:missing :missing :missing 5]]
+ (take 6 (m/sequence-padded (map vector) :missing [nil nil nil] (range 4) (range 5) (range 10))))))
+ (testing "slightly more complex xform"
+ (let [xf
+ (comp
+ (map vector)
+ (drop 5)
+ (filter (comp even? second))
+ (map #(mapv inc %))
+ (take 5))]
+ (is (= [[101 19 507] [101 25 509] [101 31 511] [101 37 513] [101 43 515]]
+ (m/sequence-padded xf
+ 100
+ (range 5)
+ (range 0 50 3)
+ (range 500 1000)))))))
View it on GitLab: https://salsa.debian.org/clojure-team/medley-clojure/-/commit/9f623c853d3e9dd5ab4e65210382ee241d9390c1
--
View it on GitLab: https://salsa.debian.org/clojure-team/medley-clojure/-/commit/9f623c853d3e9dd5ab4e65210382ee241d9390c1
You're receiving this email because of your account on salsa.debian.org. Manage all notifications: https://salsa.debian.org/-/profile/notifications | Help: https://salsa.debian.org/help
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20260730/101d4066/attachment.htm>
More information about the pkg-java-commits
mailing list