Add invocation-owned Cobra sessions
This commit is contained in:
@@ -58,52 +58,6 @@ type Result struct {
|
||||
Failure *Failure
|
||||
}
|
||||
|
||||
// RenderHelp renders registered metadata for the exact command path. It uses
|
||||
// Cobra's usage metadata and returns backend failures as neutral message data.
|
||||
func RenderHelp(root *Command, path []string) (string, *Failure) {
|
||||
if root == nil {
|
||||
return "", &Failure{Message: "command tree is nil"}
|
||||
}
|
||||
buffer := new(bytes.Buffer)
|
||||
options := normalizeOptions(ParseOptions{Context: context.Background(), In: strings.NewReader(""), Out: buffer, Err: buffer})
|
||||
backendRoot, nodes := buildTree(root, options)
|
||||
selected, remaining, err := backendRoot.Find(path)
|
||||
if err != nil {
|
||||
return "", &Failure{Message: err.Error()}
|
||||
}
|
||||
declaration := nodes[selected]
|
||||
selectedPath, pathOK := pathFor(selected, nodes)
|
||||
if declaration == nil || !pathOK || len(remaining) != 0 || !equalPath(path, selectedPath) {
|
||||
return "", &Failure{Message: "help path is not registered"}
|
||||
}
|
||||
selected.SetOut(buffer)
|
||||
selected.SetErr(buffer)
|
||||
if err := selected.Usage(); err != nil {
|
||||
return "", &Failure{Message: err.Error()}
|
||||
}
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
// UsageLine returns Cobra's usage line for an exact registered command path.
|
||||
func UsageLine(root *Command, path []string) (string, *Failure) {
|
||||
if root == nil {
|
||||
return "", &Failure{Message: "command tree is nil"}
|
||||
}
|
||||
options := normalizeOptions(ParseOptions{Context: context.Background(), In: strings.NewReader(""), Out: io.Discard, Err: io.Discard})
|
||||
backendRoot, nodes := buildTree(root, options)
|
||||
selected, remaining, err := backendRoot.Find(path)
|
||||
declaration := nodes[selected]
|
||||
selectedPath, pathOK := pathFor(selected, nodes)
|
||||
if err != nil || declaration == nil || !pathOK || len(remaining) != 0 || !equalPath(path, selectedPath) {
|
||||
return "", &Failure{Message: "usage path is not registered"}
|
||||
}
|
||||
line := selected.UseLine()
|
||||
if selected.HasSubCommands() {
|
||||
line += " [command]"
|
||||
}
|
||||
return line, nil
|
||||
}
|
||||
|
||||
// ParseOptions makes the parser's context and streams explicit. Nil writers
|
||||
// are replaced with io.Discard.
|
||||
type ParseOptions struct {
|
||||
@@ -113,29 +67,43 @@ type ParseOptions struct {
|
||||
Err io.Writer
|
||||
}
|
||||
|
||||
// Parse constructs a fresh Cobra tree and parses one argv. It routes only the
|
||||
// leading command words, stopping permanently at the first option or --.
|
||||
func Parse(root *Command, argv []string, options ParseOptions) Result {
|
||||
result := Result{}
|
||||
// Session owns one mutable backend tree for a single command invocation.
|
||||
type Session struct {
|
||||
root *spfcobra.Command
|
||||
declarations map[*spfcobra.Command]*Command
|
||||
}
|
||||
|
||||
// Prepare constructs a fresh backend tree for one invocation.
|
||||
func Prepare(root *Command, options ParseOptions) *Session {
|
||||
session := &Session{}
|
||||
if root == nil {
|
||||
return failure(result, "command tree is nil")
|
||||
return session
|
||||
}
|
||||
options = normalizeOptions(options)
|
||||
backendRoot, nodes := buildTree(root, options)
|
||||
session.root, session.declarations = buildTree(root, options)
|
||||
return session
|
||||
}
|
||||
|
||||
// Parse routes leading command words and parses flags on this session's tree.
|
||||
func (session *Session) Parse(argv []string) Result {
|
||||
result := Result{}
|
||||
if session == nil || session.root == nil {
|
||||
return failure(result, "command tree is nil")
|
||||
}
|
||||
leading, tail := splitRoutingPrefix(argv)
|
||||
selected, unconsumed, err := backendRoot.Find(leading)
|
||||
selected, unconsumed, err := session.root.Find(leading)
|
||||
if err != nil {
|
||||
return failure(result, err.Error())
|
||||
}
|
||||
if selected == nil {
|
||||
return failure(result, "no command was selected")
|
||||
}
|
||||
selectedNode := nodes[selected]
|
||||
selectedNode := session.declarations[selected]
|
||||
if selectedNode == nil {
|
||||
return failure(result, "selected command is not part of the declared tree")
|
||||
}
|
||||
var pathOK bool
|
||||
result.Path, pathOK = pathFor(selected, nodes)
|
||||
result.Path, pathOK = pathFor(selected, session.declarations)
|
||||
if !pathOK {
|
||||
return failure(result, "selected command is not part of the declared tree")
|
||||
}
|
||||
@@ -170,6 +138,67 @@ func Parse(root *Command, argv []string, options ParseOptions) Result {
|
||||
return result
|
||||
}
|
||||
|
||||
// RenderHelp renders registered metadata for an exact path on this session.
|
||||
func (session *Session) RenderHelp(path []string) (string, *Failure) {
|
||||
if session == nil || session.root == nil {
|
||||
return "", &Failure{Message: "command tree is nil"}
|
||||
}
|
||||
selected, remaining, err := session.root.Find(path)
|
||||
if err != nil {
|
||||
return "", &Failure{Message: err.Error()}
|
||||
}
|
||||
declaration := session.declarations[selected]
|
||||
selectedPath, pathOK := pathFor(selected, session.declarations)
|
||||
if declaration == nil || !pathOK || len(remaining) != 0 || !equalPath(path, selectedPath) {
|
||||
return "", &Failure{Message: "help path is not registered"}
|
||||
}
|
||||
buffer := new(bytes.Buffer)
|
||||
previousOut, previousErr := selected.OutOrStdout(), selected.ErrOrStderr()
|
||||
selected.SetOut(buffer)
|
||||
selected.SetErr(buffer)
|
||||
defer func() {
|
||||
selected.SetOut(previousOut)
|
||||
selected.SetErr(previousErr)
|
||||
}()
|
||||
if err := selected.Usage(); err != nil {
|
||||
return "", &Failure{Message: err.Error()}
|
||||
}
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
// UsageLine returns Cobra's usage line for an exact path on this session.
|
||||
func (session *Session) UsageLine(path []string) (string, *Failure) {
|
||||
if session == nil || session.root == nil {
|
||||
return "", &Failure{Message: "command tree is nil"}
|
||||
}
|
||||
selected, remaining, err := session.root.Find(path)
|
||||
declaration := session.declarations[selected]
|
||||
selectedPath, pathOK := pathFor(selected, session.declarations)
|
||||
if err != nil || declaration == nil || !pathOK || len(remaining) != 0 || !equalPath(path, selectedPath) {
|
||||
return "", &Failure{Message: "usage path is not registered"}
|
||||
}
|
||||
line := selected.UseLine()
|
||||
if selected.HasSubCommands() {
|
||||
line += " [command]"
|
||||
}
|
||||
return line, nil
|
||||
}
|
||||
|
||||
// RenderHelp renders a fresh command tree for one help request.
|
||||
func RenderHelp(root *Command, path []string) (string, *Failure) {
|
||||
return Prepare(root, ParseOptions{}).RenderHelp(path)
|
||||
}
|
||||
|
||||
// UsageLine resolves a path on a fresh command tree.
|
||||
func UsageLine(root *Command, path []string) (string, *Failure) {
|
||||
return Prepare(root, ParseOptions{}).UsageLine(path)
|
||||
}
|
||||
|
||||
// Parse constructs a fresh command tree and parses one argv.
|
||||
func Parse(root *Command, argv []string, options ParseOptions) Result {
|
||||
return Prepare(root, options).Parse(argv)
|
||||
}
|
||||
|
||||
func normalizeOptions(options ParseOptions) ParseOptions {
|
||||
if options.Context == nil {
|
||||
options.Context = context.Background()
|
||||
|
||||
Reference in New Issue
Block a user