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
* 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
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 list
applicative? returns true if the value is an applicative
apply call an applicative's underlying operative with a list of arguments
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 corresponding to the string identifier
case evaluates the branch that successfully binds the given value
cd chain a sequence of thunks with a given working directory
combiner? returns true if the value is a combiner
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
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

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

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

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

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
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
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
third return third member of a linked list
thunk-args returns the thunk's args
thunk-cmd returns the thunk's command
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

when evaluates the body if test returns true
with-args returns thunk with args set to args
with-cmd returns thunk with cmd set to cmd
with-dir returns thunk with the working directory set to dir
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-stdin returns thunk with stdin set to vals
wrap construct an applicative from a combiner (typically an operative)
wrap-cmd prepend a command + args to a thunk's command + args

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: 10 lines
=> resolve image config for docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23
f84eeaf7e457f36f271ab1acc53015037c [0.01s]
=> mkfile /file CACHED [0.00s]
=> docker-image://docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e4
57f36f271ab1acc53015037c CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f2
71ab1acc53015037c [0.01s]
=> cat <fs>/file [0.17s]
=> exporting to client [0.00s]
-> copying files 29B [0.00s]
"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)
"2022-07-24T15:31: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 218
}
(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
{
:cmd .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))
"hpdBgidbxFI="
(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))
"hpdBgidbxFI="
(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
{
:platform
{
:os "linux"
}
:repository "golang"
:tag "latest"
:digest "sha256:a452d6273ad03a47c2f29b898d6bb57630e77baf839651ef77d03e4e049c5bf3"
}
:cmd .go
:args
(
  1. "test"
  2. ./...
)
}
(from (linux/golang) ($ go test ./...))
.go
{
:image
{
:platform
{
:os "linux"
}
:repository "golang"
:tag "latest"
:digest "sha256:a452d6273ad03a47c2f29b898d6bb57630e77baf839651ef77d03e4e049c5bf3"
}
:cmd .go
:args
(
  1. "test"
  2. ./...
)
}
(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
{
:cmd .tests
:dir ./src/
}
(with-args thunk args) applicative? combiner?
𝄢

returns thunk with args set to args

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

returns thunk with cmd set to cmd

(let [inner (with-args (.go) ["build"])] (with-args (with-cmd inner ./wrapped) (cons (thunk-cmd inner) (thunk-args inner))))
./wrapped
{
:cmd ./wrapped
:args
(
  1. .go
  2. "build"
)
}
(with-stdin thunk vals) applicative? combiner?
𝄢

returns thunk with stdin set to vals

(with-stdin ($ jq ".a") [{:a 1} {:a 2}])
.jq
{
:cmd .jq
:args
(
  1. ".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") {:SECRET "shh"})
.jq
{
:cmd .jq
:args
(
  1. ".a"
)
:env
{
:SECRET "shh"
}
}
(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
:cmd .boom
}
(= (.boom) (with-insecure (.boom) false))
true
(with-label thunk name val) applicative? combiner?
𝄢

returns thunk with the label set to val

𝄢

Labels are typically used to control caching. Two thunks that differ only in labels will evaluate separately and produce independent results.

(with-label ($ sleep 10) :at (now 10))
.sleep
{
:cmd .sleep
:args
(
  1. 10
)
:labels
{
:at "2022-07-24T15:31:50Z"
}
}
(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
{
:cmd .find
:args
(
  1. ./inputs/
)
:mounts
(
  1. {
    :source <host: /tmp/bass-scope3750449318/inputs>
    :target ./inputs/
    }
)
}
(thunk-cmd thunk) applicative? combiner?
𝄢

returns the thunk's command

(thunk-cmd (.foo))
.foo
(thunk-cmd (./foo))
./foo
(thunk-args thunk) applicative? combiner?
𝄢

returns the thunk's args

(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 u_ap1YarZN8=: (.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.45s]
{
:platform
{
:os "linux"
}
:repository "golang"
:tag "latest"
:digest "sha256:9349ed889adb906efa5ebc06485fe1b6a12fb265a01c9266a137bb1352565560"
}
(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 n9YWDbJ6LbA=: (.banana)>)>)
((start (from (linux/alpine) ($ banana)) null?))
stderr: 10 lines
=> docker-image://docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e4
57f36f271ab1acc53015037c CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f2
71ab1acc53015037c [0.01s]
=> resolve image config for docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23
f84eeaf7e457f36f271ab1acc53015037c [0.01s]
=> banana ERROR [0.17s]
run error: exec: "banana": executable file not found in $PATH
!!! banana
5: [0.15s] run error: exec: "banana": executable file not found in $PATH
false
((start (from (linux/alpine) ($ echo)) null?))
stderr: 8 lines
=> resolve image config for docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23
f84eeaf7e457f36f271ab1acc53015037c [0.01s]
=> docker-image://docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e4
57f36f271ab1acc53015037c CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f2
71ab1acc53015037c [0.01s]
=> echo [0.10s]
true
(defn raiser [err] (and err (err)))
raiser
((start (from (linux/alpine) ($ banana)) raiser))
stderr: 29 lines
=> resolve image config for docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23
f84eeaf7e457f36f271ab1acc53015037c [0.01s]
=> docker-image://docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e4
57f36f271ab1acc53015037c CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f2
71ab1acc53015037c [0.01s]
=> banana ERROR [0.12s]
run error: exec: "banana": executable file not found in $PATH
!!! banana
5: [0.10s] run error: 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))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
build failed: exit code: 1
run summary:
=> resolve image config for docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d
23f84eeaf7e457f36f271ab1acc53015037c [0.01s]
=> docker-image://docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7
e457f36f271ab1acc53015037c
=> banana [0.13s]
run error: 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: 8 lines
=> resolve image config for docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23
f84eeaf7e457f36f271ab1acc53015037c [0.01s]
=> docker-image://docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e4
57f36f271ab1acc53015037c CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f2
71ab1acc53015037c [0.01s]
=> echo [0.17s]
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: 9 lines
=> resolve image config for docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23
f84eeaf7e457f36f271ab1acc53015037c [0.01s]
=> docker-image://docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e4
57f36f271ab1acc53015037c CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f2
71ab1acc53015037c [0.01s]
=> echo 42 [0.15s]
=> exporting to client [0.00s]
-> copying files 29B [0.00s]
42
(def file-thunk (from (linux/alpine) ($ sh -c "echo 42 > file")))
file-thunk
(next (read file-thunk/file :json))
stderr: 9 lines
=> resolve image config for docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23
f84eeaf7e457f36f271ab1acc53015037c [0.01s]
=> docker-image://docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e4
57f36f271ab1acc53015037c CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f2
71ab1acc53015037c [0.01s]
=> sh -c "echo 42 > file" [0.14s]
=> exporting to client [0.00s]
-> sending tarball [0.00s]
42
(cache-dir id) applicative? combiner?
𝄢

returns a cache directory corresponding to the string identifier

𝄢

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

(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
(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 (defop) to provide documentation.

(defop quote [x] _ x)
quote
(quote abc)
abc
(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 (defn) to provide documentation.

(defn times-7 [x] (* x 7))
times-7
(times-7 6)
42
(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-scope2642537395>
:*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
)
(append & xss) applicative? combiner?
𝄢

joins all given lists into one list

(append [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

𝄢

That is, (use (.foo)) is equivalent to (def foo (load (.foo))).

(use (.strings) (.time))
(
  1. strings
  2. time
)
(strings:upper-case "hallelujah")
"HALLELUJAH"
(time:weekly)
"2022-07-18T00: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
:cmd .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 (binding (load 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
=> resolve image config for docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23
f84eeaf7e457f36f271ab1acc53015037c [0.01s]
=> docker-image://docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e4
57f36f271ab1acc53015037c CACHED [0.01s]
-> resolve docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f2
71ab1acc53015037c [0.02s]
=> local:///tmp/bass-scope3421689081 [0.04s]
-> transferring /tmp/bass-scope3421689081: 417B [0.00s]
=> copy /bass.lock /bass.lock CACHED [0.00s]
=> cat <host: /tmp/bass-scope3421689081/bass.lock> [0.18s]
memos: {
module: {
cmd: {
command: {
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
=> resolve image config for docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23
f84eeaf7e457f36f271ab1acc53015037c [0.01s]
=> docker-image://docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e4
57f36f271ab1acc53015037c CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f2
71ab1acc53015037c [0.01s]
=> false ERROR [0.10s]
!!! false
false
(succeeds? (from (linux/alpine) (.true)))
stderr: 7 lines
=> resolve image config for docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23
f84eeaf7e457f36f271ab1acc53015037c [0.01s]
=> docker-image://docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e4
57f36f271ab1acc53015037c CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f2
71ab1acc53015037c [0.01s]
=> true [0.11s]
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: 8 lines
=> resolve image config for docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23
f84eeaf7e457f36f271ab1acc53015037c [0.01s]
=> docker-image://docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e4
57f36f271ab1acc53015037c CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f2
71ab1acc53015037c [0.01s]
=> echo "Hello, world!" [0.15s]
Hello, world!
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
(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
)
(insecure! thunk) applicative? combiner?
𝄢

sets the :insecure field of the thunk to true

(insecure! (.boom))
.boom
{
:insecure true
:cmd .boom
}
($ cmd & 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
{
:cmd .sh
:args
(
  1. "-c"
  2. "echo Hello, world!"
)
}
(from image & thunks) applicative? combiner?
𝄢

chain a sequence of thunks starting from an initial image

(from (linux/alpine) ($ echo "Hello, world!"))
.echo
{
:image
{
:platform
{
:os "linux"
}
:repository "alpine"
:tag "latest"
:digest "sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f271ab1acc53015037c"
}
:cmd .echo
:args
(
  1. "Hello, world!"
)
}
(cd dir thunk & thunks) applicative? combiner?
𝄢

chain a sequence of thunks with a given working directory

𝄢

Shorthand for setting (with-mount dir ./) in the first thunk and chaining them with (from).

𝄢

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
{
:platform
{
:os "linux"
}
:repository "alpine"
:tag "latest"
:digest "sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f271ab1acc53015037c"
}
:cmd .find
:args
(
  1. ./
)
:mounts
(
  1. {
    :source <host: /tmp/bass-scope2791896696>
    :target ./
    }
)
}
(wrap-cmd thunk cmd & 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
{
:cmd .strace
:args
(
  1. "-f"
  2. .go
  3. "test"
  4. "./..."
)
}
(linux & args) operative? combiner?
𝄢

a path root for resolving Linux images

𝄢

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

(linux/ubuntu)
{
:platform
{
:os "linux"
}
:repository "ubuntu"
:tag "latest"
:digest "sha256:b6b83d3c331794420340093eb706a6f152d9c1fa51b262d9bf34594887c2c7ac"
}
(linux/ubuntu :18.04)
{
:platform
{
:os "linux"
}
:repository "ubuntu"
:tag "18.04"
:digest "sha256:478caf1bec1afd54a58435ec681c8755883b7eb843a8630091890130b15a79af"
}
(linux/docker.io/library/ubuntu :18.04)
{
:platform
{
:os "linux"
}
:repository "docker.io/library/ubuntu"
:tag "18.04"
:digest "sha256:478caf1bec1afd54a58435ec681c8755883b7eb843a8630091890130b15a79af"
}
(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 (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 (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 u_ap1YarZN8=: (.strings)>>

index

i

includes? returns true if str includes substr

j

join joins a list of strings together with delim in between

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. "="
)
(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

.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 zEqerExaKy0=: (.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: 9 lines
=> resolve image config for docker.io/alpine/git@sha256:23dcd3edfd1d9c7cbb14f7823d07a49347
16cfa4d4dbc402d37ee011c440a685 [0.01s]
=> docker-image://docker.io/alpine/git@sha256:23dcd3edfd1d9c7cbb14f7823d07a4934716cfa4d4db
c402d37ee011c440a685 CACHED [0.00s]
-> resolve docker.io/alpine/git@sha256:23dcd3edfd1d9c7cbb14f7823d07a4934716cfa4d4dbc402d37
ee011c440a685 [0.01s]
=> git ls-remote https://github.com/vito/bass main [0.38s]
=> exporting to client [0.00s]
-> copying files 83B [0.00s]
"6c037a9d134c8b60d61fcd0023494358b94e265f"
(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 (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
{
:platform
{
:os "linux"
}
:repository "alpine/git"
:tag "latest"
:digest "sha256:23dcd3edfd1d9c7cbb14f7823d07a4934716cfa4d4dbc402d37ee011c440a685"
}
:cmd .git
:args
(
  1. "clone"
  2. "https://github.com/vito/bass"
  3. ./
)
}
:cmd .git
:args
(
  1. "fetch"
  2. "origin"
  3. "ea8cae6d4c871cb14448d7254843d86dbab8505f"
)
}
:cmd .git
:args
(
  1. "checkout"
  2. "ea8cae6d4c871cb14448d7254843d86dbab8505f"
)
}
:cmd .git
:args
(
  1. "submodule"
  2. "update"
  3. "--init"
  4. "--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: 9 lines
=> resolve image config for docker.io/alpine/git@sha256:23dcd3edfd1d9c7cbb14f7823d07a49347
16cfa4d4dbc402d37ee011c440a685 [0.01s]
=> docker-image://docker.io/alpine/git@sha256:23dcd3edfd1d9c7cbb14f7823d07a4934716cfa4d4db
c402d37ee011c440a685 CACHED [0.00s]
-> resolve docker.io/alpine/git@sha256:23dcd3edfd1d9c7cbb14f7823d07a4934716cfa4d4dbc402d37
ee011c440a685 [0.01s]
=> git ls-remote https://github.com/vito/bass main [0.45s]
=> exporting to client [0.00s]
-> copying files 83B [0.00s]
.git
{
:image
.git
{
:image
.git
{
:image
.git
{
:image
{
:platform
{
:os "linux"
}
:repository "alpine/git"
:tag "latest"
:digest "sha256:23dcd3edfd1d9c7cbb14f7823d07a4934716cfa4d4dbc402d37ee011c440a685"
}
:cmd .git
:args
(
  1. "clone"
  2. "https://github.com/vito/bass"
  3. ./
)
}
:cmd .git
:args
(
  1. "fetch"
  2. "origin"
  3. "6c037a9d134c8b60d61fcd0023494358b94e265f"
)
}
:cmd .git
:args
(
  1. "checkout"
  2. "6c037a9d134c8b60d61fcd0023494358b94e265f"
)
}
:cmd .git
:args
(
  1. "submodule"
  2. "update"
  3. "--init"
  4. "--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
{
:platform
{
:os "linux"
}
:repository "alpine/git"
:tag "latest"
:digest "sha256:23dcd3edfd1d9c7cbb14f7823d07a4934716cfa4d4dbc402d37ee011c440a685"
}
:cmd .git
:args
(
  1. "clone"
  2. "https://github.com/vito/bass"
  3. ./
)
}
:cmd .git
:args
(
  1. "fetch"
  2. "origin"
  3. "ea8cae6d4c871cb14448d7254843d86dbab8505f"
)
}
:cmd .git
:args
(
  1. "checkout"
  2. "ea8cae6d4c871cb14448d7254843d86dbab8505f"
)
}
:cmd .git
:args
(
  1. "submodule"
  2. "update"
  3. "--init"
  4. "--recursive"
)
}
./
(git:github/vito/bass/sha/ "ea8cae6d4c871cb14448d7254843d86dbab8505f")
.git
{
:image
.git
{
:image
.git
{
:image
.git
{
:image
{
:platform
{
:os "linux"
}
:repository "alpine/git"
:tag "latest"
:digest "sha256:23dcd3edfd1d9c7cbb14f7823d07a4934716cfa4d4dbc402d37ee011c440a685"
}
:cmd .git
:args
(
  1. "clone"
  2. "https://github.com/vito/bass"
  3. ./
)
}
:cmd .git
:args
(
  1. "fetch"
  2. "origin"
  3. "ea8cae6d4c871cb14448d7254843d86dbab8505f"
)
}
:cmd .git
:args
(
  1. "checkout"
  2. "ea8cae6d4c871cb14448d7254843d86dbab8505f"
)
}
:cmd .git
:args
(
  1. "submodule"
  2. "update"
  3. "--init"
  4. "--recursive"
)
}
./
git:github/vito/bass/ref/main/
.git
{
:image
.git
{
:image
.git
{
:image
.git
{
:image
{
:platform
{
:os "linux"
}
:repository "alpine/git"
:tag "latest"
:digest "sha256:23dcd3edfd1d9c7cbb14f7823d07a4934716cfa4d4dbc402d37ee011c440a685"
}
:cmd .git
:args
(
  1. "clone"
  2. "https://github.com/vito/bass"
  3. ./
)
}
:cmd .git
:args
(
  1. "fetch"
  2. "origin"
  3. "8bc8572e8bdb8856451ca8ea16f735b4a0aeb047"
)
}
:cmd .git
:args
(
  1. "checkout"
  2. "8bc8572e8bdb8856451ca8ea16f735b4a0aeb047"
)
}
:cmd .git
:args
(
  1. "submodule"
  2. "update"
  3. "--init"
  4. "--recursive"
)
}
./