Skip to content

three_face — one C core, three faces (fully generated)

Demonstrates the combined target just-makeit is architected for: a single C core exposed three ways, all calling the same gain_step() — and jm app generates every face, with no hand-written parsing or I/O loop:

Face Artifact Generated by
Standalone C binary CLI build/gaintool jm app --target c (argv parser + read→step→write loop + add_executable linking gain_core)
Python CLI (console entry) python -m gaintool.cli / gaintool on PATH jm app --target console (argparse + numpy I/O loop + [project.scripts])
Python module API from gaintool import Gain native jm extension
(bonus) shareable script gaintool.py jm app --target pep723

gaintool scales a stream of float32 samples by --gain. The same gain_core.c is compiled once as a CMake OBJECT library and linked by the binary, the C test, and the Python extension — so all three faces behave identically (the test asserts every face agrees to within 1e-5).

The hand-written Doxygen @brief on gain_create() in the sacred native/inc/gain/gain_core.h header drives the generated gain.pyi class docstring — jm apply re-derives the stub from that comment.

Run it

python3 src/just_makeit/examples/three_face/test.py   # scaffold → build → run all faces
pytest tests/test_examples.py -k three_face

Manually, after test.py builds a project:

printf '...' | ./build/gaintool --gain 2.0 > out.f32     # C binary
python -m gaintool.cli --gain 2.0 < in.f32 > out.f32     # Python CLI (run from src/)
python -c "from gaintool import Gain; print(Gain(2.0).step(1.5))"  # module

How it works

jm app derives a --<state-var> flag per ctor state var (so --gain feeds gain_create(gain)), adds --input/--output, and emits a read→step()→write loop over the object's sample dtype — generating the C strtof/argv parser and the Python argparse from the same object model. Extra flags can be declared with jm app --flag name:type[:default[:help]] (persisted as [[app.flags]]) and appear in both parsers.

Four shapes are generated: scalar (x → y, what gaintool uses), blockwise (x[] → y[]), consumer (x → void), and generator (void → y). no_step objects and anything else fall back to an <<IMPLEMENT>> stub. The app_shapes example (jm example app_shapes) builds the generator and blockwise faces as C binaries.

History

The first version of this example had to hand-write the C main() and the Python cli.py because jm app only scaffolded plumbing. Those hand-written bodies became the spec for the generator; this example now relies entirely on generated code (its diff against the old version is the proof the generator reproduces what was hand-written).

The follow-ons that version listed have all since shipped: [[app.commands]] subcommands (jm app --command), binding jm app to module functions (jm app --function), and blockwise/steps() I/O loops.