Skip to content

edgebr/scaffold

Repository files navigation

Scaffold

Generic component scaffolding tool for firmware projects. Generates files from Jinja2 templates and supports fully interactive or pre-filled CLI usage. Primarily focused on Zephyr RTOS but not exclusive to it.

Setup

pip install -e .

This enables both python -m scaffold and the scaffold CLI entry point.

Usage

# Fully interactive — prompts for everything
python -m scaffold

# Component type selected, rest interactive
python -m scaffold driver

# Pre-fill fields, prompt for the rest
python -m scaffold driver --name my_drv --path drivers/

# Pre-fill everything, accept all defaults silently
python -m scaffold driver --name my_drv --path drivers/ --skip-defaults

# Load external component definitions from a directory
python -m scaffold --components-dir my-components/widget

Options

Option Description
[component] Component type to scaffold. Omit to select interactively.
--name, -n Component name in snake_case.
--path, -p Output path relative to the current directory.
--skip-defaults, -S Accept all defaults without prompting.
--components-dir, -C Directory to load extra component modules from.
--hide-defaults Suppress built-in components; show only external ones.

Configuration

Scaffold loads configuration from up to two files before applying CLI flags. This lets you persist settings like components-dir per-user or per-project without passing them on every invocation.

Resolution order (lowest → highest priority)

Priority Source
1 $HOME/.config/scaffold/config.yml — user-level
2 <cwd>/scaffold.yml — project-level (git-trackable)
3 CLI flags

Keys from lower-priority sources are preserved when a higher-priority source does not set them.

Config file format

# scaffold.yml
components-dir: path/to/components
hide_defaults: true # suppress built-in components; show only external ones

components-dir is always resolved relative to the file that declares it: scaffold.yml paths resolve against <cwd>, user-config paths resolve against ~/.config/scaffold/, and --components-dir on the CLI resolves against the current working directory.

Schema

Valid keys are defined in scaffold/config_schema.json. Any config file that contains an unknown key, a wrong value type, or malformed YAML is rejected with a clear error message — scaffold will not start.

Example: committing a project-level config

# scaffold.yml  (committed to your repo)
components-dir: tools/scaffold-components

All team members get the same components-dir automatically after cloning, with no manual setup required.

Built-in components

Component Default path Generated files
driver drivers/ CMakeLists.txt, Kconfig, README.md, {name}.h, {name}.c
service services/ CMakeLists.txt, Kconfig, README.md, include/service/{name}_access.h, src/{name}.c, src/{name}_internal.h
lib lib/ CMakeLists.txt, Kconfig, README.md, {name}.h, {name}.c
sample samples/ CMakeLists.txt, README.md, sample.yaml, prj.conf, src/main.c
test tests/ CMakeLists.txt, README.md, testcase.yaml, prj.conf, src/main.c

External components

Component definitions can live outside this repository. Point --components-dir at any directory containing .py files that define a COMPONENT constant. Each component declares its own template directory, making external component sets fully self-contained.

my-components/
├── widget/
│   ├── __init__.py
│   ├── widget.py           # COMPONENT definition
│   └── templates/          # Jinja2 templates for widget
│       └── widget.c.j2
└── sensor/
    ├── __init__.py
    ├── sensor.py
    └── templates/
        └── sensor.c.j2

A minimal external component:

from pathlib import Path
from scaffold.descriptors import Component, Field, TemplateFile
from scaffold.validators import check_path_conflict, validate_snake_case

_HERE = Path(__file__).parent

COMPONENT = Component(
    name="my_type",
    default_path="src/",
    fields=[
        Field(name="name", prompt="Name (snake_case)", validator=validate_snake_case),
        Field(name="path", prompt="Output path", default="src/"),
    ],
    validators=[check_path_conflict],
    templates=[
        TemplateFile("main.c.j2", "{name}.c"),
    ],
    templates_dir=_HERE / "templates",
)

Load the component set at runtime:

scaffold --components-dir my-components/widget widget --name my_widget

External components with the same name as a built-in will override it. Use --hide-defaults (or hide_defaults: true in scaffold.yml) to suppress built-ins entirely and show only external components.

Adding a built-in component type

  1. Create a subdirectory example_components/<type>/.
  2. Add __init__.py (empty) and <type>.py with a COMPONENT constant. Set templates_dir=Path(__file__).parent / "templates".
  3. Add Jinja2 templates under example_components/<type>/templates/.
  4. Auto-discovery picks it up — nothing else needs to change.

See example_components/driver/driver.py as a reference.

West integration

Add scaffold as a module in your west manifest:

# west.yml
manifest:
  projects:
    - name: scaffold
      url: https://github.com/edgebr/scaffold
      revision: main
      path: tools/scaffold

Once added, run west update and use west scaffold with the same arguments as the CLI:

west scaffold driver --name my_drv --path drivers/ --skip-defaults
west scaffold --components-dir my-components/ --hide-defaults widget --name my_widget

About

A component generation tool for helping develop in zephyr

Resources

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors