Bass

stdlib

The standard library is the set of modules that come with Bass.

ground module

The ground module is inherited by all modules. It provides basic language constructs and the standard toolkit for running thunks.

index

*/-

$ constructs a thunk with args
$$ constructs a thunk with args passed to its entrypoint
* multiplies numbers
+ sums numbers
- subtracts ys from x
-> passes a value through a series of function calls
< returns true if the numbers are in ascending order
<= returns true if the numbers are in ascending or equal order
= returns true if the values are all equal
> returns true if the numbers are in descending order
>= returns true if the numbers are in descending or equal order

a

across returns a pipe source that yields a list of values across all the given sources
addr returns an address for a port provided by the thunk
always returns a function that returns x for any value
and returns a truthy value if none of the conds return a falsy value
append joins all given lists into one listdeprecated
applicative? returns true if the value is an applicative
apply call an applicative's underlying operative with a list of arguments
assert applies the predicate to its arguments and errors if it returns false
assoc assoc(iate) keys with values in a clone of a scope

b

bind attempts to bind values in the scope
binds? returns true if the scope has a value bound to the given symbol
boolean? returns true if the value is true or false

c

cache-dir returns a cache directory with the given identifier and concurrency mode
case evaluates the branch that successfully binds the given value
cd chain a sequence of thunks with a given working directory
collect returns (f value) for every value read from the source
combiner? returns true if the value is a combiner
concat joins all given lists into one list
cond if-then-else, but with many clauses
conj conjoins values onto the end of a list
cons construct a pair from the given values
current-scope returns the scope of the caller
curryfn returns a fn which accepts args one value at a time

d

def bind symbols to values in the current scope
defn construct a function and bind it to a symbol
defop construct an operative and bind it to a symbol
do evaluate a sequence, returning the last value
doc print docs for symbols
docker-build returns a thunk built from a Dockerfile in the context directory
dump encodes a value as JSON to stderr

e

each calls f for every value read from the source
emit emits a value to a sink
empty? returns true if the value is an empty list, a zero-length string, an empty scope, or null
error errors with the given message
eval evaluate a value in a scope
except-globs returns a path with the given globs excluded
export returns a virtual file containing the thunk as an OCI tarball

f

filter returns only values from xs which satisfy the predicate
first return the first value in a pair
fn construct a function
foldl reduces xs, leftmost values first, with initial value z
foldr reduces xs, rightmost values first, with initial value z
for loops over values from sources
from chain a sequence of thunks starting from an initial image

g

glob returns a path narrowed to paths matching the given glob patterns

i

id identity function; returns its argument
if if then else (branching logic)
ignore? returns true if the value is _ ("ignore")
import binds symbols in the current scope to their values from the source scope
insecure! sets the :insecure field of the thunk to true

j

json returns a string containing val encoded as JSON

k

keys collects the bindings from a scope

l

last returns the last value from the source
length return the length of the given list
let binds values in a child scope
linux a path root for resolving Linux images
list construct a list from a sequence of values
list* prepend a sequence of values to a list given as the final argument
list->scope constructs an object from a list of flat keyword/value pairs
list->source creates a pipe source from a list of values in chronological order
list? returns true if the value is a linked list
load load a thunk as a module
log logs a string message or arbitrary value to stderr

m

make-scope construct a scope with the given parents
map returns a list containing the result of applying f to each member of xs
map-pairs calls a function with alternating pairs in a flat list (i.e. with pairs ungrouped)
mask shrouds a string in secrecy
max returns the largest number
memo memo(ize)s a function
merge returns a scope containing the union of the given scopes
meta returns the meta attached to the value
min returns the smallest number
mkfile returns an in-memory file with the given content
mkfs returns a dir path backed by an in-memory filesystem
module returns a scope with only the specified bindings from a child scope

n

next receive the next value from a source
not negates the given bool-ish value
now returns the current UTC time truncated to the given seconds
null? returns true if the value is null
number? returns true if the value is a number

o

oci-load returns a thunk loaded from an OCI image tarball
only-globs returns a path with the given globs as the only included files
op construct an operative
operative? returns true if the value is an operative
or returns the first truthy value returned by evaluating conds

p

pair? returns true if the value is a pair
path-base returns the path-name converted into a file path
path-name returns the base name of the path
path-stem returns the base name of the path, without any extension
path? returns true if the value is a path
provide provide bindings to the current scope from a nested scope
publish publishes the thunk to a container registry

q

quot quot(ient) of dividing num by denum
quote returns the unevaluated form

r

read returns a stream producing values read from a thunk's output or a file's content
recall-memo fetches the result of a memoized function call
reduce-kv reduces a scope
refute applies the predicate to its arguments and errors if it returns true
resolve resolve an image reference to its most exact form
rest return the second value in a pair, i.e. the rest of a linked list
run runs a thunk

s

scope->list returns a flat list alternating a scope's keys and values
scope? returns true if the value is a scope
scratch an empty thunk
second return the second member of a linked list
sink? returns true if the value is a sink
source? returns true if the value is a source
start starts running a thunk asynchronously
store-memo stores the result of a memoized function call
str returns the concatenation of all given strings or values
string->cmd-path converts a string to a command or file path
string->dir converts a string to a directory path
string->fs-path parses a string value into a file or directory path
string->symbol convert a string to a symbol
string? returns true if the value is a string
subpath extend path with another path
substring returns a portion of a string
succeeds? returns true if the thunk successfully runs (i.e. exit code 0)
symbol->string convert a symbol to a string
symbol? returns true if the value is a symbol

t

take reads the next n values from the source into a list
take-all reads all values from the source into a list
third return third member of a linked list
thunk-args returns the arguments to the thunk's command
thunk-cmd returns the thunk's full command and args
thunk? returns true if the value is a valid thunk
trim removes whitespace from both ends of a string

u

unwrap returns an applicative's underlying combiner
use loads each thunk and binds it as the name from the thunk's command path

v

vals collects the values from a scope

w

wait waits for all started thunks to finish
when evaluates the body if test returns true
with-args returns thunk with args set to args
with-cmd returns the thunk's full command and argumentss
with-default-args returns thunk with default args set to args
with-dir returns thunk with the working directory set to dir
with-entrypoint returns thunk with entrypoint set to entrypoint
with-entrypoint-args returns thunk with default args set to args
with-env returns thunk with env set to the given env
with-image returns thunk with the base image set to image
with-insecure returns thunk with the insecure flag set to bool
with-label returns thunk with the label set to val
with-meta returns val with the given scope as its metadata
with-mount returns thunk with a mount from source to the target path
with-port returns thunk with a named port appended to its ports
with-stdin returns thunk with stdin set to vals
with-tls returns thunk with paths to a TLS certificate and key to generate
wrap construct an applicative from a combiner (typically an operative)
wrap-cmd prepend a command + args to a thunk's command + args
write writes the source to the destination path

bindings

(def binding value) operative? combiner?
𝄢

bind symbols to values in the current scope

𝄢

Supports destructuring assignment.

(def abc "it's easy as")
abc
(def [a b c] [1 2 3])
(
  1. a
  2. b
  3. c
)
[abc a b c]
(
  1. "it's easy as"
  2. 1
  3. 2
  4. 3
)
(if cond yes no) operative? combiner?
𝄢

if then else (branching logic)

𝄢

Evaluates the cond form. If the result is truthy (not false or null), evaluates the yes form. Otherwise, evaluates the no form.

(if false (error "bam") :phew)
phew
(dump val) applicative? combiner?
𝄢

encodes a value as JSON to stderr

𝄢

Returns the given value.

(dump {:foo-bar "baz"})
stderr: 3 lines
{
"foo-bar": "baz"
}
{
:foo-bar "baz"
}
(mkfs & file-content-kv) applicative? combiner?
𝄢

returns a dir path backed by an in-memory filesystem

𝄢

Takes alternating file paths and their content, which must be a text string, and returns the root directory of an in-memory filesystem containing the specified files.

𝄢

All embedded files have 0644 Unix file permissions and a zero (Unix epoch) mtime.

(def fs (mkfs ./file "hey" ./sub/file "im in a subdir"))
fs
(next (read (from (linux/alpine) ($ cat fs/file)) :raw))
stderr: 9 lines
=> docker-image://docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a6
24ee31d03372cc1d138a3126 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d
03372cc1d138a3126 [0.01s]
=> resolve image config for docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a
01e5a861a624ee31d03372cc1d138a3126 [0.02s]
=> mkfile /file [0.01s]
=> cat <fs>/file [0.24s]
hey
"hey"
(json val) applicative? combiner?
𝄢

returns a string containing val encoded as JSON

(json {:foo-bar "baz"})
"{\"foo-bar\":\"baz\"}"
(log val & fields) applicative? combiner?
𝄢

logs a string message or arbitrary value to stderr

𝄢

Returns the given value.

𝄢

Accepts key-value fields for structured logging data.

(log "hello, world!")
stderr: 1 lines
info hello, world!
"hello, world!"
(log "doing something" :a 1 :since {:day 1})
stderr: 1 lines
info doing something {"a": 1, "since": {"day": 1}}
"doing something"
(error msg & fields) applicative? combiner?
𝄢

errors with the given message

𝄢

Accepts key-value fields for structured error data.

(error "oh no!")
stderr: 7 lines
error! call trace (oldest first):
┆ <fs>/literate-4:1:0..1:16
1 │ (error "oh no!")
^^^^^^^^^^^^^^^^
oh no!
(error "oh no!" :exit-code 2)
stderr: 9 lines
error! call trace (oldest first):
┆ <fs>/literate-4:1:0..1:29
1 │ (error "oh no!" :exit-code 2)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
oh no!
:exit-code 2
(now seconds) applicative? combiner?
𝄢

returns the current UTC time truncated to the given seconds

𝄢

Typically used to influence caching for thunks whose result may change over time.

(now 60)
"2023-04-18T04:59:00Z"
(do & body) operative? combiner?
𝄢

evaluate a sequence, returning the last value

(do (def abc 123) (+ abc 1))
124
abc
123
(cons a d) applicative? combiner?
𝄢

construct a pair from the given values

(cons 1 [2 3])
(
  1. 1
  2. 2
  3. 3
)
(cons 1 2)
(1 & 2)
(wrap comb) applicative? combiner?
𝄢

construct an applicative from a combiner (typically an operative)

𝄢

When called, an applicative evaluates its arguments before passing them along to the underlying combiner.

(defop log-quote [x] _ (log x) x)
log-quote
(log-quote (* 6 7))
stderr: 1 lines
info (* 6 7)
(
  1. *
  2. 6
  3. 7
)
((wrap log-quote) (* 6 7))
stderr: 1 lines
info 42
42
(unwrap app) applicative? combiner?
𝄢

returns an applicative's underlying combiner

𝄢

You probably won't use this a lot. It's used to implement higher level abstractions like apply.

(op formals eformal & body) operative? combiner?
𝄢

construct an operative

𝄢

An operative is a combiner that is called with unevaluated arguments and the caller's dynamic scope.

𝄢

Operatives are used to define new syntax forms.

(def quote (op [x] _ x))
quote
(quote abc)
abc
(eval form scope) applicative? combiner?
𝄢

evaluate a value in a scope

(eval :abc {:abc 123})
123
(eval [* :x :y] {:x 6 :y 7})
42
(make-scope & parents) applicative? combiner?
𝄢

construct a scope with the given parents

(make-scope {:a 1} {:b 2})
{
{
:a 1
}
{
:b 2
}
}
(eval [+ :a :b] (make-scope {:a 1} {:b 2}))
3
(bind scope formals val) applicative? combiner?
𝄢

attempts to bind values in the scope

𝄢

Returns true if the binding succeeded, otherwise false.

(if (bind (current-scope) :abc 123) abc :mismatch)
123
(if (bind (current-scope) [] 123) _ :mismatch)
mismatch
(meta val) applicative? combiner?
𝄢

returns the meta attached to the value

𝄢

Returns null if the value has no metadata.

(meta meta) ; whoa
{
:column 0
:doc "returns the meta attached to the value\n\nReturns null if the value has no metadata.\n\n=> (meta meta) ; whoa"
:file <fs>/bass/ground.go
:line 222
}
(with-meta val meta) applicative? combiner?
𝄢

returns val with the given scope as its metadata

(meta (with-meta _ {:a 1}))
{
:a 1
}
(meta (with-meta (with-meta _ {:a 1}) {:b 2}))
{
:b 2
}
(doc & symbols) operative? combiner?
𝄢

print docs for symbols

𝄢

Prints the documentation for the given symbols resolved from the current scope.

(doc doc)
stderr: 9 lines
--------------------------------------------------
doc operative? combiner?
args: symbols
print docs for symbols
Prints the documentation for the given symbols resolved from the current scope.
=> (doc doc)
null
(null? val) applicative? combiner?
𝄢

returns true if the value is null

(null? null)
true
(null? _)
false
(null? false)
false
(ignore? val) applicative? combiner?
𝄢

returns true if the value is _ ("ignore")

(ignore? _)
true
(ignore? null)
false
(boolean? val) applicative? combiner?
𝄢

returns true if the value is true or false

(boolean? null)
false
(boolean? true)
true
(boolean? false)
true
(number? val) applicative? combiner?
𝄢

returns true if the value is a number

(number? 123)
true
(number? "123")
false
(string? val) applicative? combiner?
𝄢

returns true if the value is a string

(string? "abc")
true
(string? :abc)
false
(symbol? val) applicative? combiner?
𝄢

returns true if the value is a symbol

(symbol? :abc)
true
(symbol? "abc")
false
(scope? val) applicative? combiner?
𝄢

returns true if the value is a scope

𝄢

A scope is a mapping from symbols to values.

(scope? {})
true
true
(scope? [])
false
(sink? val) applicative? combiner?
𝄢

returns true if the value is a sink

𝄢

A sink is a type that you can send values to using emit.

(sink? *stdout*)
true
(sink? *stdin*)
false
(source? val) applicative? combiner?
𝄢

returns true if the value is a source

𝄢

A source is a type that you can read values from using next.

(source? *stdin*)
true
(source? *stdout*)
false
(list? val) applicative? combiner?
𝄢

returns true if the value is a linked list

𝄢

A linked list is a pair whose second value is another list or empty.

(list? [])
true
(list? {})
false
(pair? val) applicative? combiner?
𝄢

returns true if the value is a pair

(pair? [])
false
(pair? [1])
true
(pair? [1 & 2])
true
(pair? (quote [1 & 2]))
true
(applicative? val) applicative? combiner?
𝄢

returns true if the value is an applicative

𝄢

An applicative is a combiner that wraps another combiner.

𝄢

When an applicative is called, it evaluates its operands in the caller's evironment and passes them to the underlying combiner.

true
false
(operative? val) applicative? combiner?
𝄢

returns true if the value is an operative

𝄢

An operative is a combiner that is given the caller's scope.

𝄢

An operative may decide whether and how to evaluate its arguments. They are typically used to define new syntactic constructs.

false
true
(combiner? val) applicative? combiner?
𝄢

returns true if the value is a combiner

𝄢

A combiner takes sequence of values as arguments and returns another value.

true
true
(path? val) applicative? combiner?
𝄢

returns true if the value is a path

𝄢

A path is a reference to a file, directory, or command.

(path? ./foo)
true
(path? .foo)
true
(path? (subpath (.tests) ./coverage.html))
true
(empty? val) applicative? combiner?
𝄢

returns true if the value is an empty list, a zero-length string, an empty scope, or null

(empty? [])
true
(empty? "")
true
(empty? {})
true
(empty? null)
true
(empty? :my-soul)
false
(thunk? val) applicative? combiner?
𝄢

returns true if the value is a valid thunk

(thunk? (.yep))
true
(thunk? [.nope])
false
(thunk? {:not-even "close"})
false
(+ & nums) applicative? combiner?
𝄢

sums numbers

(+ 1 2 3)
6
(* & nums) applicative? combiner?
𝄢

multiplies numbers

(* 2 3 7)
42
(quot num denom) applicative? combiner?
𝄢

quot(ient) of dividing num by denum

(quot 84 2)
42
(- num & nums) applicative? combiner?
𝄢

subtracts ys from x

𝄢

If only x is given, returns the negation of x.

(- 10 4)
6
(- 10 4 1)
5
(- 6)
-6
(max num & nums) applicative? combiner?
𝄢

returns the largest number

(max 6 42 7)
42
(min num & nums) applicative? combiner?
𝄢

returns the smallest number

(min 6 42 7)
6
(= val & vals) applicative? combiner?
𝄢

returns true if the values are all equal

(= 1 1 1 1)
true
(= :hello :hello :goodbye)
false
(= {:a 1} {:a 1})
true
(> num & nums) applicative? combiner?
𝄢

returns true if the numbers are in descending order

(> 9 8 7)
true
(> 9 8 8)
false
(>= num & nums) applicative? combiner?
𝄢

returns true if the numbers are in descending or equal order

(> 9 8 7)
true
(> 9 8 8)
false
(< num & nums) applicative? combiner?
𝄢

returns true if the numbers are in ascending order

(< 7 8 9)
true
(> 8 8 9)
false
(<= num & nums) applicative? combiner?
𝄢

returns true if the numbers are in ascending or equal order

(< 7 8 9)
true
(> 8 8 9)
false
(list->source list) applicative? combiner?
𝄢

creates a pipe source from a list of values in chronological order

(list->source [1 2 3])
<source: 1 2 3>
(across & sources) applicative? combiner?
𝄢

returns a pipe source that yields a list of values across all the given sources

𝄢

Each list has the last value for each source. Values from each source are never skipped, but not every combination will be produced.

(def evens (list->source [0 2 4]))
evens
(def odds (list->source [1 3 5]))
odds
(def combined (across evens odds))
combined
[(next combined) (next combined)]
(
  1. (
    1. 0
    2. 1
    )
  2. (
    1. 0
    2. 3
    )
)
(emit val sink) applicative? combiner?
𝄢

emits a value to a sink

(emit {:a 1} *stdout*)
{
  "a": 1
}
null
(next src & default) applicative? combiner?
𝄢

receive the next value from a source

𝄢

If the source has ended, no value will be available. A default value may be provided, otherwise an error is raised.

(next (list->source [1]) :eof)
1
(next *stdin* :eof)
eof
(reduce-kv f init kv) applicative? combiner?
𝄢

reduces a scope

𝄢

Takes a 3-arity function, an initial value, and a scope. If the scope is empty, the initial value is returned. Otherwise, calls the function for each key-value pair, with the current value as the first argument.

(reduce-kv assoc {:d 4} {:a 1 :b 2 :c 3})
{
:d 4
:a 1
:b 2
:c 3
}
(assoc obj & kvs) applicative? combiner?
𝄢

assoc(iate) keys with values in a clone of a scope

𝄢

Takes a scope and a flat pair sequence alternating symbols and values.

𝄢

Returns a clone of the scope with the symbols fields set to their associated value.

(assoc {:a 1} :b 2 :c 3)
{
:a 1
:b 2
:c 3
}
(symbol->string sym) applicative? combiner?
𝄢

convert a symbol to a string

(symbol->string :hello!)
"hello!"
(string->symbol str) applicative? combiner?
𝄢

convert a string to a symbol

(string->symbol "hello!")
hello!
(str & vals) applicative? combiner?
𝄢

returns the concatenation of all given strings or values

(str "abc" 123 "def" 456)
"abc123def456"
(substring str start & end) applicative? combiner?
𝄢

returns a portion of a string

𝄢

With one number supplied, returns the portion from the offset to the end.

𝄢

With two numbers supplied, returns the portion between the first offset and the last offset, exclusive.

(substring "abcdef" 2 4)
"cd"
(trim str) applicative? combiner?
𝄢

removes whitespace from both ends of a string

(trim " hello world!\n ")
"hello world!"
(scope->list obj) applicative? combiner?
𝄢

returns a flat list alternating a scope's keys and values

𝄢

The returned list is the same form accepted by assoc.

(scope->list {:a 1 :b 2 :c 3})
(
  1. a
  2. 1
  3. b
  4. 2
  5. c
  6. 3
)
(apply assoc (cons {:d 4} (scope->list {:a 1 :b 2 :c 3})))
{
:d 4
:a 1
:b 2
:c 3
}
(string->fs-path str) applicative? combiner?
𝄢

parses a string value into a file or directory path

(string->fs-path "./file")
./file
(string->fs-path "file")
./file
(string->fs-path "dir/")
./dir/
(string->cmd-path str) applicative? combiner?
𝄢

converts a string to a command or file path

𝄢

If the value contains a /, it is converted into a file path.

𝄢

Otherwise, the given value is converted into a command path.

(string->cmd-path "scripts/foo")
./scripts/foo
.bash
(string->dir str) applicative? combiner?
𝄢

converts a string to a directory path

𝄢

A trailing slash is not required; the path is always assumed to be a directory.

(string->dir "dir")
./dir/
(string->dir "dir/")
./dir/
(subpath parent-dir child-path) applicative? combiner?
𝄢

extend path with another path

(subpath ./dir/ ./file)
./dir/file
(subpath (.tests) ./coverage.html)
.tests
{
:args
(
  1. .tests
)
}
./coverage.html
(path-name path) applicative? combiner?
𝄢

returns the base name of the path

𝄢

For a command path, this returns the command name.

𝄢

For a file or dir path, it returns the file or dir name.

𝄢

For a file path, it returns the file name.

𝄢

For a thunk, it returns the thunk's hash.

(path-name .bash)
"bash"
(path-name ./some/file)
"file"
(path-name ./some/dir/)
"dir"
(path-name (.tests))
"31TBBNE4GQGI0"
(path-stem path) applicative? combiner?
𝄢

returns the base name of the path, without any extension

(path-stem .bash)
"bash"
(path-stem ./some/file.bass)
"file"
(path-stem ./some/dir/)
"dir"
(path-stem (.tests))
"31TBBNE4GQGI0"
scratch combiner? path? thunk?
𝄢

an empty thunk

<no command>
{
}
(with-image thunk image) applicative? combiner?
𝄢

returns thunk with the base image set to image

𝄢

Image is either a thunk? or an image ref.

𝄢

Recurses when thunk's image is another thunk, setting the deepest ref or unset image.

𝄢

See also from.

(with-image ($ go test ./...) (linux/golang))
"go"
{
:image
<no command>
{
:image
{
:repository "golang"
:platform
{
:architecture "amd64"
:os "linux"
}
:tag "latest"
:digest "sha256:403f48633fb5ebd49f9a2b6ad6719f912df23dae44974a0c9445be331e72ff5e"
}
}
:args
(
  1. "go"
  2. "test"
  3. ./...
)
}
(from (linux/golang) ($ go test ./...))
"go"
{
:image
<no command>
{
:image
{
:repository "golang"
:platform
{
:architecture "amd64"
:os "linux"
}
:tag "latest"
:digest "sha256:403f48633fb5ebd49f9a2b6ad6719f912df23dae44974a0c9445be331e72ff5e"
}
}
:args
(
  1. "go"
  2. "test"
  3. ./...
)
}
(with-dir thunk dir) applicative? combiner?
𝄢

returns thunk with the working directory set to dir

𝄢

Unlike cd, the value of with-dir is resolved at runtime, meaning it can use container-local paths.

𝄢

If the thunk needs to write to its output directory, the output path passed to the command must be relative to the given dir. Thunk paths and other mounts will always be 1 level deep in the output directory, so use ../ to refer to back to the output directory, repeated for each additional level of depth. If the depth is unknown, you should use cd instead.

(with-dir (.tests) ./src/)
.tests
{
:args
(
  1. .tests
)
:dir ./src/
}
(with-cmd thunk cmd) applicative? combiner?
𝄢

returns the thunk's full command and argumentss

(with-cmd ($ replaced) ["go" "test" ./...])
"go"
{
:args
(
  1. "go"
  2. "test"
  3. ./...
)
}
(with-args thunk args) applicative? combiner?
𝄢

returns thunk with args set to args

(with-args (.go) ["test" "./..."])
.go
{
:args
(
  1. .go
  2. "test"
  3. "./..."
)
}
(with-entrypoint thunk entrypoint) applicative? combiner?
𝄢

returns thunk with entrypoint set to entrypoint

(with-default-args thunk args) applicative? combiner?
𝄢

returns thunk with default args set to args

(with-entrypoint-args thunk args) applicative? combiner?
𝄢

returns thunk with default args set to args

(with-stdin thunk vals) applicative? combiner?
𝄢

returns thunk with stdin set to vals

(with-stdin ($ jq ".a") [{:a 1} {:a 2}])
"jq"
{
:args
(
  1. "jq"
  2. ".a"
)
:stdin
(
  1. {
    :a 1
    }
  2. {
    :a 2
    }
)
}
(with-env thunk env) applicative? combiner?
𝄢

returns thunk with env set to the given env

(with-env ($ jq ".a") {:FOO "hello"})
"jq"
{
:args
(
  1. "jq"
  2. ".a"
)
:env
{
:FOO "hello"
}
}
(with-insecure thunk bool) applicative? combiner?
𝄢

returns thunk with the insecure flag set to bool

𝄢

The insecure flag determines whether the thunk runs with elevated privileges, and is named to be indicate the reduced security assumptions.

(with-insecure (.boom) true)
.boom
{
:insecure true
:args
(
  1. .boom
)
}
(= (.boom) (with-insecure (.boom) false))
true
(with-label thunk name val) applicative? combiner?
𝄢

returns thunk with the label set to val

𝄢

Labels are used to set metadata on a thunk, and are not used by the thunk itself.

𝄢

When the thunk is exported or published, labels will be included in the OCI image.

(with-label ($ sleep 10) :at (now 10))
"sleep"
{
:args
(
  1. "sleep"
  2. 10
)
:labels
{
:at "2023-04-18T04:59:40Z"
}
}
(with-port thunk sym int) applicative? combiner?
𝄢

returns thunk with a named port appended to its ports

(with-port ($ godoc "-http=:6060") :godoc 6060)
"godoc"
{
:args
(
  1. "godoc"
  2. "-http=:6060"
)
}
(with-tls thunk cert-path key-path) applicative? combiner?
𝄢

returns thunk with paths to a TLS certificate and key to generate

(with-tls ($ godoc "-http=:6060") ./cert.pem ./key.pem)
"godoc"
{
:args
(
  1. "godoc"
  2. "-http=:6060"
)
}
(with-mount thunk source target) applicative? combiner?
𝄢

returns thunk with a mount from source to the target path

(with-mount ($ find ./inputs/) *dir*/inputs/ ./inputs/)
"find"
{
:args
(
  1. "find"
  2. ./inputs/
)
:mounts
(
  1. {
    :source <host: /tmp/bass-scope1317982988>/inputs/
    :target ./inputs/
    }
)
}
(thunk-cmd thunk) applicative? combiner?
𝄢

returns the thunk's full command and args

(thunk-cmd ($ foo abc))
(
  1. "foo"
  2. "abc"
)
(thunk-cmd ($ foo))
(
  1. "foo"
)
(thunk-args thunk) applicative? combiner?
𝄢

returns the arguments to the thunk's command

(thunk-args ($ foo abc))
(
  1. "abc"
)
(thunk-args ($ foo))
(
)
(load thunk) applicative? combiner?
𝄢

load a thunk as a module

𝄢

This is the primitive mechanism for loading other Bass code.

𝄢

Typically used in combination with *dir* to load paths relative to the current file's directory.

(load (.strings))
<scope: {{thunk KP8HLM3BB1O7E: .strings}}>
(resolve platform ref) applicative? combiner?
𝄢

resolve an image reference to its most exact form

(resolve {:platform {:os "linux"} :repository "golang" :tag "latest"})
stderr: 1 lines
=> resolve image config for docker.io/library/golang:latest [0.16s]
<no command>
{
:image
{
:repository "golang"
:platform
{
:architecture "amd64"
:os "linux"
}
:tag "latest"
:digest "sha256:403f48633fb5ebd49f9a2b6ad6719f912df23dae44974a0c9445be331e72ff5e"
}
}
(start thunk handler) applicative? combiner?
𝄢

starts running a thunk asynchronously

𝄢

If the thunk errors or exits nonzero the handler is called with a combiner that raises the error when called.

𝄢

If the thunk runs succeeds the handler is called with null.

(start (from (linux/alpine) ($ banana)) null?)
(wrap <builtin: ({{thunk RN0K3B2K19HQ0: banana}})>)
((start (from (linux/alpine) ($ banana)) null?))
stderr: 10 lines
=> docker-image://docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a6
24ee31d03372cc1d138a3126 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d
03372cc1d138a3126 [0.01s]
=> resolve image config for docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a
01e5a861a624ee31d03372cc1d138a3126 [0.01s]
=> banana ERROR [0.18s]
start: exec: "banana": executable file not found in $PATH
!!! banana
6: [0.13s] start: exec: "banana": executable file not found in $PATH
false
((start (from (linux/alpine) ($ echo)) null?))
stderr: 8 lines
=> docker-image://docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a6
24ee31d03372cc1d138a3126 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d
03372cc1d138a3126 [0.01s]
=> resolve image config for docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a
01e5a861a624ee31d03372cc1d138a3126 [0.01s]
=> echo [0.23s]
true
(defn raiser [err] (and err (err)))
raiser
((start (from (linux/alpine) ($ banana)) raiser))
stderr: 27 lines
=> docker-image://docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a6
24ee31d03372cc1d138a3126 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d
03372cc1d138a3126 [0.01s]
=> resolve image config for docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a
01e5a861a624ee31d03372cc1d138a3126 [0.01s]
=> banana ERROR [0.20s]
start: exec: "banana": executable file not found in $PATH
!!! banana
2: [0.14s] start: exec: "banana": executable file not found in $PATH
error! call trace (oldest first):
┆ <fs>/literate-9:1:0..1:49
1 │ ((start (from (linux/alpine) ($ banana)) raiser))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
resolve failed: exit code: 1
run summary:
=> docker-image://docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861
a624ee31d03372cc1d138a3126
=> banana [0.20s]
start: exec: "banana": executable file not found in $PATH
ERROR: exit code: 1
for more information, refer to the full output above
((start (from (linux/alpine) ($ echo)) raiser))
stderr: 7 lines
=> docker-image://docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a6
24ee31d03372cc1d138a3126 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d
03372cc1d138a3126 [0.01s]
=> resolve image config for docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a
01e5a861a624ee31d03372cc1d138a3126 [0.01s]
=> echo CACHED [0.00s]
null
(addr thunk port & fmt) applicative? combiner?
𝄢

returns an address for a port provided by the thunk

𝄢

Takes an optional format argument which defaults to "$host:$port".

(def thunk (-> ($ python -m http.server) (with-port :http 8080)))
thunk
(addr thunk :http)
"python"
{
:args
(
  1. "python"
  2. "-m"
  3. "http.server"
)
}
http
(wait) applicative? combiner?
𝄢

waits for all started thunks to finish

𝄢

Returns an error if any of the thunk handlers error.

(defn echo-server [msg] (start (from (linux/alpine) ($ sleep 1 $msg)) null?))
echo-server
(wait)
null
(read thunk-or-file protocol) applicative? combiner?
𝄢

returns a stream producing values read from a thunk's output or a file's content

(def echo-thunk (from (linux/alpine) ($ echo "42")))
echo-thunk
(next (read echo-thunk :json))
stderr: 8 lines
=> docker-image://docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a6
24ee31d03372cc1d138a3126 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d
03372cc1d138a3126 [0.01s]
=> resolve image config for docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a
01e5a861a624ee31d03372cc1d138a3126 [0.02s]
=> echo 42 [0.19s]
42
42
(def file-thunk (from (linux/alpine) ($ sh -c "echo 42 > file")))
file-thunk
(next (read file-thunk/file :json))
stderr: 7 lines
=> docker-image://docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a6
24ee31d03372cc1d138a3126 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d
03372cc1d138a3126 [0.01s]
=> resolve image config for docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a
01e5a861a624ee31d03372cc1d138a3126 [0.01s]
=> sh -c "echo 42 > file" [0.18s]
42
(cache-dir id & mode) applicative? combiner?
𝄢

returns a cache directory with the given identifier and concurrency mode

𝄢

Cache directories may be mounted to thunks. Their content persists across thunk runs.

𝄢

The :shared concurrency mode allows multiple thunks to run concurrently with the same cache.

𝄢

The :locked mode allows only one thunk to run at a time with the same cache.

𝄢

The :private mode creates a new mount for concurrent thunks.

𝄢

The default mode is :shared.

(cache-dir "foo")
<cache: foo>/
(cache-dir "foo" :locked)
<cache: foo>/
(binds? scope sym) applicative? combiner?
𝄢

returns true if the scope has a value bound to the given symbol

(binds? {:x 1} :x)
true
(binds? {} :x)
false
(binds? (current-scope) :binds?)
true
(write src dest) applicative? combiner?
𝄢

writes the source to the destination path

𝄢

Source is any value that can be passed to (read), i.e. a thunk or a path.

𝄢

Writes are atomic. The content will first be written to dest.new and renamed to dest.

(write (from (linux/alpine) ($ echo "Hello, world!")) *dir*/hello)
stderr: 7 lines
=> docker-image://docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a6
24ee31d03372cc1d138a3126 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d
03372cc1d138a3126 [0.01s]
=> resolve image config for docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a
01e5a861a624ee31d03372cc1d138a3126 [0.01s]
=> echo "Hello, world!" CACHED [0.00s]
null
(publish src ref) applicative? combiner?
𝄢

publishes the thunk to a container registry

𝄢

Returns a fully qualified image reference.

(publish (from (linux/golang) ($ go version)) "basslang/publish-demo")
stderr: 29 lines
=> resolve image config for docker.io/library/golang@sha256:403f48633fb5ebd49f9a2b6ad6719f
912df23dae44974a0c9445be331e72ff5e [0.01s]
=> docker-image://docker.io/library/golang@sha256:403f48633fb5ebd49f9a2b6ad6719f912df23dae
44974a0c9445be331e72ff5e CACHED [0.00s]
-> resolve docker.io/library/golang@sha256:403f48633fb5ebd49f9a2b6ad6719f912df23dae44974a0
c9445be331e72ff5e [0.01s]
=> go version [0.56s]
go version go1.20.3 linux/amd64
=> exporting to image ERROR [0.28s]
-> exporting layers [0.03s]
-> exporting manifest sha256:ee2ec49bde927afbf361c4129b13688afab41e5e5909e2e8d6e6b18f135bc
243 [0.00s]
-> exporting config sha256:ddf70c21b2c77339d3f6673112de8241b4cb755089f128912758add24d5e4c6
b [0.00s]
-> pushing layers [0.24s]
!!! exporting to image
error! call trace (oldest first):
┆ <fs>/literate-3:1:0..1:70
1 │ (publish (from (linux/golang) ($ go version)) "basslang/publish-demo")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
failed to solve: failed to push docker.io/basslang/publish-demo:latest: push access denied
, repository does not exist or may require authorization: server message: insufficient_sco
pe: authorization failed
Tip: if this error is too cryptic, please open an issue:
https://github.com/vito/bass/issues/new?labels=cryptic&template=cryptic-error-message.md
(export thunk) applicative? combiner?
𝄢

returns a virtual file containing the thunk as an OCI tarball

𝄢

Note that the file can only be read once. You can either (read) it with the :tar protocol or (write) it to a host path.

(export (from (linux/alpine) ($ echo "Hello, world!")))
<fs>/image.tar
(write (export (from (linux/alpine) ($ echo "Hello, world!"))) *dir*/image.tar)
stderr: 14 lines
=> docker-image://docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a6
24ee31d03372cc1d138a3126 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d
03372cc1d138a3126 [0.01s]
=> echo "Hello, world!" CACHED [0.00s]
=> resolve image config for docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a
01e5a861a624ee31d03372cc1d138a3126 [0.01s]
=> exporting to oci image format [0.01s]
-> exporting layers [0.00s]
-> exporting manifest sha256:8dc66a54dc3c9385d4a1876a6c12dceffa9d4f151c9fbf7407144727900f5
a63 [0.00s]
-> exporting config sha256:6def8e016ef8a4a0d1d69a3bf033ef295e5c6ef804ae7eac0213c5446debc45
b [0.00s]
-> sending tarball [0.01s]
null
(next (read (export (from (linux/alpine) ($ echo "Hello, world!"))) :tar))
stderr: 14 lines
=> docker-image://docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a6
24ee31d03372cc1d138a3126 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d
03372cc1d138a3126 [0.01s]
=> echo "Hello, world!" CACHED [0.00s]
=> resolve image config for docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a
01e5a861a624ee31d03372cc1d138a3126 [0.01s]
=> exporting to oci image format [0.00s]
-> exporting layers [0.00s]
-> exporting manifest sha256:8dc66a54dc3c9385d4a1876a6c12dceffa9d4f151c9fbf7407144727900f5
a63 [0.00s]
-> exporting config sha256:6def8e016ef8a4a0d1d69a3bf033ef295e5c6ef804ae7eac0213c5446debc45
b [0.00s]
-> sending tarball [0.00s]
<fs>/blobs/
(only-globs path & globs) applicative? combiner?
𝄢

returns a path with the given globs as the only included files

𝄢

See also (glob).

(only-globs *dir* ./**/*.go)
<host: /tmp/bass-scope105293759>/?[./**/*.go]
(except-globs path & globs) applicative? combiner?
𝄢

returns a path with the given globs excluded

𝄢

See also (glob).

(except-globs *dir* ./.git/)
<host: /tmp/bass-scope2290460252>/![./.git/]
(recall-memo memos thunk binding input) applicative? combiner?
𝄢

fetches the result of a memoized function call

𝄢

Returns null if no result is found.

𝄢

See memo for the higher-level interface.

(store-memo memos thunk binding input result) applicative? combiner?
𝄢

stores the result of a memoized function call

𝄢

See memo for the higher-level interface.

(mask secret name) applicative? combiner?
𝄢

shrouds a string in secrecy

𝄢

Prevents the string from being revealed when the value is displayed.

𝄢

Prevents the string from being revealed in a serialized thunk or thunk path.

𝄢

Does NOT currently prevent the string's value from being displayed in log output; you still have to be careful there.

(mask "super secret" :github-token)
<secret: github-token (12 bytes)>
(list & values) applicative? combiner?
𝄢

construct a list from a sequence of values

(list 1 2 3)
(
  1. 1
  2. 2
  3. 3
)
(list* & args) applicative? combiner?
𝄢

prepend a sequence of values to a list given as the final argument

(list* 1 2 3 [4 5])
(
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
)
(first (f & _)) applicative? combiner?
𝄢

return the first value in a pair

(first [1 2 3])
1
(rest (_ & r)) applicative? combiner?
𝄢

return the second value in a pair, i.e. the rest of a linked list

(rest [1 2 3])
(
  1. 2
  2. 3
)
(length xs) applicative? combiner?
𝄢

return the length of the given list

(length [1 2 3])
3
(defop name formals eformal & body) operative? combiner?
𝄢

construct an operative and bind it to a symbol

𝄢

Returns the bound symbol. Write a comment before or after to provide documentation.

(defop quote [x] _ x) ; returns x
quote
(quote abc)
abc
stderr: 5 lines
--------------------------------------------------
quote operative? combiner?
args: [x]
returns x
null
(fn formals & body) operative? combiner?
𝄢

construct a function

𝄢

Functions are applicative combiners that take a list of arguments.

(def times-7 (fn [x] (* x 7)))
times-7
(times-7 6)
42
(defn name formals & body) operative? combiner?
𝄢

construct a function and bind it to a symbol

𝄢

Returns the bound symbol. Write a comment before or after to provide documentation.

(defn times-7 [x] (* x 7)) ; multiplies by 7
times-7
(times-7 6)
42
(doc times-7)
stderr: 5 lines
--------------------------------------------------
times-7 applicative? combiner?
args: [x]
multiplies by 7
null
(second (_ x & _)) applicative? combiner?
𝄢

return the second member of a linked list

(second [1 2 3])
2
(third (_ _ x & _)) applicative? combiner?
𝄢

return third member of a linked list

(third [1 2 3])
3
(current-scope) operative? combiner?
𝄢

returns the scope of the caller

{
:*memos* <host: .>/bass.lock
{
:*dir* <host: /tmp/bass-scope2828402187>/
:*env*
{
}
:*stdin* <source: empty>
:*stdout* <sink: empty>
:main (wrap <builtin: (main)>)
<scope: ground>
}
}
(eval [current-scope] {:a 1})
{
:a 1
}
(quote form) operative? combiner?
𝄢

returns the unevaluated form

(quote abc)
abc
(map f xs) applicative? combiner?
𝄢

returns a list containing the result of applying f to each member of xs

(map (fn [x] (* x 7)) [5 6 7])
(
  1. 35
  2. 42
  3. 49
)
(map-pairs f ps) applicative? combiner?
𝄢

calls a function with alternating pairs in a flat list (i.e. with pairs ungrouped)

𝄢

Takes 2-arity function and a flat pair sequence. Walks the sequence and calls f with 2 values at a time.

𝄢

Raises an error if the list has uneven length.

(map-pairs cons [:a 1 :b 2 :c 3])
(
  1. (a & 1)
  2. (b & 2)
  3. (c & 3)
)
(let bindings & body) operative? combiner?
𝄢

binds values in a child scope

𝄢

Takes a list alternating bindings and their values. Creates a child scope, and binds and evaluates each value in sequence. Later bindings may to refer to earlier bindings.

𝄢

Returns the result of evaluating the body in the child scope.

(let [x 6 y 7] (* 6 7))
42
(import source & symbols) operative? combiner?
𝄢

binds symbols in the current scope to their values from the source scope

(import {:x 6 :y 7} x)
(
  1. x
)
x ; y is not bound
6
(provide symbols & body) operative? combiner?
𝄢

provide bindings to the current scope from a nested scope

𝄢

Allows for modularity in code, selectively providing bindings while encapsulating bindings that they use.

(provide [y] (def x 6) (def y 7))
(
  1. y
)
y ; x is not bound
7
(foldr f z xs) applicative? combiner?
𝄢

reduces xs, rightmost values first, with initial value z

(foldr cons [4 5] [1 2 3])
(
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
)
(foldl f z xs) applicative? combiner?
𝄢

reduces xs, leftmost values first, with initial value z

(foldl conj [4 5] [1 2 3])
(
  1. 4
  2. 5
  3. 1
  4. 2
  5. 3
)
(concat & xss) applicative? combiner?
𝄢

joins all given lists into one list

(concat [1] [2 3] [4 5 6])
(
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
)
(append & xss)deprecated applicative? combiner?
𝄢

Use concat instead.

𝄢

joins all given lists into one list

(concat [1] [2 3] [4 5 6])
(
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
)
(filter predicate xs) applicative? combiner?
𝄢

returns only values from xs which satisfy the predicate

(filter symbol? [:abc 123 :def "456"])
(
  1. abc
  2. def
)
(conj xs y & ys) applicative? combiner?
𝄢

conjoins values onto the end of a list

(conj [123] 4 5 6)
(
  1. 123
  2. 4
  3. 5
  4. 6
)
(list->scope kwargs) applicative? combiner?
𝄢

constructs an object from a list of flat keyword/value pairs

(list->scope [:a 1 :b 2 :c 3])
{
:a 1
:b 2
:c 3
}
(merge & scopes) applicative? combiner?
𝄢

returns a scope containing the union of the given scopes

𝄢

Constructs a scope with all of the given scopes as parents, in reverse order.

(merge {:a 1 :b 2} {:c 3} {:b :two})
{
{
:b two
}
{
:c 3
}
{
:a 1
:b 2
}
}
(module bindings & body) operative? combiner?
𝄢

returns a scope with only the specified bindings from a child scope

(def mod (module [foo] (def bar 6) (defn foo [n] (* n bar))))
mod
mod
{
:foo (fn [n] (* n bar))
}
(mod:foo 7)
42
(use & thunks) operative? combiner?
𝄢

loads each thunk and binds it as the name from the thunk's command path

(use (.strings) (.time))
(
  1. strings
  2. time
)
(strings:upper-case "hallelujah")
"HALLELUJAH"
(time:weekly)
"2023-04-17T00:00:00Z"
(cond & clauses) operative? combiner?
𝄢

if-then-else, but with many clauses

𝄢

Takes a flat pair sequence alternating tests to evaluate and an expression to evaluate if the test returns a truthy value.

𝄢

Returns the result of the evaluated branch, or null if no tests were true.

𝄢

By convention, :else is used as the final catch-all test, though any truthy value works.

(cond false :a false :b :else :c)
c
(cond true :a false :b :else :c)
a
(or & conds) operative? combiner?
𝄢

returns the first truthy value returned by evaluating conds

𝄢

Short-circuits when it encounters a truthy value.

𝄢

Returns false if no values are given.

(or false null :yep)
yep
(or)
false
(and & conds) operative? combiner?
𝄢

returns a truthy value if none of the conds return a falsy value

𝄢

Short-circuits when it encounters a falsy value.

𝄢

Returns true if no values are given.

(or false null :yep)
yep
(apply appv arg & opt) applicative? combiner?
𝄢

call an applicative's underlying operative with a list of arguments

𝄢

A scope may be provided as the third argument. If not specified, the operative will be called in a new empty scope.

𝄢

Used to call an applicative with pre-evaluated arguments, skipping the normal evaluation the applicative would perform prior to calling the underlying operative.

(apply * [1 2 3])
6
(-> x f & fs) operative? combiner?
𝄢

passes a value through a series of function calls

𝄢

Given an input value and a series of functions, calls the first function with the input value, passing the output to the second function, and so on, returning the final value. Typically used to flatten a deeply nested function call to make it easier to read.

𝄢

Functions are either a single form (i.e. a symbol) or a pair. A single form is called with the input value as the only argument. A pair is called with the input value prepended to the rest of the pair, i.e. inserted as the first argument.

(-> (.boom) insecure! (with-env {:BAM "hi"}))
.boom
{
:insecure true
:args
(
  1. .boom
)
:env
{
:BAM "hi"
}
}
(-> 6 (* 7) (- 2) (quot 4))
10
(case v & bs) operative? combiner?
𝄢

evaluates the branch that successfully binds the given value

𝄢

Bindings are set in an child scope.

(case [] [] :empty [x] :one _ :more)
empty
(case [1] [] :empty [x] :one _ :more)
one
(case [1 2] [] :empty [x] :one _ :more)
more
(id x) applicative? combiner?
𝄢

identity function; returns its argument

(id 42)
42
(id id)
(fn [x] x)
((id id) id)
(fn [x] x)
(((id id) id) id)
(fn [x] x)
(always x) applicative? combiner?
𝄢

returns a function that returns x for any value

((always 42) :never)
42
(((always always) :never) :never)
(fn [_] x)
((((always always) :never) :unless?) :never)
unless?
(vals scope) applicative? combiner?
𝄢

collects the values from a scope

(vals {:a 1 :b 2})
(
  1. 1
  2. 2
)
(keys scope) applicative? combiner?
𝄢

collects the bindings from a scope

(keys {:a 1 :b 2})
(
  1. a
  2. b
)
(memo memos thunk binding) applicative? combiner?
𝄢

memo(ize)s a function

𝄢

Returns a function equivalent to the binding from the loaded thunk which caches its results to/from memos, a path to a file on the host (read-write) or from a thunk (read-only).

𝄢

This is a utility for caching dependency version resolution, such as image tags and git refs. It is technically the only way to perform writes against the host filesystem.

𝄢

The intended practice is to commit memos into source control to facilitate reproducible builds.

(def memos *dir*/bass.lock)
memos
(def upper-cache (memo memos (.strings) :upper-case))
upper-cache
(upper-cache "hello")
"HELLO"
(run (from (linux/alpine) ($ cat $memos)))
stderr: 38 lines
=> docker-image://docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a6
24ee31d03372cc1d138a3126 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d
03372cc1d138a3126 [0.01s]
=> resolve image config for docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a
01e5a861a624ee31d03372cc1d138a3126 [0.02s]
=> upload <host: /tmp/bass-scope1327887344>/bass.lock [0.01s]
-> transferring /tmp/bass-scope1327887344: 423B [0.00s]
=> copy /bass.lock /bass.lock [0.01s]
=> cat <host: /tmp/bass-scope1327887344>/bass.lock [0.19s]
memos: {
module: {
args: {
command_path: {
name: "strings"
}
}
}
calls: {
binding: "upper-case"
results: {
input: {
array: {
values: {
string: {
value: "hello"
}
}
}
}
output: {
string: {
value: "HELLO"
}
}
}
}
}
null
(curryfn args & body) operative? combiner?
𝄢

returns a fn which accepts args one value at a time

(succeeds? thunk) applicative? combiner?
𝄢

returns true if the thunk successfully runs (i.e. exit code 0)

𝄢

returns false if it fails (i.e. exit code nonzero)

𝄢

Used for running a thunk as a conditional instead of erroring when it fails.

(succeeds? (from (linux/alpine) (.false)))
stderr: 8 lines
=> docker-image://docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a6
24ee31d03372cc1d138a3126 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d
03372cc1d138a3126 [0.01s]
=> resolve image config for docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a
01e5a861a624ee31d03372cc1d138a3126 [0.01s]
=> .false ERROR [0.17s]
!!! .false
false
(succeeds? (from (linux/alpine) (.true)))
stderr: 7 lines
=> docker-image://docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a6
24ee31d03372cc1d138a3126 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d
03372cc1d138a3126 [0.01s]
=> resolve image config for docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a
01e5a861a624ee31d03372cc1d138a3126 [0.03s]
=> .true [0.23s]
true
(run thunk) applicative? combiner?
𝄢

runs a thunk

𝄢

Raises an error if the thunk's command fails (i.e. exit code 0)

𝄢

Returns null.

(run (from (linux/alpine) ($ echo "Hello, world!")))
stderr: 7 lines
=> docker-image://docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a6
24ee31d03372cc1d138a3126 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d
03372cc1d138a3126 [0.01s]
=> echo "Hello, world!" CACHED [0.00s]
=> resolve image config for docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a
01e5a861a624ee31d03372cc1d138a3126 [0.01s]
null
(when test & body) operative? combiner?
𝄢

evaluates the body if test returns true

𝄢

Returns the body's result, or null if the test is false.

(when true (def x :a) (log "hello") (log "world") x)
stderr: 2 lines
info hello
info world
a
x
a
(when false (def x :b))
null
x
a
(assert predicate & args) operative? combiner?
𝄢

applies the predicate to its arguments and errors if it returns false

𝄢

By convention, the expected value should be passed as the first argument.

(assert = 4 (+ 2 2))
null
(assert = 5 (+ 2 2))
stderr: 7 lines
error! call trace (oldest first):
┆ <fs>/literate-4:1:0..1:20
1 │ (assert = 5 (+ 2 2))
^^^^^^^^^^^^^^^^^^^^
assertion failed: (= 5 4)
(refute predicate & args) operative? combiner?
𝄢

applies the predicate to its arguments and errors if it returns true

𝄢

By convention, the expected value should be passed as the first argument.

(refute = 4 (+ 2 2))
stderr: 7 lines
error! call trace (oldest first):
┆ <fs>/literate-4:1:0..1:20
1 │ (refute = 4 (+ 2 2))
^^^^^^^^^^^^^^^^^^^^
refutation failed: (= 4 4)
(refute = 5 (+ 2 2))
null
(last source & default) applicative? combiner?
𝄢

returns the last value from the source

𝄢

As with next, a default may be provided to be returned when the source is empty. If not provided, an error will be raised if the source is empty.

(last (list->source [1 2 3]))
3
(each source f) applicative? combiner?
𝄢

calls f for every value read from the source

𝄢

Returns null.

(each (list->source [1 2 3]) log)
stderr: 3 lines
info 1
info 2
info 3
null
(for bindings & body) operative? combiner?
𝄢

loops over values from sources

𝄢

Takes a list alternating bindings and their sources, similar to let. Reads values across all sources and evaluates the body for each set of values as they are read with next.

𝄢

Returns null when the source reaches its end.

(def evens (list->source [0 2 4]))
evens
(def odds (list->source [1 3 5]))
odds
(for [a evens b odds] (log "got" :a a :b b))
stderr: 3 lines
info got {"a": 0, "b": 1}
info got {"a": 2, "b": 3}
info got {"a": 4, "b": 5}
null
(take n source) applicative? combiner?
𝄢

reads the next n values from the source into a list

(take 2 (list->source [1 2 3]))
(
  1. 1
  2. 2
)
(collect f source) applicative? combiner?
𝄢

returns (f value) for every value read from the source

(collect (fn [n] (+ n 1)) (list->source [1 2 3]))
(
  1. 2
  2. 3
  3. 4
)
(take-all source) applicative? combiner?
𝄢

reads all values from the source into a list

(take-all (list->source [1 2 3]))
(
  1. 1
  2. 2
  3. 3
)
(insecure! thunk) applicative? combiner?
𝄢

sets the :insecure field of the thunk to true

(insecure! (.boom))
.boom
{
:insecure true
:args
(
  1. .boom
)
}
($ & args) operative? combiner?
𝄢

constructs a thunk with args

𝄢

Symbol arguments are automatically converted to strings. Symbols beginning with $ are resolved to their binding with the leading $ removed.

($ sh -c "echo Hello, world!")
"sh"
{
:args
(
  1. "sh"
  2. "-c"
  3. "echo Hello, world!"
)
}
($$ & args) operative? combiner?
𝄢

constructs a thunk with args passed to its entrypoint

(from image & thunks) applicative? combiner?
𝄢

chain a sequence of thunks starting from an initial image

(from (linux/alpine) ($ echo "Hello, world!"))
"echo"
{
:image
<no command>
{
:image
{
:repository "alpine"
:platform
{
:architecture "amd64"
:os "linux"
}
:tag "latest"
:digest "sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d03372cc1d138a3126"
}
}
:args
(
  1. "echo"
  2. "Hello, world!"
)
}
(cd dir thunk & thunks) applicative? combiner?
𝄢

chain a sequence of thunks with a given working directory

𝄢

Shorthand for using with-mount to mount ./ on the first thunk and chaining the rest using from. The working directory will propagate between them.

𝄢

Typically used within an outer from which sets the first thunk's image in order to join it into the chain.

(from (linux/alpine) (cd *dir* ($ find ./)))
"find"
{
:image
<no command>
{
:image
{
:repository "alpine"
:platform
{
:architecture "amd64"
:os "linux"
}
:tag "latest"
:digest "sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d03372cc1d138a3126"
}
}
:args
(
  1. "find"
  2. ./
)
:mounts
(
  1. {
    :source <host: /tmp/bass-scope327685189>/
    :target ./
    }
)
}
(wrap-cmd thunk & args) applicative? combiner?
𝄢

prepend a command + args to a thunk's command + args

𝄢

Replaces the thunk's run path sets its args to and prepend-args prepended to the original cmd + args.

(wrap-cmd ($ go test "./...") .strace "-f")
.strace
{
:args
(
  1. .strace
  2. "-f"
  3. "go"
  4. "test"
  5. "./..."
)
}
(linux & args) operative? combiner?
𝄢

a path root for resolving Linux images

𝄢

Memoizes image resolution into the caller's *memos*, if set.

(linux/ubuntu)
<no command>
{
:image
{
:repository "ubuntu"
:platform
{
:architecture "amd64"
:os "linux"
}
:tag "latest"
:digest "sha256:67211c14fa74f070d27cc59d69a7fa9aeff8e28ea118ef3babc295a0428a6d21"
}
}
(linux/ubuntu :18.04)
<no command>
{
:image
{
:repository "ubuntu"
:platform
{
:architecture "amd64"
:os "linux"
}
:tag "18.04"
:digest "sha256:8aa9c2798215f99544d1ce7439ea9c3a6dfd82de607da1cec3a8a2fae005931b"
}
}
(linux/docker.io/library/ubuntu :18.04)
<no command>
{
:image
{
:repository "docker.io/library/ubuntu"
:platform
{
:architecture "amd64"
:os "linux"
}
:tag "18.04"
:digest "sha256:8aa9c2798215f99544d1ce7439ea9c3a6dfd82de607da1cec3a8a2fae005931b"
}
}
(docker-build context platform & opts) applicative? combiner?
𝄢

returns a thunk built from a Dockerfile in the context directory

𝄢

Additional parameters may be passed as opts:

𝄢

:target specifies a target stage to build.

𝄢

:args specifies build args from a scope with string values.

𝄢

:dockerfile specifies a path to a Dockerfile within the context directory.

(docker-build *dir* {:os "linux"})
<no command>
{
:image
{
:platform
{
:architecture "amd64"
:os "linux"
}
:docker_build <host: /tmp/bass-scope691957677>/
}
}
(docker-build *dir* {:os "linux"} :target "foo")
<no command>
{
:image
{
:platform
{
:architecture "amd64"
:os "linux"
}
:docker_build <host: /tmp/bass-scope691957677>/
:target "foo"
}
}
(docker-build *dir* {:os "linux"} :dockerfile ./Dockerfile.foo)
<no command>
{
:image
{
:platform
{
:architecture "amd64"
:os "linux"
}
:docker_build <host: /tmp/bass-scope691957677>/
:dockerfile ./Dockerfile.foo
}
}
(docker-build *dir* {:os "linux"} :args {:FOO "bar"})
<no command>
{
:image
{
:platform
{
:architecture "amd64"
:os "linux"
}
:docker_build <host: /tmp/bass-scope691957677>/
:args
{
:FOO "bar"
}
}
}
(oci-load file platform & opts) applicative? combiner?
𝄢

returns a thunk loaded from an OCI image tarball

𝄢

Additional parameters may be passed as opts:

𝄢

:tag specifies a tag to load.

(def build ($ nix build .))
build
(oci-load build/result {:os "linux"})
<no command>
{
:image
{
:file
"nix"
{
:args
(
  1. "nix"
  2. "build"
  3. "."
)
}
./result
:platform
{
:architecture "amd64"
:os "linux"
}
}
}
(glob path & patterns) operative? combiner?
𝄢

returns a path narrowed to paths matching the given glob patterns

𝄢

To exclude a glob, place a ! argument ahead of it. Only the path immediately after it will be excluded.

(glob *dir* ./*.nix)
<host: /tmp/bass-scope3402046316>/?[./*.nix]
(glob *dir* ./*.nix ! ./foo.nix)
<host: /tmp/bass-scope3402046316>/?[./*.nix]![./foo.nix]
(glob *dir* ./*.nix ! ./foo.nix ./**/*.go ! ./main.go)
<host: /tmp/bass-scope3402046316>/?[./*.nix ./**/*.go]![./foo.nix ./main.go]
(mkfile name content) applicative? combiner?
𝄢

returns an in-memory file with the given content

(mkfile ./hi "hello world!")
<fs>/hi
(mkfile ./hey "hello world!")
<fs>/hey
(path-base path) applicative? combiner?
𝄢

returns the path-name converted into a file path

(path-base ./foo/bar)
./bar
(path-base .cmd)
./cmd
(path-base ./foo/dir/)
./dir
(not x) applicative? combiner?
𝄢

negates the given bool-ish value

𝄢

Returns true for false and null. Returns false otherwise.

(not true)
false
(not null)
true
(not false)
true

script bindings

Bass modules always run as commands. Either a user runs the script with the bass command, or another Bass module runs it as a thunk.

When a module is run as a script, the values reflect the system values available to the bass command, and main is called with the arguments passed to bass.

When a module is run as a thunk, the values reflect the values set in the thunk, and main is called with the thunk's args.

index

*/-

*dir* current working directory
*env* environment variables
*stdin* standard input stream
*stdout* standard output sink

m

main script entrypoint

bindings

*dir* applicative? combiner? path?
𝄢

current working directory

𝄢

This value is always set to the directory containing the script being run.

𝄢

It can and should be used to load sibling/child paths, e.g. *dir*/foo to load the 'foo.bass' file in the same directory as the current file.

<host: .>/
*env* scope?
𝄢

environment variables

𝄢

System environment variables are only available to the entrypoint script. To propagate them further they must be explicitly passed to thunks using with-env.

𝄢

System environment variables are unset from the physical OS process as part of initialization to ensure they cannot be leaked.

{
:SECRET_TOKEN "im a spooky value"
}
*stdin* source?
𝄢

standard input stream

𝄢

Values read from *stdin* will be parsed from the process's stdin as a JSON stream.

<source: empty>
*stdout* sink?
𝄢

standard output sink

𝄢

Values emitted by a script to *stdout* will be encoded as a JSON stream to the process's stdout.

<sink: empty>
(main) applicative? combiner?
𝄢

script entrypoint

𝄢

The [script:main] function is called with any provided command-line args when running a Bass script.

𝄢

Scripts should define it to capture system arguments and run the script's desired effects.

𝄢

Putting effects in [script:main] instead of running them at the toplevel makes the Bass language server happier.

.strings module

Simple functions for manipulating UTF-8 encoded strings.

(load (.strings))
<scope: {{thunk KP8HLM3BB1O7E: .strings}}>

index

i

includes? returns true if str includes substr

j

join joins a list of strings together with delim in between

l

length returns the string's length

s

split split a string on a delimiter

u

upper-case capitalizes all letters in the string

bindings

(join delim strs) applicative? combiner?
𝄢

joins a list of strings together with delim in between

(use (.strings))
(
  1. strings
)
(strings:join ", " ["Hello", "World"])
"Hello, World"
(split delim str) applicative? combiner?
𝄢

split a string on a delimiter

(use (.strings))
(
  1. strings
)
(strings:split "a=b" "=")
(
  1. "a"
  2. "b"
)
(upper-case str) applicative? combiner?
𝄢

capitalizes all letters in the string

(use (.strings))
(
  1. strings
)
(strings:upper-case "hallelujah")
"HALLELUJAH"
(includes? str substr) applicative? combiner?
𝄢

returns true if str includes substr

(use (.strings))
(
  1. strings
)
(strings:includes? "team" "i")
false
(strings:includes? "racecar" "car")
true
(length str) applicative? combiner?
𝄢

returns the string's length

(use (.strings))
(
  1. strings
)
(strings:length "hello")
5

.git module

Bare essentials for fetching Git repositories, using the git CLI from an image passed on stdin.

This module is limited to functions necessary for fetching other Bass scripts, i.e. bootstrapping.

(load (.git (linux/alpine/git)))
<scope: {{thunk D7833OFUIKG8A: .git}}>

index

c

checkout returns the repo checked out to the given ref

g

github a path root for repos hosted at github.com

l

ls-remote resolves a ref to a sha at the remote repo

p

path returns a path root for repos at the given base URL

bindings

(ls-remote repo ref & timestamp) applicative? combiner?
𝄢

resolves a ref to a sha at the remote repo

𝄢

Does not cache. Used to resolve the ref at a point in time.

(use (.git (linux/alpine/git)))
(
  1. git
)
(git:ls-remote "https://github.com/vito/bass" "main")
stderr: 8 lines
=> resolve image config for docker.io/alpine/git@sha256:66b210a97bc07bfd4019826bcd13a488b3
71a6cbe2630a4b37d23275658bd3f2 [0.02s]
=> docker-image://docker.io/alpine/git@sha256:66b210a97bc07bfd4019826bcd13a488b371a6cbe263
0a4b37d23275658bd3f2 CACHED [0.00s]
-> resolve docker.io/alpine/git@sha256:66b210a97bc07bfd4019826bcd13a488b371a6cbe2630a4b37d
23275658bd3f2 [0.01s]
=> git ls-remote https://github.com/vito/bass main [1.54s]
0537fa0323f1b12a43f0ab7dc90d2d00894d9c63 refs/heads/main
"0537fa0323f1b12a43f0ab7dc90d2d00894d9c63"
(checkout repo ref) applicative? combiner?
𝄢

returns the repo checked out to the given ref

𝄢

The thunk for cloning the repo is labeled with the given ref. If the ref refers to a branch, you may want to resolve it to a sha first with [git:ls-remote] so that it's not cached forever.

𝄢

Submodules are always initialized.

(use (.git (linux/alpine/git)))
(
  1. git
)
(git:checkout "https://github.com/vito/bass" "ea8cae6d4c871cb14448d7254843d86dbab8505f")
"git"
{
:image
"git"
{
:image
"git"
{
:image
"git"
{
:image
<no command>
{
:image
{
:repository "alpine/git"
:platform
{
:architecture "amd64"
:os "linux"
}
:tag "latest"
:digest "sha256:66b210a97bc07bfd4019826bcd13a488b371a6cbe2630a4b37d23275658bd3f2"
}
}
:args
(
  1. "git"
  2. "clone"
  3. "https://github.com/vito/bass"
  4. ./
)
}
:args
(
  1. "git"
  2. "fetch"
  3. "origin"
  4. "ea8cae6d4c871cb14448d7254843d86dbab8505f"
)
}
:args
(
  1. "git"
  2. "checkout"
  3. "ea8cae6d4c871cb14448d7254843d86dbab8505f"
)
}
:args
(
  1. "git"
  2. "submodule"
  3. "update"
  4. "--init"
  5. "--recursive"
)
}
./
(path root memos) applicative? combiner?
𝄢

returns a path root for repos at the given base URL

𝄢

Please omit the trailing slash. (TODO: would be nice to just strip it or somehow make it a non-issue.)

(use (.git (linux/alpine/git)))
(
  1. git
)
(def gh (git:path "https://github.com" null))
gh
gh/vito/bass/ref/main/
stderr: 8 lines
=> resolve image config for docker.io/alpine/git@sha256:66b210a97bc07bfd4019826bcd13a488b3
71a6cbe2630a4b37d23275658bd3f2 [0.02s]
=> docker-image://docker.io/alpine/git@sha256:66b210a97bc07bfd4019826bcd13a488b371a6cbe263
0a4b37d23275658bd3f2 CACHED [0.00s]
-> resolve docker.io/alpine/git@sha256:66b210a97bc07bfd4019826bcd13a488b371a6cbe2630a4b37d
23275658bd3f2 [0.01s]
=> git ls-remote https://github.com/vito/bass main [0.51s]
0537fa0323f1b12a43f0ab7dc90d2d00894d9c63 refs/heads/main
"git"
{
:image
"git"
{
:image
"git"
{
:image
"git"
{
:image
<no command>
{
:image
{
:repository "alpine/git"
:platform
{
:architecture "amd64"
:os "linux"
}
:tag "latest"
:digest "sha256:66b210a97bc07bfd4019826bcd13a488b371a6cbe2630a4b37d23275658bd3f2"
}
}
:args
(
  1. "git"
  2. "clone"
  3. "https://github.com/vito/bass"
  4. ./
)
}
:args
(
  1. "git"
  2. "fetch"
  3. "origin"
  4. "0537fa0323f1b12a43f0ab7dc90d2d00894d9c63"
)
}
:args
(
  1. "git"
  2. "checkout"
  3. "0537fa0323f1b12a43f0ab7dc90d2d00894d9c63"
)
}
:args
(
  1. "git"
  2. "submodule"
  3. "update"
  4. "--init"
  5. "--recursive"
)
}
./
(github & args) operative? combiner?
𝄢

a path root for repos hosted at github.com

𝄢

Memoizes ref resolution into the caller's *memos*, if set.

(use (.git (linux/alpine/git)))
(
  1. git
)
git:github/vito/bass/sha/ea8cae6d4c871cb14448d7254843d86dbab8505f/
"git"
{
:image
"git"
{
:image
"git"
{
:image
"git"
{
:image
<no command>
{
:image
{
:repository "alpine/git"
:platform
{
:architecture "amd64"
:os "linux"
}
:tag "latest"
:digest "sha256:66b210a97bc07bfd4019826bcd13a488b371a6cbe2630a4b37d23275658bd3f2"
}
}
:args
(
  1. "git"
  2. "clone"
  3. "https://github.com/vito/bass"
  4. ./
)
}
:args
(
  1. "git"
  2. "fetch"
  3. "origin"
  4. "ea8cae6d4c871cb14448d7254843d86dbab8505f"
)
}
:args
(
  1. "git"
  2. "checkout"
  3. "ea8cae6d4c871cb14448d7254843d86dbab8505f"
)
}
:args
(
  1. "git"
  2. "submodule"
  3. "update"
  4. "--init"
  5. "--recursive"
)
}
./
(git:github/vito/bass/sha/ "ea8cae6d4c871cb14448d7254843d86dbab8505f")
"git"