package consoleimport ()// Prompt - A prompt is a set of functions that return the strings to print// for each prompt type. The console will call these functions to retrieve// the prompt strings to print. Each menu has its own prompt.typePrompt = ui.Prompt// Menu - A menu is a simple way to seggregate commands based on// the environment to which they belong. For instance, when using a menu// specific to some host/user, or domain of activity, commands will vary.typeMenustruct { name string active bool prompt *Prompt console *Console// Maps interrupt signals (CtrlC/IOF, etc) to specific error handlers. interruptHandlers map[error]func(c *Console)// ErrorHandler is called when an error is encountered. // // If not set, the error is printed to the console on os.Stderr. ErrorHandler ErrorHandler// Input/output channels out *bytes.Buffer// The root cobra command/parser is the one returned by the handler provided // through the `menu.SetCommands()` function. This command is thus renewed after // each command invocation/execution. // You can still use it as you want, for instance to introspect the current command // state of your menu. *cobra.Command// Command spawner cmds Commands// An error template to use to produce errors when a command is unavailable. errFilteredTemplate string// History sources peculiar to this menu. historyNames []string histories map[string]readline.History// Concurrency management mutex *sync.RWMutex}func newMenu( string, *Console) *Menu { := &Menu{console: ,name: ,Command: &cobra.Command{},out: bytes.NewBuffer(nil),interruptHandlers: make(map[error]func( *Console)),histories: make(map[string]readline.History),mutex: &sync.RWMutex{},ErrorHandler: defaultErrorHandler, }// Prompt setup := (ui.NewPrompt(.name, , .out)) .prompt = (*Prompt)()// Add a default in memory history to each menu // This source is dropped if another source is added // to the menu via `AddHistorySource()`. := .defaultHistoryName() := readline.NewInMemoryHistory() .historyNames = append(.historyNames, ) .histories[] = return}// Name returns the name of this menu.func ( *Menu) () string {return .name}// Prompt returns the prompt object for this menu.func ( *Menu) () *Prompt {return .prompt}// AddHistorySource adds a source of history commands that will// be accessible to the shell when the menu is active.func ( *Menu) ( string, readline.History) { .mutex.RLock()defer .mutex.RUnlock()iflen(.histories) == 1 && .historyNames[0] == .defaultHistoryName() {delete(.histories, .defaultHistoryName()) .historyNames = make([]string, 0) } .historyNames = append(.historyNames, ) .histories[] = }// AddHistorySourceFile adds a new source of history populated from and writing// to the specified "filepath" parameter. On the first call to this function,// the default in-memory history source is removed.func ( *Menu) ( string, string) { .mutex.RLock()defer .mutex.RUnlock()iflen(.histories) == 1 && .historyNames[0] == .defaultHistoryName() {delete(.histories, .defaultHistoryName()) .historyNames = make([]string, 0) } .historyNames = append(.historyNames, ) .histories[], _ = readline.NewHistoryFromFile()}// DeleteHistorySource removes a history source from the menu.// This normally should only be used in two cases:// - You want to replace the default in-memory history with another one.// - You want to replace one of your history sources for some reason.func ( *Menu) ( string) {if == .Name() {if != "" { = " (" + + ")" } = "local history" + }delete(.histories, )for , := range .historyNames {if == { .historyNames = append(.historyNames[:], .historyNames[+1:]...)break } }}// TransientPrintf prints a message to the console, but only if the current// menu is active. If the menu is not active, the message is buffered and will// be printed the next time the menu is active.//// The message is printed as a transient message, meaning that it will be// printed above the current prompt, effectively "pushing" the prompt down.//// If this function is called while a command is running, the console// will simply print the log below the current line, and will not print// the prompt. In any other case this function will work normally.func ( *Menu) ( string, ...any) ( int, error) { , = fmt.Fprintf(.out, , ...)if != nil {return }if !.active {fmt.Fprintf(.out, "\n")return } := .out.String() .out.Reset()return .console.TransientPrintf("%s", )}// Printf prints a message to the console, but only if the current menu// is active. If the menu is not active, the message is buffered and will// be printed the next time the menu is active.//// Unlike TransientPrintf, this function will not print the message above// the current prompt, but will instead print it below it.//// If this function is called while a command is running, the console// will simply print the log below the current line, and will not print// the prompt. In any other case this function will work normally.func ( *Menu) ( string, ...any) ( int, error) { , = fmt.Fprintf(.out, , ...)if != nil {return }if !.active {fmt.Fprintf(.out, "\n")return } := .out.String() .out.Reset()return .console.Printf("%s", )}// CheckIsAvailable checks if a target command is marked as filtered// by the console application registered/and or active filters (added// with console.Hide/ShowCommand()).// If filtered, returns a template-formatted error message showing the// list of incompatible filters. If not filtered, no error is returned.func ( *Menu) ( *cobra.Command) error {if == nil {returnnil } := .ActiveFiltersFor()iflen() == 0 {returnnil } := .errorFilteredCommandTemplate()varstrings.Builder := strutil.Template(&, , map[string]interface{}{"menu": ,"cmd": ,"filters": , })if != nil {return }returnerrors.New(.String())}// ActiveFiltersFor returns all the active menu filters that a given command// does not declare as compliant with (added with console.Hide/ShowCommand()).func ( *Menu) ( *cobra.Command) []string {if .Annotations == nil {if .HasParent() {return .(.Parent()) }returnnil } .console.mutex.Lock()defer .console.mutex.Unlock()// Get the filters on the command := .Annotations[CommandFilterKey]var []stringfor , := rangestrings.Split(, ",") {for , := range .console.filters {if != "" && == { = append(, ) } } }iflen() > 0 || !.HasParent() {return }// Any parent that is hidden make its whole subtree hidden also.return .(.Parent())}// SetErrFilteredCommandTemplate sets the error template to be used// when a called command can't be executed because it's mark filtered.func ( *Menu) ( string) { .errFilteredTemplate = }// resetPreRun is called before each new read line loop and before arbitrary RunCommand() calls.// This function is responsible for resetting the menu state to a clean state, regenerating the// menu commands, and ensuring that the correct prompt is bound to the shell.func ( *Menu) () { .mutex.Lock()defer .mutex.Unlock()// Commandsif .cmds != nil { .Command = .cmds() }if .Command == nil { .Command = &cobra.Command{Annotations: make(map[string]string), } }// Hide commands that are not available .hideFilteredCommands(.Command)// Reset or adjust any buffered command output. .resetCmdOutput() // Prompt binding := (*ui.Prompt)(.Prompt())ui.BindPrompt(, .console.shell) }// hide commands that are filtered so that they are not// shown in the help strings or proposed as completions.func ( *Menu) ( *cobra.Command) {for , := range .Commands() {// Don't override commands if they are already hiddenif .Hidden {continue }if := .ActiveFiltersFor(); len() > 0 { .Hidden = true } }}func ( *Menu) () { := strings.TrimSpace(.out.String())// If our command has printed everything to stdout, nothing to do.iflen() == 0 || == "" { .out.Reset()return }// Add two newlines to the end of the buffer, so that the // next command will be printed slightly below the current one. .out.WriteString("\n")}func ( *Menu) () string {varstringif .name != "" { = " (" + .name + ")" }return"local history" + }func ( *Menu) ( []string) string {if .errFilteredTemplate != "" {return .errFilteredTemplate }return`Command {{.cmd.Name}} is only available for: {{range .filters }} - {{.}} {{end}}`}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.