Clean up docs [skip ci]

This commit is contained in:
Michiel Borkent 2020-11-28 21:20:39 +01:00
parent 1e0887673a
commit f23723abeb
4 changed files with 5 additions and 1086 deletions

799
README.md
View file

@ -194,796 +194,13 @@ Check out the image on [Docker hub](https://hub.docker.com/r/borkdude/babashka/)
Check out the [news](doc/news.md) page to keep track of babashka-related news items.
## Babashka book
See the [babashka book](https://book.babashka.org) for more details on how to
use babashka.
## Usage
``` shellsession
Babashka v0.2.3
Options must appear in the order of groups mentioned below.
Help:
--help, -h or -? Print this help text.
--version Print the current version of babashka.
--describe Print an EDN map with information about this version of babashka.
In- and output flags:
-i Bind *input* to a lazy seq of lines from stdin.
-I Bind *input* to a lazy seq of EDN values from stdin.
-o Write lines to stdout.
-O Write EDN values to stdout.
--stream Stream over lines or EDN values from stdin. Combined with -i or -I *input* becomes a single value per iteration.
Uberscript:
--uberscript <file> Collect preloads, -e, -f and -m and all required namespaces from the classpath into a single executable file.
Evaluation:
-e, --eval <expr> Evaluate an expression.
-f, --file <path> Evaluate a file.
-cp, --classpath Classpath to use.
-m, --main <ns> Call the -main function from namespace with args.
--verbose Print debug information and entire stacktrace in case of exception.
REPL:
--repl Start REPL. Use rlwrap for history.
--socket-repl Start socket REPL. Specify port (e.g. 1666) or host and port separated by colon (e.g. 127.0.0.1:1666).
--nrepl-server Start nREPL server. Specify port (e.g. 1667) or host and port separated by colon (e.g. 127.0.0.1:1667).
If neither -e, -f, or --socket-repl are specified, then the first argument that is not parsed as a option is treated as a file if it exists, or as an expression otherwise. Everything after that is bound to *command-line-args*. Use -- to separate script command line args from bb command line args.
```
### Built-in namespaces
In addition to `clojure.core`, the following namespaces are available. Some are
available through pre-defined aliases in the `user` namespace, which can be
handy for one-liners. If not all vars are available, they are enumerated
explicitly. If some important var is missing, an issue or PR is welcome.
From Clojure:
- `clojure.core`
- `clojure.core.protocols`: `Datafiable`, `Navigable`
- `clojure.data`
- `clojure.datafy`
- `clojure.edn` aliased as `edn`
- `clojure.java.browse`
- `clojure.java.io` aliased as `io`:
- `as-relative-path`, `as-url`, `copy`, `delete-file`, `file`, `input-stream`,
`make-parents`, `output-stream`, `reader`, `resource`, `writer`
- `clojure.java.shell` aliased as `shell`
- `clojure.main`: `demunge`, `repl`, `repl-requires`
- `clojure.pprint`: `pprint`, `cl-format`
- `clojure.set` aliased as `set`
- `clojure.string` aliased as `str`
- `clojure.stacktrace`
- `clojure.test`
- `clojure.zip`
Additional libraries:
- [`babashka.curl`](https://github.com/borkdude/babashka.curl)
- [`babashka/process`](https://github.com/babashka/process)
- [`bencode.core`](https://github.com/nrepl/bencode) aliased as `bencode`: `read-bencode`, `write-bencode`
- [`cheshire.core`](https://github.com/dakrone/cheshire) aliased as `json`
- [`clojure.core.async`](https://clojure.github.io/core.async/) aliased as
`async`. Also see [docs](https://github.com/borkdude/babashka#coreasync).
- [`clojure.data.csv`](https://github.com/clojure/data.csv) aliased as `csv`
- [`clojure.data.xml`](https://github.com/clojure/data.xml) aliased as `xml`
- [`clojure.tools.cli`](https://github.com/clojure/tools.cli) aliased as `tools.cli`
- [`clj-yaml.core`](https://github.com/clj-commons/clj-yaml) alias as `yaml`
- [`cognitect.transit`](https://github.com/cognitect/transit-clj) aliased as `transit`
- [`org.httpkit.client`](https://github.com/http-kit/http-kit)
- [`org.httpkit.server`](https://github.com/http-kit/http-kit) (experimental)
See the
[projects](https://github.com/borkdude/babashka/blob/master/doc/projects.md)
page for libraries that are not built-in, but which you can load from source via
the `--classpath` option.
See the [build](https://github.com/borkdude/babashka/blob/master/doc/build.md)
page for built-in libraries that can be enabled via feature flags, if you want
to compile babashka yourself.
A selection of Java classes are available, see `babashka/impl/classes.clj`.
### Running a script
Scripts may be executed from a file using `-f` or `--file`:
``` shellsession
bb -f download_html.clj
```
Using `bb` with a shebang also works:
``` clojure
#!/usr/bin/env bb
(require '[babashka.curl :as curl])
(defn get-url [url]
(println "Downloading url:" url)
(curl/get url))
(defn write-html [file html]
(println "Writing file:" file)
(spit file html))
(let [[url file] *command-line-args*]
(when (or (empty? url) (empty? file))
(println "Usage: <url> <file>")
(System/exit 1))
(write-html file (get-url url)))
```
``` shellsession
$ ./download_html.clj
Usage: <url> <file>
$ ./download_html.clj https://www.clojure.org /tmp/clojure.org.html
Fetching url: https://www.clojure.org
Writing file: /tmp/clojure.org.html
```
If `/usr/bin/env` doesn't work for you, you can use the following workaround:
``` shellsession
$ cat script.clj
#!/bin/sh
#_(
"exec" "bb" "$0" hello "$@"
)
(prn *command-line-args*)
./script.clj 1 2 3
("hello" "1" "2" "3")
```
### Input and output flags
See [io-flags.md](doc/io-flags.md) for detailed documentation about `*input*`
and the associated flags.
### Current file path
The var `*file*` contains the full path of the file that is currently being
executed:
``` shellsession
$ cat example.clj
(prn *file*)
$ bb example.clj
"/Users/borkdude/example.clj"
```
### Command-line arguments
Command-line arguments can be retrieved using `*command-line-args*`. If you want
to parse command line arguments, you may use the built-in `clojure.tools.cli`
namespace (see
[docs](https://github.com/borkdude/babashka#parsing-command-line-arguments)) or
use the
[nubank/docopt](https://github.com/borkdude/babashka/blob/master/doc/projects.md#nubankdocopt)
library.
### Babashka namespaces
#### babashka.classpath
Contains the function `add-classpath` which can be used to add to the classpath
dynamically:
``` clojure
(require '[babashka.classpath :refer [add-classpath]]
'[clojure.java.shell :refer [sh]]
'[clojure.string :as str])
(def medley-dep '{:deps {medley {:git/url "https://github.com/borkdude/medley"
:sha "91adfb5da33f8d23f75f0894da1defe567a625c0"}}})
(def cp (-> (sh "clojure" "-Spath" "-Sdeps" (str medley-dep)) :out str/trim))
(add-classpath cp)
(require '[medley.core :as m])
(m/index-by :id [{:id 1} {:id 2}]) ;;=> {1 {:id 1}, 2 {:id 2}}
```
#### babashka.wait
Contains the functions: `wait-for-port` and `wait-for-path`.
Usage of `wait-for-port`:
``` clojure
(wait/wait-for-port "localhost" 8080)
(wait/wait-for-port "localhost" 8080 {:timeout 1000 :pause 1000})
```
Waits for TCP connection to be available on host and port. Options map supports `:timeout` and `:pause`. If `:timeout` is provided and reached, `:default`'s value (if any) is returned. The `:pause` option determines the time waited between retries.
Usage of `wait-for-path`:
``` clojure
(wait/wait-for-path "/tmp/wait-path-test")
(wait/wait-for-path "/tmp/wait-path-test" {:timeout 1000 :pause 1000})
```
Waits for file path to be available. Options map supports `:default`, `:timeout`
and `:pause`. If `:timeout` is provided and reached, `:default`'s value (if any)
is returned. The `:pause` option determines the time waited between retries.
The namespace `babashka.wait` is aliased as `wait` in the `user` namespace.
#### babashka.signal
Contains the function `signal/pipe-signal-received?`. Usage:
``` clojure
(signal/pipe-signal-received?)
```
Returns true if `PIPE` signal was received. Example:
``` shellsession
$ bb '((fn [x] (println x) (when (not (signal/pipe-signal-received?)) (recur (inc x)))) 0)' | head -n2
1
2
```
The namespace `babashka.signal` is aliased as `signal` in the `user` namespace.
#### babashka.curl
The namespace `babashka.curl` is a tiny wrapper around curl. It's aliased as
`curl` in the user namespace. See
[babashka.curl](https://github.com/borkdude/babashka.curl).
## Style
A note on style. Babashka recommends the following:
### Explicit requires
Use explicit requires with namespace aliases in scripts, unless you're writing
one-liners.
Do this:
``` shell
$ ls | bb -i '(-> *input* first (str/includes? "m"))'
true
```
But not this:
script.clj:
``` clojure
(-> *input* first (str/includes? "m"))
```
Rather do this:
script.clj:
``` clojure
(ns script
(:require [clojure.java.io :as io]
[clojure.string :as str]))
(-> (io/reader *in*) line-seq first (str/includes? "m"))
```
Some reasons for this:
- Linters like clj-kondo work better with code that uses namespace forms, explicit requires, and known Clojure constructs
- Editor tooling works better with namespace forms (sorting requires, etc).
- Writing compatible code gives you the option to run the same script with `clojure`
## [Running a REPL](doc/repl.md)
Babashka offers a REPL, a socket REPL and an nREPL server. Look
[here](doc/repl.md) for more information on how to use and integrate them with
your editor.
## Preloads
The environment variable `BABASHKA_PRELOADS` allows to define code that will be
available in all subsequent usages of babashka.
``` shellsession
BABASHKA_PRELOADS='(defn foo [x] (+ x 2))'
BABASHKA_PRELOADS=$BABASHKA_PRELOADS' (defn bar [x] (* x 2))'
export BABASHKA_PRELOADS
```
Note that you can concatenate multiple expressions. Now you can use these functions in babashka:
``` shellsession
$ bb '(-> (foo *input*) bar)' <<< 1
6
```
You can also preload an entire file using `load-file`:
``` shellsession
export BABASHKA_PRELOADS='(load-file "my_awesome_prelude.clj")'
```
Note that `*input*` is not available in preloads.
## Classpath
Babashka accepts a `--classpath` option that will be used to search for
namespaces when requiring them:
``` clojure
$ cat src/my/namespace.clj
(ns my.namespace)
(defn -main [& _args]
(println "Hello from my namespace!"))
$ bb --classpath src --main my.namespace
Hello from my namespace!
```
If you have a larger script with a classic Clojure project layout like
```shellsession
$ tree -L 3
├── deps.edn
├── README
├── src
│   └── project_namespace
│   ├── main.clj
│   └── utilities.clj
└── test
└── project_namespace
├── test_main.clj
└── test_utilities.clj
```
then you can tell babashka to include both the `src` and `test`
folders in the classpath and start a socket REPL by running:
```shellsession
$ bb --classpath src:test --socket-repl 1666
```
Note that you can use the `clojure` tool to produce classpaths and download dependencies:
``` shellsession
$ cat deps.edn
{:deps
{my_gist_script
{:git/url "https://gist.github.com/borkdude/263b150607f3ce03630e114611a4ef42"
:sha "cfc761d06dfb30bb77166b45d439fe8fe54a31b8"}}
:aliases {:my-script {:main-opts ["-m" "my-gist-script"]}}}
$ CLASSPATH=$(clojure -Spath)
$ bb --classpath "$CLASSPATH" --main my-gist-script
Hello from gist script!
```
If there is no `--classpath` argument, the `BABASHKA_CLASSPATH` environment
variable will be used:
``` shellsession
$ export BABASHKA_CLASSPATH=$(clojure -Spath)
$ export BABASHKA_PRELOADS="(require '[my-gist-script])"
$ bb "(my-gist-script/-main)"
Hello from gist script!
```
When invoking `bb` with a main function, the expression `(System/getProperty
"babashka.main")` will return the name of the main function.
Also see the
[babashka.classpath](https://github.com/borkdude/babashka/#babashkaclasspath)
namespace which allows dynamically adding to the classpath.
See [deps.clj](doc/deps.clj.md) for a babashka script that replaces the `clojure` bash script.
## Uberscript
The `--uberscript` option collects the expressions in
`BABASHKA_PRELOADS`, the command line expression or file, the main entrypoint
and all required namespaces from the classpath into a single file. This can be
convenient for debugging and deployment.
Here is an example that uses a function from the [clj-commons/fs](https://github.com/clj-commons/fs) library.
Let's first set the classpath:
``` clojure
$ export BABASHKA_CLASSPATH=$(clojure -Spath -Sdeps '{:deps {clj-commons/fs {:mvn/version "1.5.2"}}}')
```
Write a little script, say `glob.clj`:
``` clojure
(ns foo (:require [me.raynes.fs :as fs]))
(run! (comp println str)
(fs/glob (first *command-line-args*)))
```
Now we can execute the script which uses the library:
``` shellsession
$ time bb glob.clj '*.md'
/Users/borkdude/Dropbox/dev/clojure/carve/README.md
bb glob.clj '*.md' 0.03s user 0.02s system 70% cpu 0.064 total
```
Producing an uberscript with all required code:
``` shellsession
$ bb -f glob.clj --uberscript glob-uberscript.clj
```
To prove that we don't need the classpath anymore:
``` shellsession
$ unset BABASHKA_CLASSPATH
$ time bb glob-uberscript.clj '*.md'
/Users/borkdude/Dropbox/dev/clojure/carve/README.md
bb glob-uberscript.clj '*.md' 0.03s user 0.02s system 93% cpu 0.049 total
```
Caveats:
- *Dynamic requires*. Building uberscripts works by running top-level `ns` and
`require` forms. The rest of the code is not evaluated. Code that relies on
dynamic requires may not work in an uberscript.
- *Resources*. The usage of `io/resource` assumes a classpath, so when this is
used in your uberscript, you still have to set a classpath and bring the
resources along.
If any of the above is problematic for your project, using an
[uberjar](#uberjar) is a good alternative.
### Carve
Uberscripts can be optimized by cutting out unused vars with
[carve](https://github.com/borkdude/carve).
``` shellsession
$ wc -l glob-uberscript.clj
607 glob-uberscript.clj
$ clojure -M:carve --opts '{:paths ["glob-uberscript.clj"] :aggressive true :silent true}'
$ wc -l glob-uberscript.clj
172 glob-uberscript.clj
```
Note that the uberscript became 72% shorter. This has a beneficial effect on execution time:
``` shellsession
$ time bb glob-uberscript.clj '*.md'
/Users/borkdude/Dropbox/dev/clojure/carve/README.md
bb glob-uberscript.clj '*.md' 0.02s user 0.01s system 93% cpu 0.032 total
```
## Uberjar
Babashka can create uberjars from a given classpath and optionally a main
method:
``` clojure
$ cat src/foo.clj
(ns foo (:gen-class)) (defn -main [& args] (prn :hello))
$ bb -cp $(clojure -Spath) -m foo --uberjar foo.jar
$ bb foo.jar
:hello
```
When producing a classpath using the `clojure` or `deps.clj` tool, Clojure
itself, spec and the core specs will be on the classpath and will therefore be
included in your uberjar, which makes it bigger than necessary:
``` shell
$ ls -lh foo.jar
-rw-r--r-- 1 borkdude staff 4.5M Aug 19 17:04 foo.jar
```
To exclude these dependencies, you can use the following `:classpath-overrides`
in your `deps.edn`:
``` clojure
{:aliases {:remove-clojure {:classpath-overrides {org.clojure/clojure nil
org.clojure/spec.alpha nil
org.clojure/core.specs.alpha nil}}}}
```
``` clojure
$ rm foo.jar
$ bb -cp $(clojure -A:remove-clojure -Spath) -m foo --uberjar foo.jar
$ bb foo.jar
:hello
$ ls -lh foo.jar
-rw-r--r-- 1 borkdude staff 871B Aug 19 17:07 foo.jar
```
If you want your uberjar to be compatible with the JVM, you'll need to compile
the main namespace. Babashka does not do compilation, so we use Clojure on the
JVM for that part:
``` clojure
$ rm foo.jar
$ mkdir classes
$ clojure -e "(require 'foo) (compile 'foo)"
foo
$ bb -cp $(clojure -Spath):classes -m foo --uberjar foo.jar
$ bb foo.jar
:hello
$ java -jar foo.jar
:hello
```
## System properties
Babashka sets the following system properties:
- `babashka.version`: the version string, e.g. `"1.2.0"`
- `babashka.main`: the `--main` argument
- `babashka.file`: the `--file` argument (normalized using `.getAbsolutePath`)
## `__name__ == "__main__"` pattern
In Python scripts there is a well-known pattern to check if the current file was
the file invoked from the command line, or loaded from another file: the
`__name__ == "__main__"` pattern. In babashka this pattern can be implemented with:
``` clojure
(= *file* (System/getProperty "babashka.file")
```
## Data readers
Data readers can be enabled by setting `*data-readers*` to a hashmap of symbols
to functions or vars:
``` clojure
$ bb "(set! *data-readers* {'t/tag inc}) #t/tag 1"
2
```
To preserve good startup time, babashka does not scan the classpath for
`data_readers.clj` files.
## Parsing command line arguments
Babashka ships with `clojure.tools.cli`:
``` clojure
(require '[clojure.tools.cli :refer [parse-opts]])
(def cli-options
;; An option with a required argument
[["-p" "--port PORT" "Port number"
:default 80
:parse-fn #(Integer/parseInt %)
:validate [#(< 0 % 0x10000) "Must be a number between 0 and 65536"]]
["-h" "--help"]])
(:options (parse-opts *command-line-args* cli-options))
```
``` shellsession
$ bb script.clj
{:port 80}
$ bb script.clj -h
{:port 80, :help true}
```
## Reader conditionals
Babashka supports reader conditionals by taking either the `:bb` or `:clj`
branch, whichever comes first. NOTE: the `:clj` branch behavior was added in
version 0.0.71, before that version the `:clj` branch was ignored.
``` clojure
$ bb "#?(:bb :hello :clj :bye)"
:hello
$ bb "#?(:clj :bye :bb :hello)"
:bye
$ bb "[1 2 #?@(:bb [] :clj [1])]"
[1 2]
```
## Running tests
Babashka bundles `clojure.test`. To make CI scripts fail you can use a simple
runner like this:
``` shell
#!/usr/bin/env bash
bb -cp "src:test:resources" \
-e "(require '[clojure.test :as t] '[borkdude.deps-test])
(let [{:keys [:fail :error]} (t/run-tests 'borkdude.deps-test)]
(System/exit (+ fail error)))"
```
## Spawning and killing a process
Use the `java.lang.ProcessBuilder` class.
Example:
``` clojure
user=> (def ws (-> (ProcessBuilder. ["python" "-m" "SimpleHTTPServer" "1777"]) (.start)))
#'user/ws
user=> (wait/wait-for-port "localhost" 1777)
{:host "localhost", :port 1777, :took 2}
user=> (.destroy ws)
nil
```
Also see this [example](examples/process_builder.clj).
## Core.async
In addition to `future`, `pmap`, `promise` and friends, you may use the
`clojure.core.async` namespace for asynchronous scripting. The following example
shows how to get first available value from two different processes:
``` clojure
bb '
(defn async-command [& args]
(async/thread (apply shell/sh "bash" "-c" args)))
(-> (async/alts!! [(async-command "sleep 2 && echo process 1")
(async-command "sleep 1 && echo process 2")])
first :out str/trim println)'
process 2
```
Caveat: currently the `go` macro is available for compatibility with JVM
programs, but the implementation maps to `clojure.core.async/thread` and the
single exclamation mark operations (`<!`, `>!`, etc.) map to the double
exclamation mark operations (`<!!`, `>!!`, etc.). It will not "park" threads,
like on the JVM.
Examples like the following may still work, but will take a lot more system
resources than on the JVM and will break down for some high value of `n`:
``` clojure
(require '[clojure.core.async :as async])
(def n 1000)
(let [cs (repeatedly n async/chan)
begin (System/currentTimeMillis)]
(doseq [c cs] (async/go (async/>! c "hi")))
(dotimes [_ n]
(let [[v _] (async/alts!! cs)]
(assert (= "hi" v))))
(println "Read" n "msgs in" (- (System/currentTimeMillis) begin) "ms"))
```
## HTTP
For making HTTP requests you can use:
- [babashka.curl](https://github.com/borkdude/babashka.curl). This library is
included with babashka and aliased as `curl` in the user namespace. The
interface is similar to that of
[clj-http](https://github.com/dakrone/clj-http) but it will shell out to
`curl` to make requests.
- [org.httpkit.client](https://github.com/http-kit/http-kit)
- `slurp` for simple `GET` requests
- [clj-http-lite](https://github.com/babashka/clj-http-lite) as a library.
- `clojure.java.shell` or `babashka.process` for shelling out to your
favorite command line http client
### Choosing the right client
If memory usage is a concern and you are downloading big files, choose
`babashka.curl` with `:as :stream` over `org.httpkit.client` since http-kit
holds the entire response in memory at once. Let's download a 200mb file with
10mb heap size:
``` clojure
$ bb -Xmx10m -e '(io/copy (:body (curl/get "http://ipv4.download.thinkbroadband.com/200MB.zip" {:as :stream})) (io/file "/tmp/200mb.zip"))'
```
With `babashka.curl` this works fine. However with `org.httpkit.client` that
won't work. Not even 190mb of heap will do:
``` clojure
$ bb -Xmx190m -e '(io/copy (:body @(org.httpkit.client/get "http://ipv4.download.thinkbroadband.com/200MB.zip" {:as :stream})) (io/file "/tmp/200mb.zip"))'
Sun Nov 08 23:01:46 CET 2020 [client-loop] ERROR - select exception, should not happen
java.lang.OutOfMemoryError: Array allocation too large.
```
If your script creates many requests with relatively small payloads, choose
`org.httpkit.client` over `babashka.curl` since `babashka.curl` creates a `curl`
process for each request.
In the future babashka (1.0.0?) may come with an HTTP client based on the JVM 11
`java.net.http` package that ticks all the boxes (async, HTTP/2, websockets,
multi-part file uploads, sane memory usage) and is a suitable replacement for
all of the above options. If you know about a GraalVM-friendly feature-complete
well-maintained library, please reach out!
### HTTP over Unix sockets
This can be useful for talking to Docker:
``` clojure
(require '[clojure.java.shell :refer [sh]])
(require '[cheshire.core :as json])
(-> (sh "curl" "--silent"
"--no-buffer" "--unix-socket"
"/var/run/docker.sock"
"http://localhost/images/json")
:out
(json/parse-string true)
first
:RepoTags) ;;=> ["borkdude/babashka:latest"]
```
## Shutdown hook
Adding a shutdown hook allows you to execute some code before the script exits.
``` clojure
$ bb -e '(-> (Runtime/getRuntime) (.addShutdownHook (Thread. #(println "bye"))))'
bye
```
This also works when the script is interrupted with ctrl-c.
## JDBC
Babashka supports the [`next.jdbc`](https://github.com/seancorfield/next-jdbc)
library along with drivers for [PostgresQL](https://www.postgresql.org/) and
[HSQLDB](http://hsqldb.org/). These features are not part of the standard `bb`
distribution but available via feature flags. See [doc/build.md](doc/build.md)
for details on how to build babashka with these features. See this
[test](test-resources/babashka/postgres_test.clj) for an example how to use
this.
Interacting with `psql`, `mysql` and the `sqlite` CLIs can be achieved by
shelling out. See the [examples](examples) directory.
## Communicating with an nREPL server
Babashka comes with the [nrepl/bencode](https://github.com/nrepl/bencode)
library which allows you to read and write bencode messages to a socket. A
simple example which evaluates a Clojure expression on an nREPL server started
with `lein repl`:
``` clojure
(ns nrepl-client
(:require [bencode.core :as b]))
(defn nrepl-eval [port expr]
(let [s (java.net.Socket. "localhost" port)
out (.getOutputStream s)
in (java.io.PushbackInputStream. (.getInputStream s))
_ (b/write-bencode out {"op" "eval" "code" expr})
bytes (get (b/read-bencode in) "value")]
(String. bytes)))
(nrepl-eval 52054 "(+ 1 2 3)") ;;=> "6"
```
## Printing returned values
Babashka doesn't print a returned `nil` as lots of scripts end in something side-effecting.
``` shell
$ bb '(:a {:a 5})'
5
$ bb '(:b {:a 5})'
$
```
If you really want to print the nil, you can use `(prn ..)` instead.
## Differences with Clojure
@ -1083,12 +300,6 @@ etc.). See the [feature flag documentation](doc/build.md#feature-flags) and the
implementation of the existing feature flags ([example
commit](https://github.com/borkdude/babashka/commit/02c7c51ad4b2b1ab9aa95c26a74448b138fe6659)).
## Babashka book
In the future we will migrate examples from this README to the babashka
[book](https://book.babashka.org/). This is work in progress and you are welcome
to contribute.
## Related projects
- [planck](https://planck-repl.org/)

View file

@ -1,67 +0,0 @@
### Deps.clj
The [`deps.clj`](https://github.com/borkdude/deps.clj/) script can be used to work with `deps.edn`-based projects:
``` shell
$ deps.clj -A:my-script -Scommand "bb -cp {{classpath}} {{main-opts}}"
Hello from gist script!
```
Create these aliases for brevity:
``` shell
$ alias bbk='deps.clj -Scommand "bb -cp {{classpath}} {{main-opts}}"'
$ alias babashka='rlwrap deps.clj -Scommand "bb -cp {{classpath}} {{main-opts}}"'
$ bbk -A:my-script
Hello from gist script!
$ babashka
Babashka v0.0.58 REPL.
Use :repl/quit or :repl/exit to quit the REPL.
Clojure rocks, Bash reaches.
user=> (require '[my-gist-script :as mgs])
nil
user=> (mgs/-main)
Hello from gist script!
nil
```
You can also use for example `deps.clj` to produce the classpath for a
`babashka` REPL:
```shellsession
$ cat script/start-repl.sh
#!/bin/sh -e
git_root=$(git rev-parse --show-toplevel)
export BABASHKA_CLASSPATH=$("$git_root"/script/deps.clj -Spath)
bb --socket-repl 1666
$ ./script/start-repl.sh
Babashka socket REPL started at localhost:1666
```
Now, given that your `deps.edn` and source tree looks something like
```shellsession
$ cat deps.edn
{:paths ["src" "test"]
:deps {}}
$ tree -L 3
├── deps.edn
├── README
├── script
│   ├── deps.clj
│   └── start-repl.sh
├── src
│   └── project_namespace
│   ├── main.clj
│   └── utilities.clj
└── test
└── project_namespace
├── test_main.clj
└── test_utilities.clj
```
you should now be able to `(require '[multi-machine-rsync.utilities :as util])`
in your REPL and the source code in `/src/multi_machine_rsync/utilities.clj`
will be evaluated and made available through the symbol `util`.

View file

@ -1,74 +0,0 @@
# Input and output flags
In one-liners the `*input*` value may come in handy. It contains the input read
from stdin as EDN by default. If you want to read in text, use the `-i` flag,
which binds `*input*` to a lazy seq of lines of text. If you want to read
multiple EDN values, use the `-I` flag. The `-o` option prints the result as
lines of text. The `-O` option prints the result as lines of EDN values.
> **Note:** `*input*` is only available in the `user` namespace, designed for
> one-liners. For writing scripts, see [scripts](#scripts).
The following table illustrates the combination of options for commands of the form
echo "{{Input}}" | bb {{Input flags}} {{Output flags}} "*input*"
| Input | Input flags | Output flag | `*input*` | Output |
|----------------|-------------|-------------|---------------|----------|
| `{:a 1}` <br> `{:a 2}` | | | `{:a 1}` | `{:a 1}` |
| hello <br> bye | `-i` | | `("hello" "bye")` | `("hello" "bye")` |
| hello <br> bye | `-i` | `-o` | `("hello" "bye")` | hello <br> bye |
| `{:a 1}` <br> `{:a 2}` | `-I` | | `({:a 1} {:a 2})` | `({:a 1} {:a 2})` |
| `{:a 1}` <br> `{:a 2}` | `-I` | `-O` | `({:a 1} {:a 2})` | `{:a 1}` <br> `{:a 2}` |
When combined with the `--stream` option, the expression is executed for each value in the input:
``` clojure
$ echo '{:a 1} {:a 2}' | bb --stream '*input*'
{:a 1}
{:a 2}
```
## Scripts
When writing scripts instead of one-liners on the command line, it is not
recommended to use `*input*`. Here is how you can rewrite to standard Clojure
code.
### EDN input
Reading a single EDN value from stdin:
``` clojure
(ns script
(:require [clojure.edn :as edn]))
(edn/read *in*)
```
Reading multiple EDN values from stdin (the `-I` flag):
``` clojure
(ns script
(:require [clojure.edn :as edn]
[clojure.java.io :as io]))
(let [reader (java.io.PushbackReader. (io/reader *in*))]
(take-while #(not (identical? ::eof %)) (repeatedly #(edn/read {:eof ::eof} reader))))
```
### Text input
Reading text from stdin can be done with `(slurp *in*)`. To get a lazy seq of
lines (the `-i` flag), you can use:
``` clojure
(ns script
(:require [clojure.java.io :as io]))
(line-seq (io/reader *in*))
```
### Output
To print to stdout, use `println` for text and `prn` for EDN values.

View file

@ -1,151 +0,0 @@
# Running a REPL
Babashka supports running a REPL, a socket REPL and an nREPL server.
## REPL
To start the REPL, type:
``` shell
$ bb --repl
```
To get history with up and down arrows, use `rlwrap`:
``` shell
$ rlwrap bb --repl
```
## Socket REPL
To start the socket REPL you can do this:
``` shell
$ bb --socket-repl 1666
Babashka socket REPL started at localhost:1666
```
Now you can connect with your favorite socket REPL client:
``` shell
$ rlwrap nc 127.0.0.1 1666
Babashka v0.0.14 REPL.
Use :repl/quit or :repl/exit to quit the REPL.
Clojure rocks, Bash reaches.
bb=> (+ 1 2 3)
6
bb=> :repl/quit
$
```
The `--socket-repl` option takes options similar to the `clojure.server.repl` Java property option in Clojure:
``` clojure
$ bb --socket-repl '{:address "0.0.0.0" :accept clojure.core.server/repl :port 1666}'
```
Editor plugins and tools known to work with a babashka socket REPL:
- Emacs: [inf-clojure](https://github.com/clojure-emacs/inf-clojure):
To connect:
`M-x inf-clojure-connect <RET> localhost <RET> 1666`
Before evaluating from a Clojure buffer:
`M-x inf-clojure-minor-mode`
- Atom: [Chlorine](https://github.com/mauricioszabo/atom-chlorine)
- Vim: [vim-iced](https://github.com/liquidz/vim-iced)
- IntelliJ IDEA: [Cursive](https://cursive-ide.com/)
Note: you will have to use a workaround via
[tubular](https://github.com/mfikes/tubular). For more info, look
[here](https://cursive-ide.com/userguide/repl.html#repl-types).
## pREPL
Launching a prepl can be done as follows:
``` clojure
$ bb --socket-repl '{:address "0.0.0.0" :accept clojure.core.server/io-prepl :port 1666}'
```
or programmatically:
``` clojure
$ bb -e '(clojure.core.server/io-prepl)'
(+ 1 2 3)
{:tag :ret, :val "6", :ns "user", :ms 0, :form "(+ 1 2 3)"}
```
## nREPL
To start an nREPL server:
``` shell
$ bb --nrepl-server 1667
```
Then connect with your favorite nREPL client:
``` clojure
$ lein repl :connect 1667
Connecting to nREPL at 127.0.0.1:1667
user=> (+ 1 2 3)
6
user=>
```
Editor plugins and tools known to work with the babashka nREPL server:
- Emacs: [CIDER](https://docs.cider.mx/cider/platforms/babashka.html)
- `lein repl :connect`
- VSCode: [Calva](http://calva.io/)
- Atom: [Chlorine](https://github.com/mauricioszabo/atom-chlorine)
- (Neo)Vim: [vim-iced](https://github.com/liquidz/vim-iced), [conjure](https://github.com/Olical/conjure), [fireplace](https://github.com/tpope/vim-fireplace)
The babashka nREPL server does not write an `.nrepl-port` file at startup, but
you can easily write a script that launches the server and writes the file:
``` clojure
#!/usr/bin/env bb
(import [java.net ServerSocket]
[java.io File]
[java.lang ProcessBuilder$Redirect])
(require '[babashka.wait :as wait])
(let [nrepl-port (with-open [sock (ServerSocket. 0)] (.getLocalPort sock))
cp (str/join File/pathSeparatorChar ["src" "test"])
pb (doto (ProcessBuilder. (into ["bb" "--nrepl-server" (str nrepl-port)
"--classpath" cp]
*command-line-args*))
(.redirectOutput ProcessBuilder$Redirect/INHERIT))
proc (.start pb)]
(wait/wait-for-port "localhost" nrepl-port)
(spit ".nrepl-port" nrepl-port)
(.deleteOnExit (File. ".nrepl-port"))
(.waitFor proc))
```
### Debugging the nREPL server
To debug the nREPL server from the binary you can run:
``` shell
$ BABASHKA_DEV=true bb --nrepl-server 1667
```
This will print all the incoming messages.
To debug the nREPL server from source:
``` clojure
$ git clone https://github.com/borkdude/babashka --recursive
$ cd babashka
$ BABASHKA_DEV=true clojure -A:main --nrepl-server 1667
```