command and help ux pass (#21)
Reviewed-on: #21 Co-authored-by: vickydotbat <vickydotbat@tutamail.com> Co-committed-by: vickydotbat <vickydotbat@tutamail.com>
This commit was merged in pull request #21.
This commit is contained in:
+230
-34
@@ -2,7 +2,7 @@
|
||||
// builders shared by the `crucible` dispatcher and the standalone
|
||||
// `crucible-<name>` shims.
|
||||
//
|
||||
// Cutover state (Phase 5 → 6): the internal pipeline/topdata/erf/wiki/music
|
||||
// Cutover state (Phase 5 → 6): the internal pipeline/topdata/erf/wiki
|
||||
// packages migrated from gitea/sow-tools now live in this tree, so wired
|
||||
// builders delegate to internal/app's legacy command surface (mapped per
|
||||
// docs/command-surface.md). A builder with no migrated logic yet (depot) keeps
|
||||
@@ -29,25 +29,59 @@ const (
|
||||
|
||||
// Builder is one tool surface in the Crucible suite.
|
||||
type Builder struct {
|
||||
Name string // canonical dispatcher subcommand, e.g. "module"
|
||||
Bin string // standalone binary name, e.g. "crucible-module"
|
||||
Summary string // one-line description
|
||||
Legacy []string // nwn-tool commands this subsumes (migration aid; see docs/command-surface.md)
|
||||
Extra []string // additional callable subcommands beyond Legacy (e.g. cross-listed "music")
|
||||
Wired bool // true once the builder delegates to migrated internal/app logic
|
||||
Name string // canonical dispatcher subcommand, e.g. "module"
|
||||
Bin string // standalone binary name, e.g. "crucible-module"
|
||||
Summary string // one-line description
|
||||
Commands []Command // visible commands plus hidden compatibility aliases
|
||||
Wired bool // true once the builder delegates to migrated internal/app logic
|
||||
}
|
||||
|
||||
// subcommands returns every legacy command a wired builder accepts: the
|
||||
// canonical Legacy set plus any cross-listed Extra commands.
|
||||
func (b Builder) subcommands() []string { return append(append([]string{}, b.Legacy...), b.Extra...) }
|
||||
// Command is one public builder action. AppCommand names the unchanged
|
||||
// internal/app implementation command. Aliases remain callable for compatibility
|
||||
// but are omitted from routine help and the interactive menu.
|
||||
type Command struct {
|
||||
Name string
|
||||
Summary string
|
||||
AppCommand string
|
||||
Usage string
|
||||
Options []string
|
||||
Aliases []CommandAlias
|
||||
}
|
||||
|
||||
func (b Builder) accepts(sub string) bool {
|
||||
for _, s := range b.subcommands() {
|
||||
if s == sub {
|
||||
return true
|
||||
type CommandAlias struct {
|
||||
Name string
|
||||
AppCommand string
|
||||
}
|
||||
|
||||
func (b Builder) subcommands() []string {
|
||||
commands := make([]string, 0, len(b.Commands))
|
||||
for _, command := range b.Commands {
|
||||
commands = append(commands, command.Name)
|
||||
}
|
||||
return commands
|
||||
}
|
||||
|
||||
func (b Builder) command(name string) (Command, string, bool) {
|
||||
for _, command := range b.Commands {
|
||||
if command.Name == name {
|
||||
return command, command.AppCommand, true
|
||||
}
|
||||
for _, alias := range command.Aliases {
|
||||
if alias.Name == name {
|
||||
target := alias.AppCommand
|
||||
if target == "" {
|
||||
target = command.AppCommand
|
||||
}
|
||||
return command, target, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
return Command{}, "", false
|
||||
}
|
||||
|
||||
func (b Builder) accepts(sub string) bool {
|
||||
_, _, ok := b.command(sub)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Registry is the single source of truth for the Crucible command surface.
|
||||
@@ -57,40 +91,172 @@ var Registry = []Builder{
|
||||
Name: "depot",
|
||||
Bin: "crucible-depot",
|
||||
Summary: "verify/move content-addressed depot blobs (SeaweedFS)",
|
||||
Legacy: nil,
|
||||
Wired: false, // no migrated logic yet; fails closed (exit 70)
|
||||
},
|
||||
{
|
||||
Name: "hak",
|
||||
Bin: "crucible-hak",
|
||||
Summary: "pack/unpack ERF/HAK archives + hak manifests",
|
||||
Legacy: []string{"build-haks", "apply-hak-manifest"},
|
||||
Extra: []string{"music"}, // music BMUs are packed into HAKs (command-surface.md)
|
||||
Wired: true,
|
||||
Commands: []Command{
|
||||
{
|
||||
Name: "build",
|
||||
Summary: "build configured HAK archives and their manifest",
|
||||
AppCommand: "build-haks",
|
||||
Usage: "crucible hak build [options]",
|
||||
Options: []string{
|
||||
"--hak <name> build selected configured HAKs",
|
||||
"--archive <name> build selected archive members",
|
||||
"--source-manifest <path> read a generated source manifest",
|
||||
"--content-addressed-root <path> resolve source-manifest blobs here",
|
||||
"--plan-only plan outputs without writing archives",
|
||||
"--quiet | --verbose | --debug select output detail",
|
||||
},
|
||||
Aliases: []CommandAlias{{Name: "build-haks"}},
|
||||
},
|
||||
{
|
||||
Name: "manifest",
|
||||
Summary: "apply a generated HAK list to the module source",
|
||||
AppCommand: "apply-hak-manifest",
|
||||
Usage: "crucible hak manifest [manifest-path]",
|
||||
Aliases: []CommandAlias{{Name: "apply-hak-manifest"}},
|
||||
},
|
||||
},
|
||||
Wired: true,
|
||||
},
|
||||
{
|
||||
Name: "module",
|
||||
Bin: "crucible-module",
|
||||
Summary: "build/extract/validate/compare the .mod",
|
||||
Legacy: []string{"build", "build-module", "extract", "validate", "compare"},
|
||||
// apply-hak-manifest is canonically a hak command but reachable here too;
|
||||
// music is folded into the module build pipeline (command-surface.md).
|
||||
Extra: []string{"apply-hak-manifest", "music"},
|
||||
Commands: []Command{
|
||||
{
|
||||
Name: "build",
|
||||
Summary: "build the module and configured project outputs",
|
||||
AppCommand: "build",
|
||||
Usage: "crucible module build [--quiet|--verbose|--debug]",
|
||||
Aliases: []CommandAlias{{Name: "build-module", AppCommand: "build-module"}},
|
||||
},
|
||||
{
|
||||
Name: "extract",
|
||||
Summary: "extract built archives into configured source trees",
|
||||
AppCommand: "extract",
|
||||
Usage: "crucible module extract [resource ...]",
|
||||
},
|
||||
{
|
||||
Name: "validate",
|
||||
Summary: "validate project layout, config, and source inventory",
|
||||
AppCommand: "validate",
|
||||
Usage: "crucible module validate",
|
||||
},
|
||||
{
|
||||
Name: "compare",
|
||||
Summary: "compare source content with built module and HAK archives",
|
||||
AppCommand: "compare",
|
||||
Usage: "crucible module compare",
|
||||
},
|
||||
{
|
||||
Name: "manifest",
|
||||
Summary: "apply a generated HAK list to the module source",
|
||||
AppCommand: "apply-hak-manifest",
|
||||
Usage: "crucible module manifest [manifest-path]",
|
||||
Aliases: []CommandAlias{{Name: "apply-hak-manifest"}},
|
||||
},
|
||||
},
|
||||
Wired: true,
|
||||
},
|
||||
{
|
||||
Name: "topdata",
|
||||
Bin: "crucible-topdata",
|
||||
Summary: "compile 2da/tlk topdata + packages",
|
||||
Legacy: []string{"validate-topdata", "build-topdata", "build-top-package", "compare-topdata", "convert-topdata"},
|
||||
Wired: true,
|
||||
Commands: []Command{
|
||||
{
|
||||
Name: "validate",
|
||||
Summary: "validate topdata layout and canonical JSON compatibility",
|
||||
AppCommand: "validate-topdata",
|
||||
Usage: "crucible topdata validate",
|
||||
Aliases: []CommandAlias{{Name: "validate-topdata"}},
|
||||
},
|
||||
{
|
||||
Name: "build",
|
||||
Summary: "compile topdata and refresh the configured package",
|
||||
AppCommand: "build-topdata",
|
||||
Usage: "crucible topdata build [--force] [--wiki] [--skip-lfs]",
|
||||
Options: []string{
|
||||
"--force rebuild outputs even when current",
|
||||
"--wiki build wiki drafts after compilation",
|
||||
"--skip-lfs skip Git LFS materialization",
|
||||
},
|
||||
Aliases: []CommandAlias{{Name: "build-topdata"}},
|
||||
},
|
||||
{
|
||||
Name: "package",
|
||||
Summary: "package cached topdata outputs into HAK and TLK files",
|
||||
AppCommand: "build-top-package",
|
||||
Usage: "crucible topdata package [--force] [--skip-lfs]",
|
||||
Options: []string{
|
||||
"--force rebuild outputs even when current",
|
||||
"--skip-lfs skip Git LFS materialization",
|
||||
},
|
||||
Aliases: []CommandAlias{{Name: "build-top-package"}},
|
||||
},
|
||||
{
|
||||
Name: "compare",
|
||||
Summary: "compare current output with a fresh native build",
|
||||
AppCommand: "compare-topdata",
|
||||
Usage: "crucible topdata compare",
|
||||
Aliases: []CommandAlias{{Name: "compare-topdata"}},
|
||||
},
|
||||
{
|
||||
Name: "convert",
|
||||
Summary: "convert between 2DA and native JSON/module formats",
|
||||
AppCommand: "convert-topdata",
|
||||
Usage: "crucible topdata convert <2da-to-json|2da-to-module|json-to-2da> ...",
|
||||
Options: []string{
|
||||
"2da-to-json <input.2da> <output.json>",
|
||||
"2da-to-module [flags] <input.2da> [output.json]",
|
||||
"json-to-2da <input.json> <output.2da>",
|
||||
},
|
||||
Aliases: []CommandAlias{{Name: "convert-topdata"}},
|
||||
},
|
||||
},
|
||||
Wired: true,
|
||||
},
|
||||
{
|
||||
Name: "wiki",
|
||||
Bin: "crucible-wiki",
|
||||
Summary: "render + deploy mechanical wiki pages",
|
||||
Legacy: []string{"build-wiki", "deploy-wiki"},
|
||||
Wired: true,
|
||||
Commands: []Command{
|
||||
{
|
||||
Name: "build",
|
||||
Summary: "render wiki page drafts from compiled topdata",
|
||||
AppCommand: "build-wiki",
|
||||
Usage: "crucible wiki build [--force]",
|
||||
Options: []string{"--force rebuild drafts even when current"},
|
||||
Aliases: []CommandAlias{{Name: "build-wiki"}},
|
||||
},
|
||||
{
|
||||
Name: "deploy",
|
||||
Summary: "deploy generated wiki pages to NodeBB",
|
||||
AppCommand: "deploy-wiki",
|
||||
Usage: "crucible wiki deploy [options]",
|
||||
Options: []string{
|
||||
"--source-dir <path> read generated pages here",
|
||||
"--endpoint <url> override the NodeBB endpoint",
|
||||
"--token <token> authenticate with an API token",
|
||||
"--username/--password <value> authenticate with credentials",
|
||||
"--version <version> label the deployed revision",
|
||||
"--namespace <name> deploy selected namespaces",
|
||||
"--category <namespace=id> override a namespace category",
|
||||
"--manifest <path> write deployment state here",
|
||||
"--stale-policy <policy> report, archive, or purge",
|
||||
"--dry-run report changes without writing",
|
||||
"--create allow missing pages to be created",
|
||||
"--force update unchanged pages",
|
||||
"--reset-managed-namespaces reset managed namespace state",
|
||||
},
|
||||
Aliases: []CommandAlias{{Name: "deploy-wiki"}},
|
||||
},
|
||||
},
|
||||
Wired: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -128,10 +294,14 @@ func menuItems() []menu.Item {
|
||||
continue
|
||||
}
|
||||
for _, sub := range b.subcommands() {
|
||||
command, _, _ := b.command(sub)
|
||||
items = append(items, menu.Item{
|
||||
Label: b.Name + " " + sub,
|
||||
Desc: b.Summary,
|
||||
Desc: command.Summary,
|
||||
Args: []string{b.Name, sub},
|
||||
Usage: command.Usage,
|
||||
Options: append([]string(nil),
|
||||
command.Options...),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -206,14 +376,21 @@ func runBuilder(name string, args []string, out, errw io.Writer) int {
|
||||
return exitUsage
|
||||
}
|
||||
sub := args[0]
|
||||
if !b.accepts(sub) {
|
||||
command, appCommand, ok := b.command(sub)
|
||||
if !ok {
|
||||
fmt.Fprintf(errw, "crucible %s: unknown subcommand %q\n\n", b.Name, sub)
|
||||
builderHelp(errw, b)
|
||||
return exitUsage
|
||||
}
|
||||
// Delegate to the migrated legacy command surface. args[0] is already the
|
||||
// legacy command name, so it is forwarded unchanged.
|
||||
return delegateLegacy(args, errw)
|
||||
if len(args) > 1 {
|
||||
switch args[1] {
|
||||
case "-h", "--help", "help":
|
||||
commandHelp(out, command)
|
||||
return exitOK
|
||||
}
|
||||
}
|
||||
legacyArgs := append([]string{appCommand}, args[1:]...)
|
||||
return delegateLegacy(legacyArgs, errw)
|
||||
}
|
||||
|
||||
// delegateLegacy runs a migrated nwn-tool command via internal/app and maps its
|
||||
@@ -269,8 +446,27 @@ func builderHelp(w io.Writer, b Builder) {
|
||||
}
|
||||
fmt.Fprintf(w, "usage: crucible %s <subcommand> [args] (or: %s <subcommand> [args])\n\n", b.Name, b.Bin)
|
||||
fmt.Fprintf(w, "subcommands:\n")
|
||||
for _, c := range b.subcommands() {
|
||||
fmt.Fprintf(w, " %s\n", c)
|
||||
width := 0
|
||||
for _, command := range b.Commands {
|
||||
if len(command.Name) > width {
|
||||
width = len(command.Name)
|
||||
}
|
||||
}
|
||||
for _, command := range b.Commands {
|
||||
fmt.Fprintf(w, " %-*s %s\n", width, command.Name, command.Summary)
|
||||
}
|
||||
fmt.Fprintf(w, "\nstatus: wired to migrated nwn-tool logic. See docs/command-surface.md.\n")
|
||||
}
|
||||
|
||||
func commandHelp(w io.Writer, command Command) {
|
||||
fmt.Fprintf(w, "%s\n", command.Summary)
|
||||
if command.Usage != "" {
|
||||
fmt.Fprintf(w, "\nusage: %s\n", command.Usage)
|
||||
}
|
||||
if len(command.Options) > 0 {
|
||||
fmt.Fprintln(w, "\noptions:")
|
||||
for _, option := range command.Options {
|
||||
fmt.Fprintf(w, " %s\n", option)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user