Skip to content
maxlandon edited this page May 29, 2023 · 13 revisions

One of the key differences between this application library and most others is the ability to easily declare different "menus", in which commands (and some other things) are segregated. An example often seen out there in the security field, is programs consisting of commands ran on a server (the server menu), and commands ran on a remote host. The list of things that are specific to each menu includes:

  • Command sets/trees
  • Prompt engines and their configuration
  • Command history sources

The default menu

By default, when a new console is instantiated, a menu named "" (empty name) is created. You need to know of this menu thing, because even if you don't intend to use more than one, some functions available in them are not accessible via the Console type itself. To grab the current menu:

mainMenu := console.ActiveMenu()

In our case the base "" menu is the current one by default, so you can grab it like this, and then manipulate it. It should be better for consistency.

Creating menus

In our tutorials we will use one other menu: a client one, which might be a scenario for a SSH client.

We will separate everything related to this new menu (prompts, handlers, history, commands, etc) later.

func createMenu(c *console.Console) {
	clientMenu := c.NewMenu("client")

    // ... here we will setup the menu stuff.
}

...

func main() {
    app := console.New()

    createMenu(app)
}

Switching between menus

To make the server menu as the current one (remember the "" (default) menu is still the current):

console.SwitchMenu("client")
console.SwitchMenu("")       // Switch back to default/main

Below is the usual example of how you want to use these functions in commands. The following command "notation" is a reeflective/flags one, although you could attain the same result in a cobra RunE(cmd *cobra.Command) function.

// connectHost - A command that might be used to connect to a remote host, via SSH or something.
type connectHost struct {
        Args struct {
                URL string `description:"SSH URL of the host we want to connect to" required:"yes"`
        } `positional-args:"yes" required:"yes"`
}

func (ch *connectHost) Execute(args []string) (err error) {

        // At the beginning, we are in our main context (the default one)
        // where we have a bunch of server commands.
        current := console.CurrentMenu()  // Name: ""

        // Here we connect over to our host with our application code, etc.
        // Maybe this might give you state to use later for prompts, etc.
        err = mysshlib.Connect(ch.Args.URL)
        if err != nil {
                // In case of failure we don't switch menus.
                fmt.Println(err)
                return 
        }

        // Now we switch menus. At the next readline loop, the prompt, command set, etc. 
        // will be those of the new menu: you don't have to deal with any of this.
        console.SwitchMenu("client")

        return
}

Note that you can also safely call app.SwitchMenu("client") in asyncrhonous handlers (seen later): the shell will ensure that upon switch, the currently available commands are refreshed according to the switch.