Quick Start

Five minutes with each tool. First download the archive for your system and put bxm-canon and bxm-memo on your PATH.

BXM Canon

Write the same data two ways and hash both:

echo '{"name":"bxm","tags":["a","b"],"n":3}' > a.json
echo '{ "n": 3, "tags": ["a", "b"], "name": "bxm" }' > b.json

bxm-canon hash --text a.json
bxm-canon hash --text b.json

The two hashes are the same, because both files hold the same value and Canon has one encoding for it.

Turn the text into canonical bytes and back:

bxm-canon encode a.json -o a.bxm
bxm-canon decode --pretty a.bxm

Check that a file is canonical. Here is a byte string that spells the number 0 the long way, which Canon refuses:

printf '\x03\x80\x00' > long.bxm
bxm-canon check long.bxm
# bad long.bxm: overlong-varint at byte 1: varint is not in its shortest form

See what changed between two versions of a value:

echo '{"name":"bxm","tags":["a","b","c"],"n":4}' > c.json
bxm-canon diff --text a.json c.json
# ~ $.n 3 -> 4
# + $.tags[2] "c"

Rewrite messy JSON in its one normal form, keys sorted and no spaces:

bxm-canon fmt b.json
# {"n":3,"name":"bxm","tags":["a","b"]}

BXM Canon in JavaScript

The same core runs in Node and browsers as WebAssembly. Install it from the latest release:

npm install https://github.com/softdevstudio/bxm/releases/latest/download/bxm-canon-js.tgz
import { load } from "bxm-canon";      // or require("bxm-canon")
const canon = await load();

canon.hashText('{"b":1,"a":2}');
// 'e70e7c7b89c3a8b38fe00713290413936c9f976266c102b35aa91556dcc5233a'
canon.hashText('{ "a": 2, "b": 1 }');
// the same hash

const bytes = canon.fromText('{"name":"bxm","raw":h"cafe"}');  // Uint8Array
canon.toText(bytes);       // '{"name":"bxm","raw":h"cafe"}'
canon.isCanonical(bytes);  // true

The hash is the one bxm-canon hash printed above for the same value, because it’s the same code.

BXM Canon in Python

Python 3.9 or later, on Linux, macOS or Windows. Install the wheel for your system from the release:

pip install bxm-canon --find-links https://github.com/softdevstudio/bxm/releases/expanded_assets/v0.3.0
import bxm_canon as canon

canon.hash_value({"b": 1, "a": 2})
# 'e70e7c7b89c3a8b38fe00713290413936c9f976266c102b35aa91556dcc5233a'

data = canon.encode({"n": 3, "x": 1.0, "raw": b"\xca\xfe"})
canon.decode(data)    # {'n': 3, 'raw': b'\xca\xfe', 'x': 1.0}
canon.to_text(data)   # '{"n":3,"raw":h"cafe","x":1.0}'

Same hash as the command line and the JavaScript package. Python’s int and float stay apart, so 3 and 3.0 encode differently, and bytes stay bytes.

BXM Memo

Make a small project with a slow step:

mkdir demo && cd demo
mkdir src && echo hello > src/a.txt && echo world > src/b.txt
cat > build.sh <<'SH'
#!/bin/sh
sleep 2
mkdir -p out
cat src/*.txt > out/all.txt
echo built
SH
chmod +x build.sh

Run it through Memo twice:

bxm-memo run -i src -i build.sh -o out -- ./build.sh
# bxm-memo: miss build.sh: no earlier run of this job in the cache
# built

bxm-memo run -i src -i build.sh -o out -- ./build.sh
# built
# bxm-memo: hit build.sh: restored 1 file(s) in 222µs (the run took 2.007s)

The second run printed the same built line, from the cache, and took no time. Now change an input:

echo there > src/b.txt
bxm-memo explain -i src -i build.sh -o out -- ./build.sh
# key b4633aea6db3e900663860a95beb651dbf12896fd298507f6a912f38a6e2c165
# inputs 3 file(s), outputs out
# miss:
#   input src/b.txt changed

explain runs nothing. It tells you whether the next run would hit, and if not, why.

Next