diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 00000000..08f6e3c8 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,66 @@ +# otdfctl Architecture + +## Overview + +`otdfctl` is a modular, documentation-driven CLI tool. Its architecture ensures that command definitions, arguments, and flags are defined in Markdown documentation files (`docs/man/`). These docs are parsed at runtime to generate CLI help and to drive the registration of command-line arguments and flags, ensuring that code and documentation remain in sync. + +## Project Structure + +- **Commands:** + - Source files for commands are in `cmd/`. + - Each command is registered using `man.Docs.GetCommand`, which loads its definition from the corresponding Markdown doc in `docs/man/`. + - The command string passed to `GetCommand` uses forward slashes (e.g., `dev/hello`, `policy/subject-mappings/update`) to represent the command path. + - Arguments and flags are registered in code using the doc-driven helpers, e.g.: + ```go + cmd.Flags().StringP( + cmd.GetDocFlag("flagname").Name, + cmd.GetDocFlag("flagname").Shorthand, + cmd.GetDocFlag("flagname").Default, + cmd.GetDocFlag("flagname").Description, + ) + ``` + This ensures the flag's name, shorthand, default, and description are always sourced from the documentation. +- **Handlers:** + - Business logic is implemented in `pkg/handlers/`. + - Command handlers in `cmd/` delegate to these packages for core operations. +- **Configuration:** + - Configuration management is handled in `pkg/config/` and `otdfctl.yaml`. +- **Authentication/Profiles:** + - Authentication flows and user profiles are managed in `pkg/auth/` and `pkg/profiles/`. +- **Documentation:** + - All command and subcommand documentation lives in `docs/man/`. + - These Markdown files define command names (with forward slashes), arguments, flags, descriptions, and usage examples. + - The CLI help system is generated from these docs at runtime. +- **TUI:** + - Experimental text-based UI in `tui/`. +- **Testing:** + - End-to-end BATS tests are in `e2e/`. + +## Command and Flag Registration Pattern + +- The canonical source for command structure, arguments, and flags is the Markdown documentation in `docs/man/`. +- In Go code, commands are registered using `man.Docs.GetCommand("path/to/command", ...)`. +- Flags and arguments are registered using the `GetDocFlag` helper, which pulls all metadata from the doc: + ```go + cmd.Flags().StringP( + cmd.GetDocFlag("flagname").Name, + cmd.GetDocFlag("flagname").Shorthand, + cmd.GetDocFlag("flagname").Default, + cmd.GetDocFlag("flagname").Description, + ) + ``` +- This pattern is used for all commands and subcommands, ensuring that the CLI and its documentation are always in sync. + +## Adding or Modifying Commands + +1. **Write or update the Markdown documentation** for your command in `docs/man//`. This defines the command's arguments, flags, and help text. +2. **Implement the command handler** in `cmd/`, using `man.Docs.GetCommand` to load the command and inject flags/args from the doc using `GetDocFlag`. +3. **Add or update business logic** in `pkg/handlers/` as needed. +4. **Add or update tests** in `e2e/`. +5. **Run and verify** the command, ensuring CLI help matches the documentation. + +See `docs/example-add-subcommand.md` for a step-by-step example. + +--- + +Update this document as the architecture evolves. diff --git a/CONTRIBUTOR_GUIDE.md b/CONTRIBUTOR_GUIDE.md new file mode 100644 index 00000000..bc4c521c --- /dev/null +++ b/CONTRIBUTOR_GUIDE.md @@ -0,0 +1,59 @@ +# Contributor Guide: otdfctl + +## Project Structure +- **Commands:** Add new commands in `cmd/` using Cobra. Each command should have a corresponding handler in `pkg/handlers/`. +- **Handlers:** Place business logic in `pkg/handlers/`. +- **Configuration:** Use `pkg/config/` and `otdfctl.yaml` for config management. +- **Authentication/Profiles:** Use `pkg/auth/` and `pkg/profiles/` for auth flows and user profiles. +- **Documentation:** Update or add Markdown docs in `docs/man/` for each command. These docs are parsed at runtime for CLI help. +- **TUI:** Experimental; avoid major changes unless contributing to TUI development. +- **Testing:** Add/extend BATS tests in `e2e/` for new features. Use test mode for development. + +## How to Add a Command + +1. **Create or Update the Command File:** + - For a new top-level command, create a new file in `cmd/` (e.g., `cmd/foo.go`). + - For a subcommand, add it to the appropriate parent command file (e.g., add a `hello` subcommand to `cmd/dev.go`). + - Use the CLI helpers (`cli.New`, `c.Args.GetOptionalString`, `c.Flags.GetOptionalBool`, etc.) for argument and flag parsing, following the style in [`docs/example-add-subcommand.md`](docs/example-add-subcommand.md). + +2. **Implement Business Logic:** + - Place complex logic in `pkg/handlers/` and call it from your command handler. + +3. **Add Documentation:** + - Create or update the Markdown documentation for your command or subcommand in `docs/man//`. + - Follow the format shown in the example, including argument and flag descriptions, and usage examples. + +4. **Add or Update Tests:** + - Add or extend BATS tests in `e2e/` to cover your new command and its options. + +5. **Register the Command:** + - Ensure your command or subcommand is registered with its parent in the `init()` function. + +6. **Verify and Sync:** + - Run your command to verify it works as expected. + - Check that CLI help output matches your documentation. + +See [`docs/example-add-subcommand.md`](docs/example-add-subcommand.md) for a step-by-step example. + +## Documentation-Driven CLI +- The CLI help system is powered by Markdown docs in `docs/man/`. +- Always update docs when adding or changing commands/flags. + +## Testing +- Run all BATS tests: `make test-bats` +- Run a specific test: `bats e2e/.bats` +- Use test mode for development: `make build-test` + +## TUI Guidelines +- The TUI in `tui/` is experimental. Avoid changes unless coordinated with maintainers. + +## Syncing Docs and Code +- When adding or changing commands, always update the corresponding Markdown doc in `docs/man/`. +- Review CLI help output to ensure it matches the documentation. + +## Known Limitations +- Some authentication/profile features may not work on all platforms. +- TUI is not production-ready. + +--- +Update this guide as the project evolves. diff --git a/README.md b/README.md index da96c384..5f79d3c8 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,8 @@ The output format (currently `styled` or `json`) is configurable in the `otdfctl 2. Run the handler which is located in `pkg/handlers` and pass the values as arguments 3. Handle any errors and return the result in a lite TUI format +For a detailed example of adding a new subcommand (including arguments, flags, and documentation), see [`docs/example-add-subcommand.md`](docs/example-add-subcommand.md). + ### TUI > [!CAUTION] @@ -52,6 +54,17 @@ The TUI will be used to create an interactive experience for the user. Documentation drives the CLI in this project. This can be found in `/docs/man` and is used in the CLI via the `man.Docs.GetDoc()` function. +## Documentation-Driven CLI + +The CLI help system and usage output are powered by Markdown documentation in `docs/man/`. When adding or changing commands or flags, always update the corresponding Markdown file. The documentation is parsed at runtime and also published to the OpenTDF docs website. + +To ensure documentation and code remain in sync: +- Update the relevant Markdown doc in `docs/man/` when you add or change a command. +- Review the CLI help output after changes. +- See `CONTRIBUTOR_GUIDE.md` for more details. + +--- + ## Testing The CLI is equipped with a test mode that can be enabled by building the CLI with `config.TestMode = true`. diff --git a/docs/example-add-subcommand.md b/docs/example-add-subcommand.md new file mode 100644 index 00000000..bc61b0c9 --- /dev/null +++ b/docs/example-add-subcommand.md @@ -0,0 +1,104 @@ +# Example: Adding a New Subcommand to an Existing Command + +This guide demonstrates how to add a `hello` subcommand under the existing `dev` command in `otdfctl`, using the project's CLI helpers and conventions for arguments and flags. + +--- + +## 1. Add Documentation + +Create a new file at `docs/man/dev/hello.md` with: + +```markdown +--- +title: Print Hello, ! +command: + name: dev/hello + arguments: + - name: name + description: Name to greet (optional, defaults to 'World') + flags: + - name: excited + description: Print the greeting in uppercase + default: false +--- + +Prints a greeting to the terminal. If a name is provided, it greets that name. If the `--excited` flag is set, the greeting is printed in uppercase. + +### Examples + +``` +otdfctl dev hello +Hello, World! + +otdfctl dev hello Alice +Hello, Alice! + +otdfctl dev hello Bob --excited +HELLO, BOB! +``` +``` + +--- + +## 2. Create the Subcommand in `cmd/dev.go` + +Add the following code to `cmd/dev.go`: + +```go +// ...existing imports... + +var helloCmd = man.Docs.GetCommand("dev/hello", man.WithRun(func(cmd *cobra.Command, args []string) { + c := cli.New(cmd, args) + name := c.Args.GetOptionalString(0, "World") + excited := c.Flags.GetOptionalBool("excited") + msg := fmt.Sprintf("Hello, %s!", name) + if excited { + msg = strings.ToUpper(msg) + } + c.Println(msg) +})) + +func init() { + // Register flags using the doc-driven helpers: + helloCmd.Flags().StringP( + helloCmd.GetDocFlag("name").Name, + helloCmd.GetDocFlag("name").Shorthand, + helloCmd.GetDocFlag("name").Default, + helloCmd.GetDocFlag("name").Description, + ) + helloCmd.Flags().Bool( + helloCmd.GetDocFlag("excited").Name, + helloCmd.GetDocFlag("excited").DefaultAsBool(), + helloCmd.GetDocFlag("excited").Description, + ) + devCmd.AddCommand(helloCmd) +} +``` + +- **Arguments:** `[name]` (optional) — the name to greet. Defaults to `World` if not provided. +- **Flags:** `--excited` — prints the greeting in uppercase if set. +- **Helpers Used:** `cli.New`, `c.Args.GetOptionalString`, `c.Flags.GetOptionalBool`, `c.Println`, and `GetDocFlag` for doc-driven flag registration. + +--- + +## 3. Test the Subcommand + +Run: + +```sh +otdfctl dev hello +otdfctl dev hello Alice +otdfctl dev hello Bob --excited +``` + +You should see: + +``` +Hello, World! +Hello, Alice! +HELLO, BOB! +``` + +--- + +For more complex logic, implement the business logic in `pkg/handlers/` and call it from your subcommand.