Scaffold commands¶
These commands build the project structure. Run them once at the start of a project, then use Extend commands to add behaviour.
just-makeit new <proj> [--object name ...] [--state name:type[:default] ...]¶
Start a new project. One command gives you a complete, building, tested C extension project — optionally with a first object already scaffolded.
just-makeit new my_project
just-makeit new my_project --object engine
just-makeit new my_project --object engine --state rate:double:1.0
just-makeit new my_project --object engine --state rate:double --state order:int:4
just-makeit new my_project --object gain --arg-type float --return-type float --state gain:float:1.0
just-makeit new my_filters --module filter
just-makeit new my_dsp --module osc --module env
new writes a just-makeit.toml that records the project name, version, and
any objects — the source of truth for all subsequent commands.
By default, new projects use the per-component fragment
layout: the manifest carries
only [project] plus include globs, and each object/module routes to its
own objects/<name>.toml / modules/<name>.toml. Pass --no-fragments for
the legacy single-manifest layout where every section is inlined into
just-makeit.toml.
Arguments
| Argument | Description |
|---|---|
project |
Project name in snake_case. Used as the Python package name and distribution name. |
--object name |
Scaffold a standalone object immediately. Repeatable. |
--module name |
Scaffold an empty extension module immediately. Repeatable; mutually exclusive with --object. |
--state name:type[:default] |
Declare a state variable for the object. Repeatable. |
--arg-type TYPE |
C type for step() input x. Defaults to float _Complex. Use void for generator objects with no scalar input. Append [] for objects whose primary operation takes a whole buffer: --arg-type "float _Complex[]". |
--return-type TYPE |
C type for step() return value. Defaults to --arg-type for scalar inputs, or void for array inputs (T[]). Use void explicitly for sink objects that consume input but produce no scalar output. |
--build-system <cmake\|make> |
Build system to use. Default: cmake. Use make for a plain Makefile without CMake — useful for quick prototypes. |
--basic |
Deprecated alias for --build-system make. |
--perf |
Generate jm_perf.h with JM_HOT, JM_LIKELY, and JM_FORCEINLINE macros and apply them to step(). See Performance annotations. |
--mutable |
Remove const from the state pointer in step(). Use for objects whose step() must mutate state directly (e.g. an NCO). |
--no-state |
Scaffold the first object with no auto-generated state (see just-makeit object). Works in this one command. Mutually exclusive with --state. |
--no-step |
Scaffold the first object with no step()/steps() (see just-makeit object). Works in this one command. |
--pytest |
Generate pure pytest tests instead of the default unittest-compatible shim. |
--pytest-benchmark |
Generate pytest-benchmark bench files alongside the pytest tests. |
--windows |
Target Windows too. Records platforms = ["linux", "macos", "windows"] in [project] and emits the MinGW runtime-DLL if(WIN32 …) block into each component/module CMakeLists.txt. Off by default (Linux/macOS only); a default project's CMakeLists.txt carries no if(WIN32) per-target block. |
--no-fragments |
Use the legacy single-manifest layout: every object/module section is inlined into just-makeit.toml instead of routing to a objects/<name>.toml / modules/<name>.toml fragment. |
--fragments |
Deprecated no-op — the per-component fragment layout is the default now. |
just-makeit module <name>¶
Group multiple types into one .so subpackage. Creates the extension module
shell; types are added with just-makeit object --module. Must be run from
the project root.
The name may be dotted to nest the module in a subpackage — just-makeit module dsp.filters places the extension at src/<pkg>/dsp/filters/ so it
imports as from <pkg>.dsp.filters import Fir (arbitrary depth supported).
Intermediate packages (dsp) get a plain __init__.py; the native sources
stay in a single flat dir (native/src/dsp_filters/). Objects are added the
same way: just-makeit object fir --module dsp.filters.
Creates:
| File | Purpose |
|---|---|
native/inc/<name>/<name>_core.h |
Public C API — declare module-level functions here |
native/src/<name>/<name>_core.c |
Implementation — write module-level functions here |
native/src/<name>/<name>_ext.c |
Python binding (auto-generated) |
native/src/<name>/CMakeLists.txt |
Python module target |
src/<pkg>/<name>/__init__.py |
Subpackage init (empty exports) |
Appends add_subdirectory(native/src/<name>) to the root CMakeLists.txt
and records [module.<name>] with an empty objects list in just-makeit.toml.
Types are added with just-makeit object. Module-level functions are added
with just-makeit function.
just-makeit object <name> [--module <name>] [--state name:type[:default] ...] [--arg-type TYPE] [--return-type TYPE]¶
Add a Python type to the project — standalone or inside a module. Use this when the algorithm needs persistent state (history, coefficients, a cursor, a running accumulator). For stateless operations see Stateful vs Pure. Must be run from the project root.
Without --module — standalone object (own .so):
just-makeit object engine --state rate:double:1.0
just-makeit object ema --arg-type float --return-type float --state alpha:double:0.1 --state prev:float:0.0
Creates the full standalone set of files, updates the top-level CMakeLists.txt,
and splices the import + __all__ entry into src/<pkg>/__init__.py.
With --module — grouped into a module subpackage .so:
just-makeit object fir --module filter --state "coeffs:float[16]" --state "delay:float _Complex[16]" --state "gain:float:1.0"
just-makeit object biquad --module filter --state "b0:double:1.0" --state "a1:double:0.0" --state "w1:double:0.0"
Per-object files created (same for both modes):
| File | Purpose |
|---|---|
native/inc/<obj>/<obj>_core.h |
Header: struct, inline _step, getters/setters |
native/src/<obj>/<obj>_core.c |
Source: create/destroy/reset/steps |
native/src/<obj>/CMakeLists.txt |
OBJECT library + C test + bench |
native/tests/test_<obj>_core.c |
C test with CHECK macro counter |
native/benchmarks/bench_<obj>_core.c |
C benchmark |
Additional files for standalone objects (no --module):
| File | Purpose |
|---|---|
native/src/<obj>/<obj>_ext.c |
Python C extension (own .so) |
src/<pkg>/<obj>.pyi |
Type stub |
src/<pkg>/tests/test_<obj>.py |
pytest suite |
Module files regenerated after each just-makeit object --module:
| File | What changes |
|---|---|
native/src/<module>/<module>_ext.c |
New type block added; PyMODINIT_FUNC updated |
native/src/<module>/CMakeLists.txt |
New <obj>_core added to link list |
src/<pkg>/<module>/__init__.py |
New type added to import and __all__ |
src/<pkg>/<module>/<module>.pyi |
Type stub regenerated with new type |
Per-object files created for module objects:
| File | Purpose |
|---|---|
src/<pkg>/<module>/tests/test_<obj>.py |
pytest suite (API, getters/setters, reset, lifecycle) |
src/<pkg>/<module>/benchmarks/bench_<obj>.py |
pytest-benchmark throughput suite |
The module _ext.c is always fully regenerated from the complete object list
— never patched — so adding a third type never disturbs the first two.
Arguments
| Argument | Description |
|---|---|
name |
Object name in snake_case. Becomes the C prefix and Python class name (title-cased). |
--module name |
Target module. Without this flag the object is standalone (own .so). |
--state name:type[:default] |
Declare a state variable. Repeatable. |
--arg-type TYPE |
C type for step() input. Defaults to float _Complex. Use void for generator objects with no input. Append [] for objects whose primary operation takes a whole buffer: --arg-type "float _Complex[]" — steps() is not generated. |
--return-type TYPE |
C type for step() return value. Defaults to --arg-type for scalar inputs, or void for array inputs (T[]). Use void explicitly for sink objects that consume input but produce no output. |
--perf |
Generate jm_perf.h and apply JM_FORCEINLINE JM_HOT to step(). |
--mutable |
Remove const from the state pointer in step(). Use for objects whose step() must mutate state directly (e.g. an NCO that advances phase, a counter). The default const is correct for pure-computation objects; --mutable opts out. |
--no-state |
Suppress auto-generated state variables, constructor args, and getter/setter scaffolding. Emits <<IMPLEMENT>> stubs in the C struct body and lifecycle functions (create, destroy, reset). Mutually exclusive with --state. Use when the constructor signature is too domain-specific to express via --state (e.g. a filter that takes const float *taps, size_t num_taps). |
--no-step |
Suppress step() and steps() from all C and Python output. Lifecycle functions (create, destroy, reset) are still generated. Use for objects whose interface consists entirely of named methods added with jm method. |
--streamable |
Generate a stream(block, *, count=None, on_block=None) iterator and __iter__, so callers write for blk in obj.stream(4096): ... instead of a hand-rolled drain loop. Drives the object's variable_output method (blockwise) or built-in steps (a void-arg source); stops on a drained/empty block or after count blocks. on_block(block) fires after each block is consumed — the seam for pacing/back-pressure/progress. |
--stream-block N |
Default block size used by __iter__ (and the stream() default). Implies --streamable. |
--async-stream |
Also generate __aiter__ / __anext__ so async for blk in obj.stream(...) (and async for blk in obj) work alongside the sync forms. __anext__ runs each producer step in the running loop's default executor — pair with a nogil producer to actually free the loop during the kernel. Implies --streamable; off by default (no async glue unless asked). |
--init-param name:type[:default] |
Declare a constructor parameter for --no-state objects. Repeatable. Generates a typed constructor argument in both C and Python; not stored as a struct field. Requires --no-state. |
--impl file::funcname |
Lift the step() body from funcname in file instead of emitting a blank <<IMPLEMENT>> stub. The body is extracted verbatim and --replace substitutions are applied before insertion. |
--impl file::N:M |
Lift lines N..M (inclusive, 1-based) instead of a named function body. Out-of-bounds or inverted ranges error cleanly. |
--replace old::new |
String substitution applied to the body lifted by --impl. Repeatable. Use to rename identifiers from the source file to match the generated struct/param names. |
See State Variable Types for supported types, defaults, and
C/Python mappings. bool is a usable scalar arg, return, and state type.
--preset NAME¶
A preset is a shorthand for a common flag combination — nothing more. It expands before the normal parser runs, so every preset is equivalent to typing its flags out, and you can still pass any of them directly.
| Preset | Expands to | Shape |
|---|---|---|
processor |
(none) | Default: scalar in → scalar out. |
blockwise |
--arg-type "float _Complex[]" --return-type "float _Complex[]" |
Array in → array out of the same length. |
generator |
--arg-type void |
No input; produces output. |
consumer |
--return-type void |
Consumes input; no output. |
reader |
--no-step --init-param filepath:const char * |
No auto-step(); add custom methods via jm method. |
--preset is not repeatable. The blockwise preset pairs an array input with
an array output of the same length; override the element types by passing your
own --arg-type "T[]" --return-type "T[]".
Each --state name:type[:default] generates:
- A field in the C state struct
- A constructor parameter with the declared default
get_name()andset_name()methods in both C and Python- Getter/setter tests in both CTest and pytest
- Reset behaviour that restores the declared default
Naming rules
- Lowercase letters, digits, and underscores only.
- Must not start with a digit.
- Examples:
engine,parser,rate_limiter
The Python class name is derived automatically:
| Object name | Python class |
|---|---|
engine |
Engine |
rate_limiter |
RateLimiter |
half_band_filter |
HalfBandFilter |
just-makeit add --state name:type[:default] [...] [--object name]¶
Add one or more state variables to an existing standalone object. Must be run from the project root.
just-makeit add --state order:int:4
just-makeit add --state threshold:double:0.5 --state window:int:64
just-makeit add --object parser --state depth:int:8
just-makeit add --param n_taps:int:16
When the project has a single standalone object --object may be omitted.
add accepts --state and --param (repeatable, mixable in one call).
Adding state is structural: add authors the new [[obj.state]] entries
into just-makeit.toml, then rebuilds the object from the manifest via the
regenerate path (delete + apply). The new fields reach the struct, the
constructor, the getter/setter, the reset target, and the Python stub in one
shot.
Because the rebuild deletes and re-stubs the sacred _core.c, any
hand-written steps()/lifecycle body is discarded unless it lives in the
TOML impl/create_impl (which the rebuild re-asserts) — keep your algorithm
there, or git stash first. add prompts for one confirmation before
rebuilding; --force skips it.
Constraints
- Each new variable name must be unique within the object's state list.
- Requires a
just-makeit.toml— runjust-makeit newfirst.