
Joker is a small Clojure interpreter, linter and formatter written in Go.
On macOS, the easiest way to install Joker is via Homebrew:
brew install candid82/brew/joker
The same command can be used on Linux if you use Homebrew on Linux.
If you use Arch Linux, there is AUR package.
If you use Nix, then you can install Joker with
nix-env -i joker
On other platforms (or if you prefer manual installation), download a precompiled binary for your platform and put it on your PATH.
You can also build Joker from the source code.
joker - launch REPL. Exit via (exit), EOF (such as Ctrl-D), or SIGINT (such as Ctrl-C).
Hint: In the REPL typing ( adds a pair of matched parentheses. Use the delete key to remove individual parenthesis ignoring parenthesis matching. Ctrl-D works as a delete key on some systems. If you find the default REPL editing behavior annoying (e.g., automatic parenthesis matching, backspace doesn't delete individual parenthesis), try joker --no-readline or rlwrap joker --no-readline if you have rlwrap installed.
joker <filename> - execute a script. Joker uses .joke filename extension. For example: joker foo.joke. Normally exits after executing the script, unless --exit-to-repl is specified before --file <filename>
in which case drops into the REPL after the script is (successfully) executed. (Note use of --file in this case, to ensure <filename> is not treated as a <socket> specification for the repl.)
joker --eval <expression> - execute an expression. For example: joker -e '(println "Hello, world!")'. Normally exits after executing the script, unless --exit-to-repl is specified before --eval,
in which case drops into the REPL after the expression is (successfully) executed.
joker - - execute a script on standard input (os.Stdin).
joker --lint <filename> - lint a source file. See Linter mode for more details.
joker --lint --working-dir <dirname> - recursively lint all Clojure files in a directory.
joker --format <filename> - format a source file and write the result to standard output. See Format mode for more details.
joker --format - - read Clojure source code from standard input, format it and print the result to standard output.
Dash docset: dash-feed://https%3A%2F%2Fraw.githubusercontent.com%2Fcandid82%2Fjoker%2Fmaster%2Fdocs%2Fjoker.xml
(either copy and paste this link to your browser's url bar or open it in a terminal with open command)
Organizing libraries (namespaces)
These are high level goals of the project that guide design and implementation decisions.
joker --hashmap-threshold -1 -e "(pprint (read))"
There is Sublime Text plugin that uses Joker for pretty printing EDN files. Here you can find the description of --hashmap-threshold parameter, if curious.
| Joker type | Corresponding Go type |
|---|---|
| BigFloat | big.Float (see below) |
| BigInt | big.Int |
| Boolean | bool |
| Char | rune |
| Double | float64 |
| Int | int |
| Keyword | n/a |
| Nil | n/a |
| Ratio | big.Rat |
| Regex | regexp.Regexp |
| String | string |
| Symbol | n/a |
| Time | time.Time |
See Floating-point Constants and the BigFloat Type for more on BigFloat (M-suffixed) constants.
Note that Nil is a type that has one value: nil.
| Joker type | Corresponding Clojure type |
|---|---|
| ArrayMap | PersistentArrayMap |
| MapSet | PersistentHashSet (or hypothetical PersistentArraySet, depending on which kind of underlying map is used) |
| HashMap | PersistentHashMap |
| List | PersistentList |
| Vector | PersistentVector |
p* functions that use multiple threads. Vars always have just one "root" binding. Joker does have core.async style support for concurrency. See go macro documentation for details.subseq, iterator-seq, reduced?, reduced, mix-collection-hash, definline, re-groups, hash-ordered-coll, enumeration-seq, compare-and-set!, rationalize, load-reader, find-keyword, comparator, resultset-seq, file-seq, sorted?, ensure-reduced, rsubseq, pr-on, seque, alter-var-root, hash-unordered-coll, re-matcher, unreduced.joker prefix. The core namespace is called joker.core. Other built-in namespaces include joker.string, joker.json, joker.os, joker.base64 etc. See standard library reference for details.(-main) entry point as Clojure does. It simply reads s-expressions from the file and executes them sequentially. If you want some code to be executed only if the file it's in is passed as joker argument but not if it's loaded from other files, use (when (= *main-file* *file*) ...) idiom. See https://github.com/candid82/joker/issues/277 for details.Miscellaneous:
case is just a syntactic sugar on top of condp and doesn't require options to be constants. It scans all the options sequentially.
slurp only takes one argument - a filename (string) or a reader object (e.g. *in*). No options are supported.ifn? is called callable?nil, not the value Unbound. You can still check if the var is bound with bound? function.To run Joker in linter mode pass --lint --dialect <dialect> flag, where <dialect> can be clj, cljs, joker or edn. If --dialect <dialect> is omitted, it will be set based on file extension. For example, joker --lint foo.clj will run linter for the file foo.clj using Clojure (as opposed to ClojureScript or Joker) dialect. joker --lint --dialect cljs - will run linter for standard input using ClojureScript dialect. Linter will read and parse all forms in the provided file (or read them from standard input) and output errors and warnings (if any) to standard output (for edn dialect it will only run read phase and won't parse anything). Let's say you have file test.clj with the following content:
(let [a 1])
Executing the following command joker --lint test.clj will produce the following output:
test.clj:1:1: Parse warning: let form with empty body
The output format is as follows: <filename>:<line>:<column>: <issue type>: <message>, where <issue type> can be Read error, Read warning, Parse error, Parse warning or Exception.
Here are some examples of errors and warnings that the linter can output.
Joker lints the code in one file at a time and doesn't try to resolve symbols from external namespaces. Because of that and since it's missing some Clojure(Script) features it doesn't always provide accurate linting. In general it tries to be unobtrusive and error on the side of false negatives rather than false positives. One common scenario that can lead to false positives is resolving symbols inside a macro. Consider the example below:
(ns foo (:require [bar :refer [def-something]]))
(def-something baz ...)
Symbol baz is introduced inside def-something macro. The code is totally valid. However, the linter will output the following error: Parse error: Unable to resolve symbol: baz. This is because by default the linter assumes external vars (bar/def-something in this case) to hold functions, not macros. The good news is that you can tell Joker that bar/def-something is a macro and thus suppress the error message. To do that you need to add bar/def-something to the list of known macros in Joker configuration file. The configuration file is called .joker and should be in the same directory as the target file, or in its parent directory, or in its parent's parent directory etc up to the root directory. When reading from stdin Joker will look for a .joker file in the current working directory. The --working-dir <path/to/file> flag can be used to override the working directory that Joker starts looking in. Joker will also look for a .joker file in your home directory if it cannot find it in the above directories. The file should contain a single map with :known-macros key:
{:known-macros [bar/def-something foo/another-macro ...]}
Please note that the symbols are namespace qualified and unquoted. Also, Joker knows about some commonly used macros (outside of clojure.core namespace) like clojure.test/deftest or clojure.core.async/go-loop, so you won't have to add those to your config file.
Joker also allows you to specify symbols that are introduced by a macro:
{:known-macros [[riemann.streams/where [service event]]]}
So each element in :known-macros vector can be either a symbol (as in the previous exampl
$ claude mcp add joker \
-- python -m otcore.mcp_server <graph>