GitHub

A small task runner

Commands live in fach.bnml next to your project.

Requires Odin. Config is BNML. The CLI is a thin wrapper around package fach.

fach init
fach run build
fach list
fach get name
fach dep
01

Build

From the repository root:

odin build cmd/fach -out:fach

Put the binary on your PATH, or run it as ./fach.

02

CLI

fach reads fach.bnml from the current directory. Blank lines and whole-line ; comments are ignored. fach init writes a starter file instead of reading one.

Command Needs What it does
fach init — Write a starter fach.bnml. Errors if one already exists.
fach run <name> project, entrypoints Run a public entrypoint.
fach list entrypoints List public entrypoints.
fach get <path> project Print a leaf project value — name, out.dir, and so on.
fach dep deps List deps.

private is optional. run only takes a public name; a public entrypoint can inline a private one with [[name]].

03

Config

Four roots, numbered indent. Descriptions on an entrypoint line show up in fach list; the same pattern on a dep is a hint for fach dep.

0 project
1 name: hello
1 version: 0.1.0

0 deps
1 odin: The Odin compiler

0 entrypoints
1 build: compile the binary
2 cmds
3 odin build . -out:{{name}}
2 regardless
3 echo done

0 private
1 clean
2 cmds
3 rm -rf {{name}}

fach init writes a thinner starter:

; A new fach.bnml
0 project

0 deps

0 entrypoints
04

Entrypoints

Each public or private entrypoint has cmds (required) and optional regardless. regardless always runs after cmds; every line runs; the exit code is still that of cmds.

cmds share one shell, so cd and export persist into later cmds and into regardless.

Command text is the BNML key. If you need a colon in the command, write it as key: value — BNML splits on the first unescaped colon, and fach joins the key and value back with : .

Inside commands

{{path}}
Substitute a leaf project value, quoted for the shell. Dots walk children: {{out.dir}}.
[name]
As a whole command, inline that public entrypoint.
[[name]]
As a whole command, inline a private entrypoint.
[[
A literal [.

[name] and [[name]] only inline when the whole line is that ref. cd src && [build] is not an inline; use two children (cd src, then [build]).

Public and private inlines share a cycle check. Missing values, missing entrypoints, and missing privates are errors.

05

Example

fach init writes a starter file; this example is fuller. Copy it to a project root as fach.bnml, then try fach list, fach dep, fach get name, fach get out.dir, and fach run check.

; Example fach.bnml. `fach init` writes a starter file; this example is fuller.
; Copy into a project root as fach.bnml, or start from `fach init`, then:
;   fach list
;   fach dep
;   fach get name
;   fach get out.dir
;   fach run check

0 project
1 name: hello
1 version: 0.1.0
1 out
2 dir: build

0 deps
1 odin: The Odin compiler
1 sh: POSIX shell

0 entrypoints

1 build: print a fake build
2 cmds
3 echo building {{name}} {{version}} into {{out.dir}}
2 regardless
3 echo done

1 test: print a fake test
2 cmds
3 echo tests
3 echo note: colons are kept

1 check: setup, then test, then build
2 cmds
3 [[setup]]
3 [test]
3 [build]

0 private
1 setup
2 cmds
3 echo private setup for {{name}}
3 echo a literal bracket: [[name]
06

Library

package fach is the library; the CLI is a wrapper around it. Imports are relative to the importing file. Default config path is fach.bnml (CONFIG_FILE). Pass another path to init or load if you want.

Error.kind == .None is success; Error.text is an allocated message.

import fach "path/to/fach"

file, err := fach.load()
if err.kind != .None {
	fmt.eprintln(err.text)
	return
}
defer fach.destroy(&file)

code, err := fach.run(file, "build")
Procedure Returns What it does
init Error Write a starter file (STARTER). Error if it already exists.
load File, Error Read and parse a fach.bnml.
destroy — Free a loaded File.
get string, Error Leaf project value. Needs project.
list_deps []Dep, Error Dep names and hints. Needs deps.
list_entrypoints []Entrypoint, Error Public names and descriptions. Needs entrypoints.
run int, Error Run a public entrypoint. Needs project and entrypoints.

run’s int is the shell exit code. A non-.None Error is a fach failure — missing schema, unknown name, bad entrypoint — not that exit code.

Strings from get, Dep, and Entrypoint are borrowed from the File; keep it alive. delete the slices from list_deps and list_entrypoints.

07

Layout

Path What it is
fach.odin Library (package fach).
cmd/fach/main.odin CLI.
examples/ Sample fach.bnml.
vendor/bnml/ BNML parser.