Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
10 changes: 6 additions & 4 deletions docs/argmojo_overall_planning.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,10 @@ tests/
├── test_remainder_known.mojo # remainder, parse_known_arguments, allow_hyphen_values tests
├── test_fullwidth.mojo # full-width → half-width auto-correction tests
├── test_groups_help.mojo # argument groups in help + value_name wrapping tests
├── test_prompt.mojo # interactive prompting tests
├── test_parents.mojo # argument parents (shared definitions) tests
└── test_confirmation.mojo # confirmation option (--yes / -y) tests
├── test_prompt.mojo # interactive prompting tests
├── test_parents.mojo # argument parents (shared definitions) tests
├── test_confirmation.mojo # confirmation option (--yes / -y) tests
└── test_usage.mojo # usage line customisation tests
examples/
├── demo.mojo # comprehensive showcase of all ArgMojo features
├── mgrep.mojo # grep-like CLI example (no subcommands)
Expand Down Expand Up @@ -241,6 +242,7 @@ examples/
| Interactive prompting (`.prompt()`, `.prompt["..."]()` → prompt for missing args) | ✓ | ✓ |
| Argument parents (`add_parent(parent)` → share args across commands) | ✓ | ✓ |
| Confirmation option (`confirmation_option()` → `--yes`/`-y` to skip confirmation) | ✓ | ✓ |
| Usage line customisation (`command.usage("...")` → override auto-generated usage line) | ✓ | ✓ |

> ⚠ Response file support is temporarily disabled due to a Mojo compiler deadlock under `-D ASSERT=all`. The implementation is preserved and will be re-enabled when the compiler bug is fixed.

Expand Down Expand Up @@ -581,7 +583,7 @@ Before adding Phase 5 features, further decompose `parse_arguments()` for readab
- [x] **Colored error output** — ANSI styled error messages (help output already colored)
- [x] **Shell completion script generation** — `generate_completion["bash"]()` (compile-time validated) or `generate_completion("bash")` (runtime, case-insensitive) returns a complete completion script; static approach (no runtime hook), covers options/flags/choices/subcommands (clap `generate`, cobra `completion`, click `shell_complete`)
- [x] **Argument groups in help** — `.group["name"]()` groups related options under headings; independent per-section padding; persistent args stay in "Global Options:" (argparse `add_argument_group`) (PR #17)
- [ ] **Usage line customisation** — two approaches: (1) manual override via `.usage("...")` for git-style hand-written usage strings (e.g. `[-v | --version] [-h | --help] [-C <path>] ...`); (2) auto-expanded mode that enumerates every flag inline like argparse (good for small CLIs, noisy for large ones). Current default `[OPTIONS]` / `<COMMAND>` is the cobra/clap/click convention and is the right default.
- [x] **Usage line customisation** — two approaches: (1) manual override via `.usage("...")` for git-style hand-written usage strings (e.g. `[-v | --version] [-h | --help] [-C <path>] ...`); (2) auto-expanded mode that enumerates every flag inline like argparse (good for small CLIs, noisy for large ones). Current default `[OPTIONS]` / `<COMMAND>` is the cobra/clap/click convention and is the right default.
- [x] **Partial parsing** — `parse_known_arguments()` collects unrecognised options instead of erroring; access via `result.get_unknown_args()` (argparse `parse_known_args`) (PR #13)
- [x] **Require equals syntax** — `.require_equals()` forces `--key=value`, disallows `--key value` (clap `require_equals`) (PR #12)
- [x] **Default-if-no-value** — `.default_if_no_value["val"]()`: `--opt` uses fallback; `--opt=val` uses val; absent uses default (argparse `const`) (PR #12)
Expand Down
3 changes: 2 additions & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ Comment out unreleased changes here. This file will be edited just before each r
13. **Registration-time validation for group constraints.** `mutually_exclusive()`, `required_together()`, `one_required()`, and `required_if()` now validate argument names against `self.args` at the moment they are called. An `Error` is raised immediately if any name is unknown, empty lists are rejected, and duplicates are silently deduplicated. `required_if()` additionally rejects self-referential rules (`target == condition`). This catches developer typos on the very first `mojo run`, without waiting for end-user input (PR #22).
14. **Interactive prompting.** Add `.prompt()` and `.prompt["text"]()` builder methods on `Argument`. When an argument marked with `.prompt()` is not provided on the command line, the user is interactively prompted for its value before validation runs. Use `.prompt()` to prompt with the argument's help text, or `.prompt["Custom text"]()` to set a custom message. Works on both required and optional arguments. Prompts show valid choices for `.choice[]()` arguments and show default values in parentheses. For flag arguments, `y`/`n` input is accepted. When stdin is not a terminal (e.g., piped input, CI environments, `/dev/null`), or when `input()` otherwise raises, the exception is caught, prompting stops gracefully, and any values collected so far are preserved (PR #23).
15. **Argument parents.** Add `add_parent(parent)` method on `Command`. Copies all argument definitions and group constraints (mutually exclusive, required together, one-required, conditional requirements, implications) from a parent `Command` into the current command. This lets you share a common set of arguments across multiple commands without repeating them — equivalent to Python argparse's `parents` parameter. The parent is not modified. All registration-time validation guards run on each inherited argument as usual (PR #25).
16. **Confirmation option.** Add `confirmation_option()` and `confirmation_option["prompt"]()` builder methods on `Command`. When enabled, the command automatically registers a `--yes` / `-y` flag and prompts the user for confirmation after parsing (and after interactive prompting, if any). If the user does not confirm (`y`/`yes`), the command aborts with an error. Passing `--yes` or `-y` on the command line skips the prompt entirely. When stdin is not interactive (piped input, `/dev/null`), the command aborts gracefully. This is equivalent to Click's `confirmation_option` decorator (PR #26).
16. **Confirmation option.** Add `confirmation_option()` and `confirmation_option["prompt"]()` builder methods on `Command`.
17. **Usage line customisation.** Add `usage(text)` method on `Command`. When set, the given text replaces the auto-generated `Usage: myapp [OPTIONS] ...` line in both `--help` output and error messages. This lets you write git-style usage strings like `git [-v | --version] [-h | --help] [-C <path>] <command> [<args>]`. When not set, the default auto-generated usage line is used. When enabled, the command automatically registers a `--yes` / `-y` flag and prompts the user for confirmation after parsing (and after interactive prompting, if any). If the user does not confirm (`y`/`yes`), the command aborts with an error. Passing `--yes` or `-y` on the command line skips the prompt entirely. When stdin is not interactive (piped input, `/dev/null`), the command aborts gracefully. This is equivalent to Click's `confirmation_option` decorator (PR #26).

### 🦋 Changed in v0.4.0

Expand Down
46 changes: 43 additions & 3 deletions docs/user_manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ from argmojo import Argument, Command
- [Custom Prompt Text](#custom-prompt-text)
- [Using with Subcommands](#using-with-subcommands-1)
- [Non-Interactive Use](#non-interactive-use)
- [Usage Line Customisation](#usage-line-customisation)
- [Shell Completion](#shell-completion)
- [Built-in `--completions` Flag](#built-in---completions-flag)
- [Disabling the Built-in Flag](#disabling-the-built-in-flag)
Expand Down Expand Up @@ -441,9 +442,11 @@ Argument("name", help="...")
║ │ command.disable_punctuation_correction() disable CJK punctuation correction
║ ├── Argument inheritance
║ │ command.add_parent(parent) copy arguments from a parent command
║ └── Confirmation
║ command.confirmation_option() add --yes/-y confirmation prompt
║ command.confirmation_option["text"]() custom confirmation prompt text
║ ├── Confirmation
║ │ command.confirmation_option() add --yes/-y confirmation prompt
║ │ command.confirmation_option["text"]() custom confirmation prompt text
║ └── Usage
║ command.usage("...") override the auto-generated usage line
╚═══════════════════════════════════════════════════════════════════════════════
```

Expand Down Expand Up @@ -491,6 +494,7 @@ The table below shows which builder methods can be used with each argument mode.
| `command.implies()` ³ | ✓ | ✓ | ✓ | — |
| `command.add_parent()` ³ | ✓ | ✓ | ✓ | ✓ |
| `command.confirmation_option()` ³ | — | — | — | — |
| `command.usage()` ³ | — | — | — | — |

> ¹ Requires `.range[min,max]()` first. ² Implies `.append()` automatically. ³ Command-level method — called on `Command`, not chained on `Argument`. ⁴ Accepts compile-time parameter: `.value_name[wrapped: Bool = True]("NAME")` — `True` wraps in `<NAME>`, `False` displays bare `NAME`. ⁵ Response files temporarily disabled due to Mojo compiler bug.

Expand Down Expand Up @@ -3284,6 +3288,42 @@ $ ./drop mydb --yes # works in CI
Dropping database: mydb
```

## Usage Line Customisation

By default, ArgMojo generates usage lines like `Usage: myapp [OPTIONS] <PATTERN>` — showing `[OPTIONS]` for named arguments and listing each positional. This convention (shared by clap, cobra, and Click) works well for most CLIs.

For some programs you may want a hand-written usage string — for example, git's usage line enumerates a few key flags inline rather than collapsing them into `[OPTIONS]`. The `usage()` method on `Command` lets you replace the auto-generated usage line with your own text:

```mojo
from argmojo import Command, Argument

fn main() raises:
var cmd = Command("git", "The stupid content tracker", version="2.45.0")
cmd.usage("git [-v | --version] [-h | --help] [-C <path>] <command> [<args>]")

cmd.add_argument(Argument("verbose", help="Verbose output").long["verbose"]().short["v"]().flag())
cmd.add_argument(Argument("path", help="Run as if started in <path>").long["C"]().short["C"]())

var result = cmd.parse()
```

The custom string appears as-is after `Usage: ` in both `--help` output and error messages:

```sh
$ ./git --help
Usage: git [-v | --version] [-h | --help] [-C <path>] <command> [<args>]

The stupid content tracker

Options:
-v, --verbose Verbose output
-C <path> Run as if started in <path>
-h, --help Print help
-V, --version Print version
```

When no custom usage is set, the auto-generated line is used as before — no change in default behaviour.

## Shell Completion

ArgMojo can generate **shell completion scripts** for Bash, Zsh, and Fish. These scripts enable tab-completion for your CLI's options, flags, subcommands, and choice values — with zero runtime overhead.
Expand Down
1 change: 1 addition & 0 deletions pixi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ test = """\
&& mojo run -I src -D ASSERT=all tests/test_groups_help.mojo \
&& mojo run -I src -D ASSERT=all tests/test_prompt.mojo < /dev/null \
&& mojo run -I src -D ASSERT=all tests/test_parents.mojo \
&& mojo run -I src -D ASSERT=all tests/test_usage.mojo \
&& mojo run -I src -D ASSERT=all tests/test_confirmation.mojo < /dev/null"""
# NOTE: test_response_file.mojo is excluded — response file expansion
# is temporarily disabled to work around a Mojo compiler deadlock
Expand Down
39 changes: 39 additions & 0 deletions src/argmojo/command.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,9 @@ struct Command(Copyable, Movable, Stringable, Writable):
var _confirmation_prompt: String
"""The prompt text shown when asking for confirmation.
Empty until ``confirmation_option()`` is called."""
var _custom_usage: String
"""When non-empty, replaces the auto-generated usage line.
Set via ``usage("...")``."""

# ===------------------------------------------------------------------=== #
# Life cycle methods
Expand Down Expand Up @@ -295,6 +298,7 @@ struct Command(Copyable, Movable, Stringable, Writable):
self._disable_punctuation_correction = False
self._confirmation_enabled = False
self._confirmation_prompt = String("")
self._custom_usage = String("")
self._header_color = _DEFAULT_HEADER_COLOR
self._arg_color = _DEFAULT_ARG_COLOR
self._warn_color = _DEFAULT_WARN_COLOR
Expand Down Expand Up @@ -337,6 +341,7 @@ struct Command(Copyable, Movable, Stringable, Writable):
)
self._confirmation_enabled = move._confirmation_enabled
self._confirmation_prompt = move._confirmation_prompt^
self._custom_usage = move._custom_usage^
self._header_color = move._header_color^
self._arg_color = move._arg_color^
self._warn_color = move._warn_color^
Expand Down Expand Up @@ -396,6 +401,7 @@ struct Command(Copyable, Movable, Stringable, Writable):
)
self._confirmation_enabled = copy._confirmation_enabled
self._confirmation_prompt = copy._confirmation_prompt
self._custom_usage = copy._custom_usage
self._header_color = copy._header_color
self._arg_color = copy._arg_color
self._warn_color = copy._warn_color
Expand Down Expand Up @@ -1283,6 +1289,33 @@ struct Command(Copyable, Movable, Stringable, Writable):
var imp_triple: List[String] = [trigger, implied, implied_kind]
self._implications.append(imp_triple^)

fn usage(mut self, text: String):
"""Sets a custom usage line, replacing the auto-generated one.

By default, ArgMojo generates a usage line like::

Usage: myapp <file> [OPTIONS]

Call this method to override it with a completely custom string.
The string should **not** include the ``Usage:`` prefix — it will
be added automatically.

Args:
text: The custom usage text (e.g. ``"myapp [-v | --version]
[-C <path>] <command> [<args>]"``).

Example:

```mojo
from argmojo import Command
var cmd = Command("git", "The stupid content tracker")
cmd.usage("git [-v | --version] [-C <path>] <command> [<args>]")
# --help will show:
# Usage: git [-v | --version] [-C <path>] <command> [<args>]
```
"""
self._custom_usage = text

fn help_on_no_arguments(mut self) raises:
"""Enables showing help when invoked with no arguments.

Expand Down Expand Up @@ -1623,6 +1656,8 @@ struct Command(Copyable, Movable, Stringable, Writable):

Example output: ``Usage: git clone <repository> [directory] [OPTIONS]``
"""
if self._custom_usage:
return String("Usage: ") + self._custom_usage
var s = String("Usage: ") + self.name
for i in range(len(self.args)):
if self.args[i]._is_positional and not self.args[i]._is_hidden:
Expand Down Expand Up @@ -3545,6 +3580,10 @@ struct Command(Copyable, Movable, Stringable, Writable):
s += self.description + "\n\n"

# Usage line.
if self._custom_usage:
s += header_color + "Usage:" + reset_code + " "
s += self._custom_usage + "\n\n"
return s
s += header_color + "Usage:" + reset_code + " "
s += arg_color + self.name + reset_code

Expand Down
Loading
Loading