Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ba76517
feat(runner): add guided error for missing manifest and onboarding im…
leynos Jan 8, 2026
b99b6f5
refactor(tests/bdd): refactor manifest_command test steps to reuse ru…
leynos Jan 8, 2026
25d8586
fix(runner): suppress unused_assignments lint for thiserror fields
leynos Jan 11, 2026
4e45b61
fix(l10n): use snake_case keys for fetch flag localisation
leynos Jan 11, 2026
5f301d2
docs(execplan): use Oxford spelling for localize/localization
leynos Jan 13, 2026
f3f8434
style(execplan): fix repetitive sentences and use em dashes in table
leynos Jan 13, 2026
a2aa4a2
docs(quickstart): use Oxford spelling for visualize
leynos Jan 13, 2026
ea120bf
fix(runner): validate manifest file_name and add tracking issue link
leynos Jan 13, 2026
c419048
fix(tests/bdd): use isolated temp directories for manifest tests
leynos Jan 13, 2026
8a56ea1
fix(l10n): differentiate clap error messages for invalid-value vs val…
leynos Jan 13, 2026
9608069
docs(execplan): update Surprises & Discoveries with actual findings
leynos Jan 13, 2026
e87ed7d
docs(execplan): update progress, outcomes, and fix list numbering
leynos Jan 13, 2026
e7920e3
docs(quickstart): remove second-person pronouns and add punctuation
leynos Jan 13, 2026
56d700c
refactor(runner): isolate derive-macro lint suppression to error subm…
leynos Jan 13, 2026
a2b289b
fix(tests/bdd): prevent /tmp deletion and document argument parsing
leynos Jan 13, 2026
74a497c
style(execplan): convert headings to sentence case and add Oxford comma
leynos Jan 13, 2026
8862393
style(quickstart): convert headings to sentence case and fix grammar
leynos Jan 13, 2026
1729075
fix(runner): replace expect() with fallible error handling
leynos Jan 13, 2026
f13ddde
docs(execplan): add article 'The' for grammatical completeness
leynos Jan 15, 2026
f88f45e
docs(quickstart): reword imperatives to avoid second-person voice
leynos Jan 15, 2026
91643a3
docs(quickstart): reword imperatives to avoid second-person voice
leynos Jan 15, 2026
c2c4fb8
docs(quickstart): reword imperatives to avoid second-person voice
leynos Jan 15, 2026
694a634
docs(quickstart): correct example manifest path in usage instruction
leynos Jan 15, 2026
4dd3f77
docs(quickstart): clarify Ninja tool installation instructions
leynos Jan 16, 2026
dbfc74e
test(bdd): sanitize test workspace paths in manifest_command steps
leynos Jan 16, 2026
2e7459a
fix(examples/hello-world): use printf instead of echo for greeting.tx…
leynos Jan 17, 2026
e982613
test(bdd): support -C flag with workspace at custom path
leynos Jan 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

178 changes: 178 additions & 0 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# Quick start: a Netsuke build

This guide walks through creating and running a Netsuke build in under five
minutes.

## Prerequisites

Before beginning, ensure the following are available:

- **Netsuke** installed (build from source with `cargo build --release` or
install via `cargo install netsuke`)
- **Ninja** build tool in the system PATH (install via the package manager,
e.g., `apt install ninja-build` or `brew install ninja`)

## Step 1: Create a project directory

In a terminal, create a new directory for the project:

```sh
mkdir hello-netsuke
cd hello-netsuke
```

## Step 2: Create the first manifest

A file named `Netsukefile` should be created with the following content:

```yaml
netsuke_version: "1.0.0"

targets:
- name: hello.txt
command: "echo 'Hello from Netsuke!' > hello.txt"

defaults:
- hello.txt
```

This manifest defines:

- A target called `hello.txt` that creates a file with a greeting.
- A default target, so running `netsuke` without arguments builds `hello.txt`.

## Step 3: Run Netsuke

To build the project, run Netsuke:

```sh
netsuke
```

The output should be similar to:

```text
[1/1] echo 'Hello from Netsuke!' > hello.txt
```

The result can be verified:

```sh
cat hello.txt
```

Output:

```text
Hello from Netsuke!
```

This completes the first Netsuke build.

## Step 4: Add variables and templates

Netsuke supports Jinja templating for dynamic manifests. The `Netsukefile` can
be updated as follows:

```yaml
netsuke_version: "1.0.0"

vars:
greeting: "Hello"
name: "World"

targets:
- name: greeting.txt
command: "echo '{{ greeting }}, {{ name }}!' > greeting.txt"

defaults:
- greeting.txt
```

Running `netsuke` again:

```sh
netsuke
```

The output can be checked:

```sh
cat greeting.txt
```

Output:

```text
Hello, World!
```

## Step 5: Use globbing and foreach

For more complex builds, Netsuke can process multiple files. Some input files
can be created as follows:

```sh
echo "Content A" > input_a.txt
echo "Content B" > input_b.txt
```

The `Netsukefile` can be updated to process all `.txt` files:

```yaml
netsuke_version: "1.0.0"

targets:
- foreach: glob('input_*.txt')
name: "output_{{ item | basename | with_suffix('.out') }}"
command: "cat {{ item }} | tr 'a-z' 'A-Z' > {{ outs }}"
sources: "{{ item }}"

defaults:
- output_input_a.out
- output_input_b.out
```

Running `netsuke`:

```sh
netsuke
```

The outputs can be checked:

```sh
cat output_input_a.out
cat output_input_b.out
```

The input files have been transformed to uppercase.

## Next steps

- Read the full [User Guide](users-guide.md) for comprehensive documentation
- Explore the `examples/` directory for real-world manifest examples:
- `basic_c.yml` — C compilation with rules and variables
- `website.yml` — Static site generation from Markdown
- `photo_edit.yml` — Photo processing with glob patterns
- Run `netsuke --help` to see all available options
- Try `netsuke graph` to visualize the build dependency graph

## Troubleshooting

### "No `Netsukefile` found in the current directory"

Ensure the current directory is correct and that a file named `Netsukefile`
exists. A different manifest path can be specified with `-f`:

```sh
netsuke -f path/to/manifest.yml
```

### "ninja: command not found"

Install Ninja using the system's package manager:

- **Ubuntu/Debian:** `sudo apt install ninja-build`
- **macOS (Homebrew):** `brew install ninja`
- **Windows (Chocolatey):** `choco install ninja`
16 changes: 8 additions & 8 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,16 +215,16 @@ library, and CLI ergonomics.

### 3.6. Onboarding and defaults

- [ ] 3.6.1. Ensure default subcommand builds manifest defaults.
- [ ] Emit guided error and hint for missing-manifest scenarios. See CLI
- [x] 3.6.1. Ensure default subcommand builds manifest defaults.
- [x] Emit guided error and hint for missing-manifest scenarios. See CLI
design.
- [ ] Guard with integration tests.
- [ ] 3.6.2. Curate OrthoConfig-generated Clap help output.
- [ ] Ensure every subcommand and flag has plain-language, localizable
- [x] Guard with integration tests.
- [x] 3.6.2. Curate OrthoConfig-generated Clap help output.
- [x] Ensure every subcommand and flag has plain-language, localizable
description. See style guide.
- [ ] 3.6.3. Publish "Hello World" quick-start walkthrough.
- [ ] Demonstrate running Netsuke end-to-end.
- [ ] Exercise via documentation test or example build fixture.
- [x] 3.6.3. Publish "Hello World" quick-start walkthrough.
- [x] Demonstrate running Netsuke end-to-end.
- [x] Exercise via documentation test or example build fixture.

### 3.7. Localization with Fluent

Expand Down
19 changes: 16 additions & 3 deletions docs/users-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,25 @@ netsuke build target_name another_target # Builds specific targets

```

If no `Netsukefile` is found, Netsuke will provide a helpful error message
guiding you.
If no `Netsukefile` is found, Netsuke will provide a helpful error message:

```text
Error: No `Netsukefile` found in the current directory.

Hint: Run `netsuke --help` to see how to specify or create a manifest.
```

A different manifest path can be specified using the `-f` or `--file` option:

```sh
netsuke -f path/to/manifest.yml
```

For a step-by-step introduction, see the [Quick Start guide](quickstart.md).

## 3\. The Netsukefile Manifest

The `Netsukefile` is a YAML file describing your build process.
The `Netsukefile` is a YAML file describing the build process.

Netsuke targets YAML 1.2 and forbids duplicate keys in manifests. If the same
mapping key appears more than once (even if a YAML parser would normally accept
Expand Down
19 changes: 19 additions & 0 deletions examples/hello-world/Netsukefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
netsuke_version: "1.0.0"

# A minimal "Hello World" example demonstrating Netsuke basics.
# This manifest transforms input.txt to output.txt by converting to uppercase.

vars:
greeting: "Hello from Netsuke"

targets:
- name: output.txt
command: "cat input.txt | tr 'a-z' 'A-Z' > output.txt"
sources: input.txt

- name: greeting.txt
command: "printf '%s\\n' '{{ greeting }}!' > greeting.txt"

defaults:
- output.txt
- greeting.txt
31 changes: 31 additions & 0 deletions examples/hello-world/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Hello World Example

A minimal Netsuke example that demonstrates:

- Basic manifest structure with `netsuke_version` and `targets`
- Using variables with Jinja templating
- File transformation (text to uppercase)
- Default targets

## Usage

From this directory, run:

```sh
netsuke
```

This builds the default targets (`output.txt` and `greeting.txt`).

## Expected Output

After running `netsuke`:

- `output.txt` contains the uppercase version of `input.txt`
- `greeting.txt` contains "Hello from Netsuke!"

## Files

- `Netsukefile` — The build manifest
- `input.txt` — Sample input file for transformation
- `README.md` — This file
2 changes: 2 additions & 0 deletions examples/hello-world/input.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
hello world from netsuke
this is a sample input file
25 changes: 24 additions & 1 deletion locales/en-US/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ cli.about = Netsuke compiles YAML + Jinja manifests into Ninja build plans.
cli.long_about = Netsuke transforms YAML + Jinja manifests into reproducible Ninja graphs and runs Ninja with safe defaults.
cli.usage = { $usage }

# Root-level flag help text.
cli.flag.file.help = Path to the Netsuke manifest file to use.
cli.flag.directory.help = Run as if started in this directory.
cli.flag.jobs.help = Set the number of parallel build jobs.
cli.flag.verbose.help = Enable verbose diagnostic logging.
cli.flag.locale.help = Locale tag for CLI copy (for example: en-US, es-ES).
cli.flag.fetch_allow_scheme.help = Additional URL schemes allowed for the fetch helper.
cli.flag.fetch_allow_host.help = Hostnames that are permitted when default deny is enabled.
cli.flag.fetch_block_host.help = Hostnames that are always blocked, even when allowed elsewhere.
cli.flag.fetch_default_deny.help = Deny all hosts by default; only allow the declared allowlist.

# Subcommand descriptions.
cli.subcommand.build.about = Build targets defined in the manifest (default).
cli.subcommand.build.long_about = Build the requested targets; when none are provided, use the manifest defaults.
cli.subcommand.clean.about = Remove build artefacts via Ninja.
Expand All @@ -13,9 +25,20 @@ cli.subcommand.graph.long_about = Generate a temporary Ninja file, then run `nin
cli.subcommand.manifest.about = Write the generated Ninja manifest without running Ninja.
cli.subcommand.manifest.long_about = Generate the Ninja file and write it to the specified path or '-' for stdout.

# Build subcommand flag help text.
cli.subcommand.build.flag.emit.help = Write the generated Ninja file to this path and keep it.
cli.subcommand.build.flag.targets.help = Targets to build (uses manifest defaults if omitted).

# Manifest subcommand argument help text.
cli.subcommand.manifest.flag.file.help = Output path for the Ninja file (use '-' for stdout).

# Clap error messages.
clap-error-missing-argument = Missing required argument: { $argument }
clap-error-missing-subcommand = Missing subcommand. Available options: { $valid_subcommands }
clap-error-unknown-argument = Unknown argument: { $argument }
clap-error-invalid-value = Invalid value for { $argument }: { $value }
clap-error-invalid-subcommand = Unknown subcommand: { $subcommand }
clap-error-value-validation = Invalid value for { $argument }: { $value }
# Note: value-validation uses distinct wording from invalid-value to differentiate
# custom validator failures (ErrorKind::ValueValidation) from type mismatches
# (ErrorKind::InvalidValue).
clap-error-value-validation = Validation failed for { $argument }: { $value }
25 changes: 24 additions & 1 deletion locales/es-ES/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ cli.about = Netsuke compila manifiestos YAML + Jinja en planes de compilación N
cli.long_about = Netsuke transforma manifiestos YAML + Jinja en grafos Ninja reproducibles y ejecuta Ninja con valores seguros.
cli.usage = { $usage }

# Texto de ayuda para opciones globales.
cli.flag.file.help = Ruta al archivo de manifiesto Netsuke.
cli.flag.directory.help = Ejecutar como si se iniciara en este directorio.
cli.flag.jobs.help = Número de trabajos de compilación en paralelo.
cli.flag.verbose.help = Habilitar registro de diagnóstico detallado.
cli.flag.locale.help = Etiqueta de idioma para la CLI (por ejemplo: en-US, es-ES).
cli.flag.fetch_allow_scheme.help = Esquemas de URL adicionales permitidos para el ayudante fetch.
cli.flag.fetch_allow_host.help = Nombres de host permitidos cuando la denegación predeterminada está habilitada.
cli.flag.fetch_block_host.help = Nombres de host siempre bloqueados, incluso cuando están permitidos.
cli.flag.fetch_default_deny.help = Denegar todos los hosts por defecto; solo permitir la lista de permitidos.

# Descripciones de subcomandos.
cli.subcommand.build.about = Compila objetivos definidos en el manifiesto (predeterminado).
cli.subcommand.build.long_about = Compila los objetivos solicitados; si no se indican, usa los predeterminados del manifiesto.
cli.subcommand.clean.about = Elimina artefactos de compilación mediante Ninja.
Expand All @@ -13,9 +25,20 @@ cli.subcommand.graph.long_about = Genera un archivo Ninja temporal y ejecuta `ni
cli.subcommand.manifest.about = Escribe el manifiesto Ninja sin ejecutar Ninja.
cli.subcommand.manifest.long_about = Genera el archivo Ninja y lo escribe en la ruta indicada o '-' para stdout.

# Texto de ayuda para opciones del subcomando build.
cli.subcommand.build.flag.emit.help = Escribir el archivo Ninja generado en esta ruta y conservarlo.
cli.subcommand.build.flag.targets.help = Objetivos a compilar (usa los predeterminados del manifiesto si se omite).

# Texto de ayuda para argumentos del subcomando manifest.
cli.subcommand.manifest.flag.file.help = Ruta de salida para el archivo Ninja (use '-' para stdout).

# Mensajes de error de Clap.
clap-error-missing-argument = Falta el argumento requerido: { $argument }
clap-error-missing-subcommand = Falta el subcomando. Opciones disponibles: { $valid_subcommands }
clap-error-unknown-argument = Argumento desconocido: { $argument }
clap-error-invalid-value = Valor no válido para { $argument }: { $value }
clap-error-invalid-subcommand = Subcomando desconocido: { $subcommand }
clap-error-value-validation = Valor no válido para { $argument }: { $value }
# Nota: value-validation usa una redacción distinta de invalid-value para
# diferenciar errores de validadores personalizados (ErrorKind::ValueValidation)
# de errores de tipo (ErrorKind::InvalidValue).
clap-error-value-validation = Validación fallida para { $argument }: { $value }
9 changes: 6 additions & 3 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,14 +243,17 @@ where
T: Into<OsString> + Clone,
{
let mut command = localize_command(Cli::command(), localizer);
let mut matches = command
let matches = command
.try_get_matches_from_mut(iter)
.map_err(|err| localize_clap_error_with_command(err, localizer, Some(&command)))?;
let cli = Cli::from_arg_matches_mut(&mut matches).map_err(|clap_err| {
// Clone matches before from_arg_matches_mut consumes the values.
let matches_for_merge = matches.clone();
let mut matches_for_parse = matches;
let cli = Cli::from_arg_matches_mut(&mut matches_for_parse).map_err(|clap_err| {
let with_cmd = clap_err.with_cmd(&command);
localize_clap_error_with_command(with_cmd, localizer, Some(&command))
})?;
Ok((cli, matches))
Ok((cli, matches_for_merge))
}

/// Return the prefixed environment provider for CLI configuration.
Expand Down
Loading
Loading