package commandkit import ( "fmt" "io" "runtime/debug" internalcobra "gitea.maximumdirect.net/eric/CommandKit/internal/cobra" commandmeta "gitea.maximumdirect.net/eric/CommandKit/internal/commandmeta" yaml "go.yaml.in/yaml/v4" ) const commandKitModulePath = "gitea.maximumdirect.net/eric/CommandKit" type helpInput struct { Targets Supplied[[]string] } type versionInput struct { Scope string Format string } type versionDocument struct { Name string `yaml:"name"` Version string `yaml:"version"` Revision string `yaml:"revision"` } func (a *App) builtinCommands() []Declaration { help := captureCommand(CommandSpec[helpInput]{ Name: "help", Summary: "Show help for commands.", Description: "Describe registered commands and their inputs.", Arguments: []Argument[helpInput]{RepeatArgument(ArgumentMeta{Name: "COMMAND", Usage: "registered command path"}, 0, -1, func(value string) (string, error) { return value, nil }, func(input *helpInput, value Supplied[[]string]) { input.Targets = value })}, }, func(current commandExecution, input helpInput) Outcome { return current.renderHelpTargets(input.Targets.Value) }) scopeDefault := "app" formatDefault := "text" version := NewCommand(CommandSpec[versionInput]{ Name: "version", Summary: "Show application or CommandKit version.", Description: "Report identity and build metadata without loading application configuration.", Flags: []Flag[versionInput]{ ValueFlag(FlagMeta{Name: "scope", Usage: "version subject"}, &scopeDefault, parseVersionScope, func(input *versionInput, value Supplied[string]) { input.Scope = value.Value }), ValueFlag(FlagMeta{Name: "format", Usage: "output format"}, &formatDefault, parseVersionFormat, func(input *versionInput, value Supplied[string]) { input.Format = value.Value }), }, Run: func(invocation Invocation, input versionInput) Outcome { identity := a.identity if input.Scope == "commandkit" { identity = commandKitBuildIdentity(readBuildInfo()) } if identity.Version == "" { identity.Version = "dev" } return renderVersionOutcome(identity, input.Format, invocation.Reporter()) }, }) return []Declaration{help, version} } func parseVersionScope(value string) (string, error) { if value != "app" && value != "commandkit" { return "", fmt.Errorf("scope must be app or commandkit, got %q", value) } return value, nil } func parseVersionFormat(value string) (string, error) { if value != "text" && value != "yaml" { return "", fmt.Errorf("format must be text or yaml, got %q", value) } return value, nil } func (a *App) renderHelpViewOutcomeWithSession(view commandmeta.HelpView, reporter *Reporter, session *internalcobra.Session) Outcome { content, failure := session.RenderHelpView(view) if failure != nil { if failure.Usage { return Usage(failure.Message) } return Failure(failure.Message) } _ = reporter.RenderOutput(func(w io.Writer) error { _, err := io.WriteString(w, content) return err }) return Success() } func renderVersionOutcome(identity Identity, format string, reporter *Reporter) Outcome { if identity.Version == "" { identity.Version = "dev" } switch format { case "text": line := identity.Name + " " + identity.Version if identity.Revision != "" { line += " (" + identity.Revision + ")" } _ = reporter.RenderOutput(func(w io.Writer) error { _, err := io.WriteString(w, line+"\n") return err }) case "yaml": _ = reporter.RenderOutput(func(w io.Writer) error { return encodeYAML(w, versionDocument{Name: identity.Name, Version: identity.Version, Revision: identity.Revision}) }) default: return Usage(fmt.Sprintf("unsupported version format %q", format)) } return Success() } func encodeYAML(writer io.Writer, document any) error { encoder := yaml.NewEncoder(writer) encoder.SetIndent(2) return encoder.Encode(document) } func readBuildInfo() *debug.BuildInfo { info, ok := debug.ReadBuildInfo() if !ok { return nil } return info } func commandKitBuildIdentity(info *debug.BuildInfo) Identity { identity := Identity{Name: "CommandKit", Version: "dev"} if info == nil { return identity } var module *debug.Module if info.Main.Path == commandKitModulePath { module = &info.Main } else { for _, dependency := range info.Deps { if dependency != nil && dependency.Path == commandKitModulePath { module = dependency break } } } if module == nil { return identity } version := module.Version if module.Replace != nil { version = module.Replace.Version } if version != "" && version != "(devel)" { identity.Version = version } return identity }