clojure.core
APIoverflow: hidden
text)
:id
:first-name
|
keywords evaluate to themselves.
I.e., they're just keyword literals. They're not pointers or references to something else.
To convert a string to a keyword: (keyword "name") .
|
:js/alert
::port
|
namespaced keywords are basically keywords, but it's more than just a convention faciliated by the double-colon syntax.
A :: prefix, as with ::port , produces a keyword with the current namespace prepended to the given name. So after calling (ns server.core) , ::port would evaluate to :server.core/port .
Keywords can only have one slash, but you can declare a keyword with a different namespace than the current one. There's also an interesting effect that arises in destructuring.
If you destructure a map with namespaced keys, the variable name you put into the scope is just the local (post- |
split
find-first
zero?
|
symbols represent other things, like functions or values. They can contain alphanumeric characters, *, +, !, -, _, ', and ?, but cannot start with a numeral. |
"The end"
"span {
}"
|
strings can contain literal newlines |
\K
\newline
|
characters are unquoted escape sequences. |
42
+42
-42
0.001
1e100
2e-16
|
numbers use decimal syntax. |
Commas are optional; they are treated as whitespace.
'(user :name "Lester")
'(1 8 19 47)
'(a, alt, end)
|
lists are usually immediately evaluated (i.e., (verb noun) would try to call verb with noun as the argument), but ' "quotes" the list, producing a list literal.
(list x y) does the same thing as '(x y) .
Lists have singly-linked performance characteristics.
|
[user1, user2, admin1]
[0 1 1 2 3 5]
|
vectors are like lists, but don't invoke function application.
Like list , you can use (vector 1 10) to create
a vector like [1 10] .
Vectors have random-access performance characteristics.
|
{:first "Chris" :last "Brown"}
{:k "abbr", :v "cb"}
|
maps are key-value pairs. |
{"Chris" "Brown" "H"}
#{101, 103, 107}
|
sets are unordered collections of unique elements.
Set literals cannot contain duplicates.
Use set to create a set from other collections (and handle duplicates): (set [101 103 107 101]) .
|
(cons x seq)
|
Returns a new seq that prepends x to seq .
|
(map f coll)
(map f & colls)
|
Call f for each item in coll , and returns a new (lazy) sequence with each return value of calling f .
To evaluate strictly (non-lazily), use The second form is rare, and is like zipping first and then running the resulting tuples through (map + [1 2 3] [10 20 30])
(11 22 33)
Using the multi-collection form, the zipper stops zipping at the end of the shortest Omitting the collection(s) returns a transducer. |
(conj coll x)
(conj coll x & xs)
|
Returns a new collection with the x (or x and xs ) added.
The specific operation — prepend vs. append (or perhaps even insertion) — and thus the resulting order — depends on the type of collection (the "concrete type"). (conj [1 2] 3)
[1 2 3]
(conj '(1 2) 3)
(3 1 2)
If |
(list* xs)
(list* x & xs)
|
Returns a new seq prepending all items but the last to the last item.
Basically it's the same as (list ...) , but the last argument gets splatted in at the end.
|
(into to from)
|
conj 's each of from onto to .
There is also a transducer form. |
(concat)
(concat x)
(concat x y)
(concat x y & zs)
|
Returns a lazy seq combining x , y , and each item in zs , in order from left to right.
x , y , and each item in zs must be "ISeqable".
|
(flatten xs)
|
Recursively flattens xs into a single sequence.
Returns the empty list if xs is not a sequence.
To non-recursively flatten a list: Or, without even using |
(mapcat f & xs)
|
Flat-maps f across xs .
f should take a single argument x (from xs ) and return a collection.
It's effectively the same (equivalent?) as |
(filter f xs)
|
Return a lazy sequence of each x from xs where (f x) returns true.
Omitting the collection ( |
(remove f xs)
|
remove is the opposite of filter .
(remove f xs) is equivalent to (filter (complement f) xs) .
|
(filterv f xs)
|
Strict version of filter ; returns a vector.
(Unlike (Also, there is no |
(keep f xs)
|
Returns a lazy sequence of the results of calling (f x) on each x in xs ,
dropping nil values.
(keep f xs) is equivalent to (remove nil? (map f xs)) .
|
(doall coll)
|
Forces a lazy sequence to evaluate, and returns the sequence. |
(dorun coll)
|
Forces a lazy sequence to evaluate, and returns nil. |
(doseq seq-bindings & body)
|
Like (for seq-bindings & body) but non-lazy and returns nil.
|
Clojure syntax is minimal. These are the basic categories:
Category | Examples |
---|---|
Comments | ; |
Numeric types | 1 , 2.3e4 , 5N , 6.7M , 8/9 |
Character types | "a" , \b , \space |
Other types | nil , true , #".+" , :a , :b/c , d , e/f |
Collection types | () , [] , #{} , {} |
Macros | ' , ` , ~ , ~@ |
Readers | #? , #?@ , etc. |
The characters allowed in symbols and keywords are pretty flexible, but have some limitations. From the Clojure reference on The Reader:
- Symbols begin with a non-numeric character and can contain alphanumeric characters and
*
,+
,!
,-
,_
,'
, and?
(other characters may be allowed eventually)./
has special meaning - it can be used once in the middle of a symbol to separate the namespace from the name..
has special meaning - it can be used one or more times in the middle of a symbol to designate a fully-qualified class name, e.g.java.util.BitSet
, or in namespace names. Symbols beginning or ending with.
are reserved by Clojure.- Symbols beginning or ending with
:
are reserved by Clojure. A symbol can contain one or more non-repeating:
s.
In practice, the set of characters used in symbols is even wider:
>
is found in many clojure.core
macros, the “threading” macros, e.g., ->
, ->>
, cond->
, some->>
, a.o.
And (defrecord X ...)
defines the vars, ->X
and map->X
.
<
is less pervasive, but appears in several core.async
functions.=
is another that’s not terribly common outside comparison operators, but there is clojure.core/not=
.
(def name "Chris")
|
variables are set at the top level in the current namespace. |
(def ^{:private true} conn-string "pg:db:root:5ecr3t")
(def ^:private conn-string "pg:db:root:5ecr3t")
|
Variables can be made private to the module in which they are defined.
The alternative syntax with lone keyword is sugar for the first.
For ^:private specifically, this usage is idiomatic,
but if there is more metadata than just :private , the full map syntax is generally used.
|
(def ^{:constant true} rand-seed 2016)
|
Variables can be declared constant, though I'm not sure what the point is, what with Clojure being immutable and all. Maybe for JVM-compliance? |
(fn [x] (* x x))
|
functions are anonymous, but can be set to a variable with def .
|
(defn square
"Multiply the argument by itself"
[x]
(* x x))
|
(defn name args body) is sugar for
(def name (fn name args body)) , but also
facilitates supplying a string describing the function's purpose.
|
(defn name doc-string? [params*] prepost-map? body*)
(defn name doc-string? ([params*] prepost-map? body*)+)
(fn name? [params*] prepost-map? body*)
(fn name? ([params*] prepost-map? body*)+)
where
prepost-map looks like:{:pre [pre-expr*]
:post [post-expr*]}
|
The prepost-map? part of the defn /fn syntax is an optional map that immediately follows the argument vector (aka. the "arglist", aka. [params*] ).
Contrived example: (defn delete-customer!
"Find the customer identified by `id` and delete that row from the database,
but then also return the deleted row.
Using pre/post conditions, this function checks that `id` is a positive integer,
and that the returned row is a map with an `:id` value identical to the given `id`.
The function may very well throw other exceptions.
E.g., if the database indicated by `db` doesn't exist."
[db id]
{:pre [(integer? id) (pos? id)]
:post [(map? %) (= (get % :id) id)]}
(let [rows (jdbc/query db ["SELECT * FROM customer WHERE id = ?" id])]
(jdbc/delete! db :customer ["id = ?" id])
(first rows)))
|
(defn- connect [conf] (...))
|
Functions declared with defn- are private to the module in which they are defined.
|
(defn range-size
([from to] (- to from))
([to] (range-size 0 from)))
|
function overloading (a.k.a., "multiple arities") allows differentiating function behavior based on the number of arguments it's called with. |
(defn join-name [first last & middles]
(str first " " (join " " middles) " " last))
|
A function's variadic argument is signaled by & and comes last in the argument vector.
The syntax (and implementation) is identical to destructuring a sequence.
|
(map #(str "(" % ")") ["c" "h" "b"])
("(c)" "(h)" "(b)")
|
The reader macro makes anonymous function syntax more concise.
#(body) is kind of like shorthand for (fn [%1] (body)) ,
but adapts the argument vector based on the number of % -prefixed names supplied in
body.% is short for %1 , but the latter is preferred if your #(...) function takes more than one argument.%& captures the variadic remainder, i.e., all the arguments beyond the highest %N present.
|
(let [x 100
y (* x 2)]
(println (str "Starting at (" x ", " y ")"))
[x, y])
Starting at (100, 200)
[100 200]
|
let takes a vector of symbol-expression pairs to declare immutable variables in local scope,
then evaluates all the following expressions, returning the final expression.
|
(do
(println (str "Starting at (0, 0)"))
[0, 0])
Starting at (0, 0)
[0 0]
|
do is like let , but without the local variables.
|
(loop [x 0] ... (recur (inc x)))
|
loop is a lot like let , but allows jumping back to just after binding the variables with recur .
Otherwise, it will return the value of the last expression of whatever dead end it hits.
|
(for [x (range 4)
y (range 2)]
[x y])
([0 0] [0 1] [1 0] [1 1] [2 0] [2 1] [3 0] [3 1])
|
for is like let , but each variable's expression must be a sequence, and it iterates through the cross product of all sequences.The bindings vector supports three modifiers:
|
As far as I can tell, dots in a namespace/package/module name are equivalent to alphanumerics except that they specify directory descent rather than file name, when mapping module names to filesystem location.
(ns io.github.chbrown.docs)
|
Sets the current namespace to io.github.chbrown.docs , which should usually be in the file PROJECT_ROOT/src/io/github/chbrown/docs.clj .
|
(ns ... (:require org.anist.render))
|
Can now call (org.anist.render/text "Hello") |
(ns ... (:require [org.anist.render :as render]))
|
Can now call (render/text "Hello") |
(ns ... (:require [org.anist.render :refer [html pdf text]]))
|
Can now call (text "Hello") |
(ns ... (:require [org.anist.render :refer :all]))
|
Wildcard import. |
(ns ... (:import java.time.ZonedDateTime))
|
Import class from the new Java 8 time implementation.
Can call it like (ZonedDateTime/now) . |
(ns ... (:import [java.time Duration Instant ZonedDateTime]))
|
Import multiple classes from the java.time namespace. |
(ns ... (:use ...))
|
:use is deprecated as of Clojure 1.4; there's nothing :use can do that :require + :refer (:refer was added in Clojure 1.4) can't do better. |
(require 'org.anist.render)
(import 'java.time.ZonedDateTime)
|
In the REPL, you may want to require or import another module on the fly; this form takes a quoted "libspec" (see the full (ns ...) examples above). |
java.text.Normalizer$Form/NFKC
|
Java allows declaring classes inside other classes, which are accessed statically, and slightly differently than classes nested in another layer of namespace.
Instead of using . to access the inner class, use $ .
|
(apply f xs)
(apply f x & xs)
(apply f x y & xs)
|
Call f with each x in xs as its arguments.
If multiple arguments are given, everything after f are combined as if with Useful for splatting a list into multiple function arguments, since there is no function invokation syntax sugar like Python's Suppose you have a list of strings,
Instead, you'd want to use
|
(partial f & args)
|
Curries f and returns a new function that will use each arg in args as an argument when eventually calling that new function.
Clojure is not generally lazy, so otherwise calling a function with an insufficient number of arguments will raise an arity error. |
(comp)
(comp f)
(comp f g)
(comp f g & fs)
|
Compose multiple functions into a single function; the right-most function will be called first, then the next right-most, etc.
(reduce (fn [args f] (apply f args)) init (reverse [f g h]))
|
(complement f)
|
Return a new function that takes the same arguments as f , does the same thing as f ,
but returns the boolean opposite of what f returns.
I.e., true if f returns false or nil , false otherwise.
|
(cond & clauses)
|
Each clause is a pair of two functions; if the first returns true, the second gets called.
The clauses are tested in order, and the (cond ...) itself returns the result of the second function in the first clause whose first function returns true.
The last clause 's test function can be :else , which always evaluates as if true (so it operates as an "otherwise" or default: in a switch construct.)
|
(-> x & forms)
|
The thread-first operator.
Call each If a form takes only one argument, you may omit the surrounding parentheses. |
(doto x & forms)
|
Just like -> , except that all of the forms are just side-effects; the flow behaves as if each form in forms returned x .
|
(->> x & forms)
|
The thread-last operator. (Also called the "thrush" operator?)
Call each If a form takes only one argument, you may omit the surrounding parentheses. |
References:
Quoting:
'x
(quote x)
Syntax quoting:
`x
`(+ x y)
Unquoting:
~x
Unquote splicing:
~@xs
Helpers:
macroexpand
(atom value)
|
Creates a new Atom with the value value , and returns the atom.
|
(deref atom)
@atom
|
Returns the value of atom (as opposed to the Atom itself).
Also known as "dereferencing", I'm guessing because (deref ...) was the original syntax,
and the @ sugar came later.
|
(reset! atom value)
|
Sets atom to value (discarding atom 's current value), and returns value .
|
(swap! atom fn & args)
|
Calls fn with the current value of atom (and args , if any) and sets the atom 's value to the result (also, returns the new value of atom ).
|
(compare-and-set! atom oldval newval)
|
Calls (reset! atom newval) iff (= @atom oldval) .
Returns the boolean value of (= @atom oldval) .
|
Work in progress
References:
pr
and prn
are for machinesprint
and println
are for humansTranslations
Exception in thread "main" java.lang.IllegalArgumentException:
No single method: xyz of interface: my/mod found for function: xyz of protocol: Xyz,
compiling:(my/mod.clj:10:7)
This often means you’re calling a protocol’s method with the wrong arity.
See https://dev.clojure.org/jira/browse/CLJ-735(defrecord Template [id name html])
(def new-template (Template. "new" "hello-world" "Hello world!"))
(class new-template)
user.Template
(ancestors new-template)
nil
(ancestors (class new-template))
#{clojure.lang.IHashEq
clojure.lang.Seqable
java.io.Serializable
clojure.lang.IKeywordLookup
clojure.lang.ILookup
clojure.lang.IObj
clojure.lang.IMeta
clojure.lang.IPersistentCollection
java.lang.Object
java.lang.Iterable
java.util.Map
clojure.lang.IRecord
clojure.lang.Counted
clojure.lang.Associative
clojure.lang.IPersistentMap}
Ring is the de facto protocol for web requests and responses in Clojure.
Request maps have a standard set of fields:
Key | Description |
---|---|
:server-port |
The port on which the request is being handled. |
:server-name |
The resolved server name, or the server IP address. |
:remote-addr |
The IP address of the client or the last proxy that sent the request. |
:uri |
The request URI (the full path after the domain name). |
:query-string |
The query string, if present. |
:scheme |
The transport protocol, either :http or :https . |
:request-method |
The HTTP request method, which is one of :get , :head , :options , :put , :post , or :delete . |
:headers |
A Clojure map of lowercase header name strings to corresponding header value strings. |
:body |
An InputStream for the request body, if present. |
The clj-http library adds a few extensions:
Key | Description |
---|---|
:body |
java.io.InputStream or String / java.io.File / byte-array |
:body-encoding |
character encoding to use when serializing :body (defaults to “UTF-8”) |
:length |
Value to use (number of bytes) for the Content-Length: header if :body is an InputStream and thus has no predetermined length (defaults to -1, which implies Transfer-Encoding: chunked ) |
:accept |
What to use for the Accept: header |
:headers |
A Clojure map of lowercase header name strings or keywords to corresponding header value strings or lists of strings. |
:client-params |
Map of specific client parameters to be sent directly to the underlying Apache HttpComponents client |
:insecure? |
True to accept untrusted SSL certificates (default is false) |
:follow-redirects? |
False not to follow redirects automatically (default is true) |
:max-redirects |
Maximum number of redirects to follow (set :throw-exceptions to true to throw an exception if this limit is exceeded) |
:socket-timeout |
in milliseconds |
:conn-timeout |
in milliseconds |
:query-params |
query parameters as a map from strings / keywords to strings / keywords / numbers / nested maps (of the same structure) |
:cookies |
cookies as map from strings to maps of cookie parameters, with keys like :discard , :path , :value , and :version |
:form-params |
a map from strings / keywords (when :request-method is :put or :post ) |
:form-param-encoding |
character encoding to use when serializing form-params |
:content-type |
what content type / serialization to perform on the :form-params value, e.g., :json (default is urlencoded); also supports :transit+json and :transit+msgpack with accompanying :transit-opts {:encode {:handlers ...} :decode {:handlers ...}} config |
:multipart |
a vector of maps with keys like :name , :content , :part-name , :encoding , and :mime-type , where :content can be a String / java.io.InputStream / java.io.File / byte-array |
Response maps are simpler; they have three fields:
:status
The HTTP status code, such as 200, 302, 404 etc.:headers
A Clojure map of HTTP header names to header values. These values may either be strings, in which case one name/value header will be sent in the HTTP response, or a collection of strings, in which case a name/value header will be sent for each value.:body
A representation of the response body, if a response body is appropriate for the response’s status code. The body can be one of four types:
String
The body is sent directly to the client.ISeq
Each element of the seq is sent to the client as a string.File
The contents of the referenced file is sent to the client.InputStream
The contents of the stream is sent to the client. When the stream is exhausted, the stream is closed.References:
Utility functions that seem like they ought to be in the standard library.
map-values
(defn map-values
"construct a new map with all the values of the given map passed through f"
[f kvs]
(into {} (for [[k v] kvs] [k (f v)])))
Alternatively, this can be written a bit more verbosely but perhaps more properly/Clojurely with reduce
like so:
(defn map-values
"construct a new map with all the values of the given map passed through f"
[f kvs]
(reduce (fn [out [k v]] (assoc out k (f v))) {} kvs))
update-when
The standard (update m k f)
adds an entry to m
with the key k
and the value (f (get m k))
, even if (get m k)
is nil
and even if f
returns nil
.
It’s perhaps more Clojureish to imagine that all maps contain every key with the value of nil
, but sometimes it’s more practically useful to distinguish between key-value pairs with nil
values and keys that are not contains?
-ed by the map m
.
(defn update-when
"Like update but only calls f if m contains k"
[m k f & args]
(if (contains? m k) (apply update m k f args) m))
interleave-all
(defn
^{:doc "A lazier version of interleave that also does not truncate incomplete rows"
:test (fn []
(assert (= (interleave-all [1 3] [2]) [1 2 3]))
(assert (= (interleave-all [1] [] [2 3]) [1 2 3])))}
interleave-all
[& colls]
; filter is already lazy so no need to wrap the whole thing in lazy-seq
(when-let [ss (seq (filter seq colls))]
; map is also lazy
(concat (map first ss)
; but apply will cause (map rest ss) to be evaluated,
; so this lazy-seq is required
(lazy-seq
(apply interleave-all (map rest ss))))))
export
From clojure/core.clj (commented-out, but not private)
(defn export [syms]
(doseq [sym syms]
(.. *ns* (intern sym) (setExported true))))
dedupe-by
Based on clojure.core/dedupe (also in the transducers reference)
(defn dedupe-by
"Like clojure.core/dedupe, removes consecutive duplicates in coll,
but compares values after passing elements from coll through f.
Returns a transducer when no collection is provided."
([f]
(fn [xf]
(let [previous (volatile! ::none)]
(fn
([] (xf))
([result] (xf result))
([result input]
(let [current (f input)
dupe? (= @previous current)]
(vreset! previous current)
(if dupe?
result
(xf result input))))))))
([f coll] (sequence (dedupe-by f) coll)))
count-realized
From https://stackoverflow.com/a/21950175; lazily counts the realized elements of a lazy sequence.
(defn count-realized
"Count the realized portion of a sequence"
[s]
(loop [s s
n 0]
(if (instance? clojure.lang.IPending s)
(if (and (realized? s) (seq s))
(recur (rest s) (inc n))
n)
(if (seq s)
(recur (rest s) (inc n))
n))))
read-resource-version
Uses clojure.java.io/resource
to look up Maven metadata in the JVM classpath for the given package
,
which should be a symbol like 'aleph
or 'org.clojure/clojure
.
(defn- ^java.util.Properties ->Properties
[^java.io.Reader reader]
(doto (java.util.Properties.) (.load reader)))
(defn read-resource-version
[package]
(let [package-name (name package)
package-namespace (or (namespace package) (name package))]
(some-> (str "META-INF/maven/" package-namespace "/" package-name "/pom.properties")
; io/resource will return nil if given a path that doesn't exist
io/resource
io/reader
; Load the given reader as a Properties instance
->Properties
(.getProperty "version"))))
JavaScript | ClojureScript |
---|---|
let objContainsKey = key in obj
|
(def objContainsKey (js-in key obj))
|
These packages are very useful development / testing helpers:
Their use is described below.
Test your entire project:
lein test
For nicer test failure explanations, add the dependency [pjstadig/humane-test-output "0.8.1"]
and the following :injections
to your ~/.lein/profiles.clj
:
(require 'pjstadig.humane-test-output)
(pjstadig.humane-test-output/activate!)
Then use the ordering (clojure.test/is (= expected actual))
for proper error messages.
To calculate test coverage; add [lein-cloverage "1.0.9"]
to your list of plugins, then run:
lein cloverage
To list suggested changes in git diff
format, add [lein-cljfmt "0.5.6"]
to your plugins, and run:
lein cljfmt check
To make the changes in place, run:
lein cljfmt fix
To ‘lint’ your Clojure code, add the plugin, [jonase/eastwood "0.2.3"]
, and run:
lein eastwood
To enable more linters, specify them in an EDN string argument:
lein eastwood "{:add-linters [:keyword-typos :unused-namespaces :unused-private-vars :unused-fn-args :unused-locals]}"
To optimize/modernize your imports, add the dependency [slamhound "1.5.5"]
and the alias {"slamhound" ["run" "-m" "slam.hound"]}
, and then run:
lein slamhound src/
To list your project’s dependencies that are not the latest available version, add [lein-ancient "0.6.10"]
to your plugins, and run:
lein ancient [upgrade [:interactive|:no-tests]] [:all] [:allow-qualified] [:check-clojure]
option | effect |
---|---|
check |
List outdated artifacts (default if no sub-command is used) |
upgrade |
Update project.clj in-place with current versions if lein test runs successfully |
upgrade :no-tests |
Update with current versions without checking the tests |
upgrade :interactive |
Confirm each change to project.clj at CLI before writing |
:all |
Include both your (defproject ...) ’s :dependencies and :plugins (default is to look only at :dependencies ) |
:plugins |
Check only your project’s :plugins |
:allow-qualified |
Also consider qualified versions of artifacts as candidates for latest version |
Examples of commonly used combinations:
lein ancient :all
List all out-of-date dependencies.
lein ancient upgrade :all :no-tests
Upgrade all out-of-date dependencies in-place, without testing.
lein ancient check-profiles
Check the dependencies in ~/.lein/profiles.clj
(run this command from any path)
Source: Leiningen DEPLOY doc.
Generally, Clojure packages for general consumption are published to Clojars.
Some of the release process requires signing some amount of code/commits/tags, and is generally much easier when gpg-agent is running and loaded with a default key.
I use my Keybase key, which is handy:
# cd /into/my/secret/keybase/dir
gpg --allow-secret-key-import --import keybase.private.key
gpg --import keybase.public.key
The current keys available to the gpg agent are listable:
gpg --list-secret-keys
gpg --list-keys # might also be handy
Your project.clj
should contain the key-value :deploy-repositories [["releases" :clojars]]
.
The easiest all-in-one publishing solution is simply lein release
,
which handles tagging, signing, and uploading to Clojars.
If you have the lein-pprint
plugin installed, you can show what happens in a release by calling:
lein pprint :release-tasks
It will output a list of the lein commands it will run when you call lein release
:
# 1. stop immediately if there are any uncommitted outstanding changes
["vcs" "assert-committed"]
# 2. remove the snapshot or other suffix from the version
["change" "version" "leiningen.release/bump-version" "release"]
# 3. commit the change from #2
["vcs" "commit"]
# 4. create a signed git tag for the current version
["vcs" "tag"]
# 5. push to Clojars, pulling credentials from ~/.lein/profiles.clj or ~/.lein/credentials.clj.gpg
["deploy"]
# 6. bump the version number, and add a -SNAPSHOT suffix
["change" "version" "leiningen.release/bump-version"]
# 7. commit
["vcs" "commit"]
# 8. push to remote
["vcs" "push"]
Unfortunately, there is no rollback mechanism if one of these fail, so you might need to do your own cleanup if it breaks halfway through.
If it only got up to the commit part, might need to rollback the version change:
git reset --hard HEAD^
If it created the tag, but failed to upload to Clojars (e.g., incorrect credentials), you might also need to delete the tag:
git tag -d 1.2.3 # or whatever tag it just added
By default, the post-release version bump just increments the patch level.
You can alternatively run lein release :minor
or lein release :major
to increment the minor/major level.
I’m not sure exactly how the :minor
(or whichever) argument gets directed to the right command – the change version ...
one – but that’s where it gets picked up.
I’m not sure why you’re supposed to decide what the next patch level is going to be while cutting the current release, so it’s often handy to run some subset of the release sequence.
For example, to bump the minor version without publishing:
lein do vcs assert-committed, \
change version leiningen.release/bump-version :minor, \
vcs commit, vcs tag
Or to set a specific version, check, and deploy:
lein do vcs assert-committed, change version set '"1.0.0-rc1"'
# leiningen doesn't re-read / internally update the version; easier to just restart:
lein do vcs commit, vcs tag
# take this opportunity to double-check: git show; git tag
lein do vcs push, deploy