From f23723abeb45d185c26ccc4d6bb7f7611d1ed670 Mon Sep 17 00:00:00 2001 From: Michiel Borkent Date: Sat, 28 Nov 2020 21:20:39 +0100 Subject: [PATCH] Clean up docs [skip ci] --- README.md | 799 +----------------------------------------------- doc/deps.clj.md | 67 ---- doc/io-flags.md | 74 ----- doc/repl.md | 151 --------- 4 files changed, 5 insertions(+), 1086 deletions(-) delete mode 100644 doc/deps.clj.md delete mode 100644 doc/io-flags.md delete mode 100644 doc/repl.md diff --git a/README.md b/README.md index 8d31f514..c7cf8974 100644 --- a/README.md +++ b/README.md @@ -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 Collect preloads, -e, -f and -m and all required namespaces from the classpath into a single executable file. - -Evaluation: - - -e, --eval Evaluate an expression. - -f, --file Evaluate a file. - -cp, --classpath Classpath to use. - -m, --main 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: ") - (System/exit 1)) - (write-html file (get-url url))) -``` - -``` shellsession -$ ./download_html.clj -Usage: - -$ ./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/) diff --git a/doc/deps.clj.md b/doc/deps.clj.md deleted file mode 100644 index 2205a27e..00000000 --- a/doc/deps.clj.md +++ /dev/null @@ -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`. diff --git a/doc/io-flags.md b/doc/io-flags.md deleted file mode 100644 index e1b7b1ce..00000000 --- a/doc/io-flags.md +++ /dev/null @@ -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}`
`{:a 2}` | | | `{:a 1}` | `{:a 1}` | -| hello
bye | `-i` | | `("hello" "bye")` | `("hello" "bye")` | -| hello
bye | `-i` | `-o` | `("hello" "bye")` | hello
bye | -| `{:a 1}`
`{:a 2}` | `-I` | | `({:a 1} {:a 2})` | `({:a 1} {:a 2})` | -| `{:a 1}`
`{:a 2}` | `-I` | `-O` | `({:a 1} {:a 2})` | `{:a 1}`
`{: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. diff --git a/doc/repl.md b/doc/repl.md deleted file mode 100644 index 0dae3f05..00000000 --- a/doc/repl.md +++ /dev/null @@ -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 localhost 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 -```