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.