cli API

cli

package

API reference for the cli package.

S
struct

confirmModel

pkg/v1/cli/prompt_yes_no.go:19-26
type confirmModel struct

Methods

Init
Method

Returns

func (confirmModel) Init() tea.Cmd
{
	return nil
}
Update
Method

Parameters

msg tea.Msg

Returns

func (confirmModel) Update(msg tea.Msg) (tea.Model, tea.Cmd)
{
	switch msg := msg.(type) {
	case tea.KeyMsg:
		switch msg.String() {
		case "ctrl+c":
			m.err = fmt.Errorf("interrupted")
			return m, tea.Quit
		case "y", "Y":
			m.choice = true
		case "n", "N":
			m.choice = false
		case "left", "right", "h", "l", "tab":
			m.choice = !m.choice
		case "enter":
			m.submitted = true
			return m, tea.Quit
		}
	}
	return m, nil
}
View
Method

Returns

string
func (confirmModel) View() string
{
	var s strings.Builder
	s.WriteString("\n" + lipgloss.NewStyle().Bold(true).Render(m.prompt) + "\n\n")

	yStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("241")).MarginRight(2)
	nStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("241")).MarginRight(2)

	if m.choice {
		yStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("205")).Bold(true).MarginRight(2)
	} else {
		nStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("205")).Bold(true).MarginRight(2)
	}

	s.WriteString(yStyle.Render(m.yesText))
	s.WriteString(nStyle.Render(m.noText))
	s.WriteString("\n")

	return s.String()
}

Fields

Name Type Description
prompt string
yesText string
noText string
choice bool
err error
submitted bool
F
function

initialConfirmModel

Parameters

prompt
string
yesText
string
noText
string
defaultChoice
bool

Returns

pkg/v1/cli/prompt_yes_no.go:28-35
func initialConfirmModel(prompt, yesText, noText string, defaultChoice bool) confirmModel

{
	return confirmModel{
		prompt:  prompt,
		yesText: yesText,
		noText:  noText,
		choice:  defaultChoice,
	}
}
T
type

Base

Base is an alias for builder.Base to be used by consumers

pkg/v1/cli/cli.go:24-24
type Base builder.Base
S
struct

Command

Command represents a CLI command.

pkg/v1/cli/cli.go:27-35
type Command struct

Methods

StartProgressBar starts a progress bar with a message and a total. The progress bar is stopped automatically when it reaches the total or manually by calling the Stop method on the returned model.

Parameters

message string
total int

Returns

func (Command) StartProgressBar(message string, total int) *ProgressBarModel
{
	p := progress.New(
		progress.WithGradient("#277eff", "#e0388d"),
		progress.WithoutPercentage(),
	)
	m := progressComponent{
		progress: p,
		total:    total,
		message:  message,
	}

	prog := tea.NewProgram(m)

	go func() {
		if _, err := prog.Run(); err != nil {
			fmt.Println("Error running progress bar:", err)
		}
	}()

	return &ProgressBarModel{
		program: prog,
		total:   total,
		current: 0,
	}
}
Example
progressBar := myApp.CLI.StartProgressBar("Loading the batmobile...", 100)
for i := 0; i < 100; i++ {
	progressBar.Increment(1)
	time.Sleep(50 * time.Millisecond)
}
StartSpinner
Method

StartSpinner starts a spinner with a message. The spinner can be stopped by calling the Stop method on the returned model.

Parameters

message string

Returns

func (Command) StartSpinner(message string) *SpinnerModel
{
	p := tea.NewProgram(initialSpinnerComponent(message))
	quit := make(chan struct{})

	go func() {
		if _, err := p.Run(); err != nil {
			fmt.Println("Error running spinner:", err)
		}
		close(quit)
	}()

	return &SpinnerModel{
		program: p,
	}
}
Example
spinner := myApp.CLI.StartSpinner("Loading the batmobile...")
time.Sleep(3 * time.Second)
spinner.Stop()
ConfirmAction
Method

ConfirmAction prompts the user to confirm an action, it supports customizing the prompt and the text for the "yes" and "no" options. If the user does not provide an answer, the default choice is used.

Parameters

prompt string
yesText string
noText string
defaultChoice bool

Returns

bool
error
func (*Command) ConfirmAction(prompt, yesText, noText string, defaultChoice bool) (bool, error)
{
	// If custom text provided, use it, assuming 'y' maps to yesText and 'n' to noText visual
	p := tea.NewProgram(initialConfirmModel(prompt, yesText, noText, defaultChoice))
	m, err := p.Run()
	if err != nil {
		return false, err
	}

	if m, ok := m.(confirmModel); ok {
		if m.err != nil {
			return false, m.err
		}
		if !m.submitted {
			return defaultChoice, nil
		}
		return m.choice, nil
	}

	return false, fmt.Errorf("could not retrieve confirmation")
}
Example
confirm, err := myApp.CLI.ConfirmAction(
	"Do you like Batman?",
	"Yes", "No",
	true,
)
if err != nil {
	fmt.Println(err)
	return err
}
if confirm {
	fmt.Println("Everybody likes Batman!")
} else {
	fmt.Println("You don't like Batman...")
}
Name
Method

Name returns the name of the command

Returns

string
func (*Command) Name() string
{
	return c.Use
}
Execute
Method

Execute runs the command

Returns

error
func (*Command) Execute() error
{
	if c.app == nil {
		return fmt.Errorf("no application initialized. Use NewCommandFromStruct")
	}
	return c.app.Run()
}
AddCommand
Method

AddCommand adds a dynamic command to the application.

Parameters

name string
func (*Command) AddCommand(name string, cmd *parser.CommandNode)
{
	c.app.AddCommand(name, cmd)
}
SetTranslator
Method

SetTranslator sets the translator for the application.

Parameters

func (*Command) SetTranslator(tr help.Translator)
{
	c.app.SetTranslator(tr)
	if c.manCmd != nil {
		c.manCmd.translator = tr
	}
}
SetName
Method

SetName sets the name of the root command.

Parameters

name string
func (*Command) SetName(name string)
{
	c.app.SetName(name)
}
Reload
Method

Reload re-parses the root struct to pick up dynamic changes.

Returns

error
func (*Command) Reload() error
{
	return c.app.Reload()
}
GetRoot
Method

GetRoot returns the underlying root struct of the command.

Returns

any
func (*Command) GetRoot() any
{
	return c.root
}
SelectOption
Method

SelectOption prompts the user to select an option from a list of options.

Parameters

prompt string
options []string

Returns

string
error
func (*Command) SelectOption(prompt string, options []string) (string, error)
{
	if strings.Contains(prompt, "%d") {
		prompt = fmt.Sprintf(prompt, len(options))
	}
	p := tea.NewProgram(initialListModel(prompt, options))
	m, err := p.Run()
	if err != nil {
		return "", err
	}

	if m, ok := m.(listModel); ok {
		if m.err != nil {
			return "", m.err
		}
		return m.selected, nil
	}

	return "", fmt.Errorf("could not retrieve selection")
}
Example
selected, err := myApp.CLI.SelectOption(
	"What is your preferred hero?",
	[]string{"Batman", "Ironman", "Spiderman", "Robin", "None"},
)
if err != nil {
	fmt.Println(err)
	return err
}
fmt.Printf("You selected %s!\n", selected)
Table
Method

Table renders a styled table with the given headers and data using lipgloss.

Parameters

headers []string
data [][]string

Returns

error
func (*Command) Table(headers []string, data [][]string) error
{
	headerStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("205")).Bold(true).Padding(0, 1)
	styledHeaders := make([]string, len(headers))
	for i, h := range headers {
		styledHeaders[i] = headerStyle.Render(h)
	}

	t := table.New().
		Border(lipgloss.NormalBorder()).
		BorderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("238"))).
		Headers(styledHeaders...).
		Rows(data...).
		StyleFunc(func(row, col int) lipgloss.Style {
			switch {
			default:
				if row%2 == 0 {
					return lipgloss.NewStyle().
						Foreground(lipgloss.Color("252")).
						Padding(0, 1)
				}
				return lipgloss.NewStyle().
					Foreground(lipgloss.Color("246")).
					Padding(0, 1)
			}
		})

	fmt.Println(t)
	return nil
}
Example
err := myApp.CLI.Table(
	[]string{"Name", "Age"},
	[][]string{
		{"Batman", "35"},
		{"Robin", "25"},
	},
)
PromptText
Method

PromptText prompts the user to input a text, it supports customizing the prompt and the placeholder.

Parameters

prompt string
placeholder string

Returns

string
error
func (*Command) PromptText(prompt, placeholder string) (string, error)
{
	p := tea.NewProgram(initialTextInputModel(prompt, placeholder))
	m, err := p.Run()
	if err != nil {
		return "", err
	}

	if m, ok := m.(textInputModel); ok {
		if m.err != nil {
			return "", m.err
		}
		if m.textInput.Value() == "" {
			return placeholder, nil
		}
		return m.textInput.Value(), nil
	}

	return "", fmt.Errorf("could not retrieve manual input")
}
Example
response, err := myApp.CLI.PromptText(
	"What is your name?",
	"Bruce Wayne",
)
if err != nil {
	fmt.Println(err)
	return err
}
fmt.Printf("Hello %s!\n", response)

Fields

Name Type Description
Use string
Short string
Long string
root any
app *builder.App
manCmd *ManCmd
S
struct

ManCmd

ManCmd is the command to generate the man page

pkg/v1/cli/cli.go:38-42
type ManCmd struct

Methods

Run
Method

Run runs the man command

Returns

error
func (*ManCmd) Run() error
{
	man, err := GenerateManPage(c.root, c.translator)
	if err != nil {
		return err
	}

	fmt.Println(man)
	return nil
}
Example
manCmd := &cli.ManCmd{root: s}
err := parser.Run(manCmd)

Fields

Name Type Description
root any
translator help.Translator
F
function

NewCommandFromStruct

NewCommandFromStruct returns a new Command created from a struct.

Parameters

s
any

Returns

error
pkg/v1/cli/cli.go:112-136
func NewCommandFromStruct(s any) (*Command, error)

{
	app, err := builder.New(s)
	if err != nil {
		return nil, err
	}

	// We inject the man command
	manCmd := &ManCmd{root: s}
	manNode, err := parser.Parse("man", manCmd)
	if err == nil {
		manNode.Description = "Generate man page"
		app.AddCommand("man", manNode)
	}

	node := app.RootNode

	c := &Command{
		Use:    node.Name,
		Short:  node.Description,
		root:   s,
		app:    app,
		manCmd: manCmd,
	}
	return c, nil
}

Example

type RootCmd struct {
	cli.Base
	Poll PollCmd `cmd:"poll" help:"Ask the user preferred hero"`
	Man  ManCmd  `cmd:"man" help:"Generate man page"`
}

cmd, err := cli.NewCommandFromStruct(&RootCmd{})
if err != nil {
	return nil, err
}

err := cmd.Execute()
F
function

GenerateManPage

GenerateManPage generates a man page for the declarative struct

Parameters

root
any

Returns

string
error
pkg/v1/cli/cli.go:155-178
func GenerateManPage(root any, tr help.Translator) (string, error)

{
	t := reflect.TypeOf(root)
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}
	cleanRoot := reflect.New(t).Interface()

	node, err := parser.Parse("root", cleanRoot)
	if err != nil {
		return "", err
	}

	description := node.Description
	if tr != nil {
		description = tr(cleanKey(description))
	}

	d := roff.NewDocument()
	d.Heading(1, node.Name, description, time.Now())

	docNode(d, node, tr)

	return d.String(), nil
}

Example

	type RootCmd struct {
		cli.Base
		Poll PollCmd `cmd:"poll" help:"Ask the user preferred hero"`
		Man  ManCmd  `cmd:"man" help:"Generate man page"`
	}

	man, err := cli.GenerateManPage(&RootCmd{}, nil)
	if err != nil {
		return "", err
	}

GenerateManPage automatically uses a zero-value instance of the root struct
to exclude any dynamic commands.
F
function

docNode

docNode recursively documents a command node and its children.

pkg/v1/cli/cli.go:181-216
func docNode(d *roff.Document, node *parser.CommandNode, tr help.Translator)

{
	description := node.Description
	if tr != nil {
		description = tr(cleanKey(description))
	}

	d.Section("subcommand " + node.Name)
	d.Indent(4)
	d.Text(description)
	d.IndentEnd()
	d.EndSection()

	// Options
	if len(node.Flags) > 0 {
		d.SubSection("Options")
		for name, meta := range node.Flags {
			short := ""
			if meta.Short != "" {
				short = fmt.Sprintf("-%s, ", meta.Short)
			}
			desc := meta.Description
			if tr != nil {
				desc = tr(cleanKey(desc))
			}
			d.Text(fmt.Sprintf("  %s--%s  %s\n", short, name, desc))
		}
		d.EndSection()
	}

	// Commands
	if len(node.Children) > 0 {
		for _, child := range node.Children {
			docNode(d, child, tr)
		}
	}
}
F
function

cleanKey

Parameters

key
string

Returns

string
pkg/v1/cli/cli.go:218-223
func cleanKey(key string) string

{
	if strings.HasPrefix(key, "pr:") {
		return strings.TrimPrefix(key, "pr:")
	}
	return key
}
T
type

progressMsg

pkg/v1/cli/prompt_progressbar.go:20-20
type progressMsg float64
T
type

titleMsg

pkg/v1/cli/prompt_progressbar.go:21-21
type titleMsg string
S
struct

stopMsg

pkg/v1/cli/prompt_progressbar.go:22-22
type stopMsg struct
S
struct

progressComponent

pkg/v1/cli/prompt_progressbar.go:24-29
type progressComponent struct

Methods

Init
Method

Returns

func (progressComponent) Init() tea.Cmd
{
	return nil
}
Update
Method

Parameters

msg tea.Msg

Returns

func (progressComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd)
{
	switch msg := msg.(type) {
	case tea.KeyMsg:
		if msg.Type == tea.KeyCtrlC {
			return m, tea.Quit
		}
	case progressMsg:
		var cmd tea.Cmd
		if m.current < m.total {
			pct := float64(m.current) / float64(m.total)
			cmd = m.progress.SetPercent(pct)
			return m, cmd
		}
	case titleMsg:
		m.message = string(msg)
	case stopMsg:
		return m, tea.Quit
	case progress.FrameMsg:
		progressModel, cmd := m.progress.Update(msg)
		m.progress = progressModel.(progress.Model)
		return m, cmd
	}
	return m, nil
}
View
Method

Returns

string
func (progressComponent) View() string
{
	pad := strings.Repeat(" ", 2)
	return "\n" +
		pad + m.message + "\n" +
		pad + m.progress.View() + "\n\n"
}

Fields

Name Type Description
progress progress.Model
total int
current int
message string
S
struct

ProgressBarModel

pkg/v1/cli/prompt_progressbar.go:67-71
type ProgressBarModel struct

Methods

Increment
Method

Parameters

inc int
func (*ProgressBarModel) Increment(inc int)
{
	m.current += inc
	if m.current >= m.total {
		m.current = m.total
		m.Stop()
		return
	}

	// Bubbles progress takes a 0-1 float.
	pct := float64(m.current) / float64(m.total)
	m.program.Send(progressMsg(pct))
}
UpdateMessage
Method

UpdateMessage updates the title logic.

Parameters

msg string
func (*ProgressBarModel) UpdateMessage(msg string)
{
	m.program.Send(titleMsg(msg))
}
Stop
Method
func (*ProgressBarModel) Stop()
{
	m.program.Send(stopMsg{})
	// Allow cleanup
	time.Sleep(100 * time.Millisecond)
}

Fields

Name Type Description
program *tea.Program
total int
current int
T
type

item

pkg/v1/cli/prompt_select_option.go:29-29
type item string
S
struct

itemDelegate

pkg/v1/cli/prompt_select_option.go:33-33
type itemDelegate struct

Methods

Height
Method

Returns

int
func (itemDelegate) Height() int
{ return 1 }
Spacing
Method

Returns

int
func (itemDelegate) Spacing() int
{ return 0 }
Update
Method

Parameters

Returns

func (itemDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd
{ return nil }
Render
Method

Parameters

index int
listItem list.Item
func (itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item)
{
	i, ok := listItem.(item)
	if !ok {
		return
	}

	str := fmt.Sprintf("%d. %s", index+1, i)

	fn := itemStyle.Render
	if index == m.Index() {
		fn = func(s ...string) string {
			return selectedItemStyle.Render("> " + strings.Join(s, " "))
		}
	}

	fmt.Fprint(w, fn(str))
}
S
struct

listModel

pkg/v1/cli/prompt_select_option.go:56-60
type listModel struct

Methods

Init
Method

Returns

func (listModel) Init() tea.Cmd
{
	return nil
}
Update
Method

Parameters

msg tea.Msg

Returns

func (listModel) Update(msg tea.Msg) (tea.Model, tea.Cmd)
{
	switch msg := msg.(type) {
	case tea.KeyMsg:
		key := msg.String()
		switch key {
		case "ctrl+c":
			m.err = fmt.Errorf("interrupted")
			return m, tea.Quit
		case "enter":
			if i, ok := m.list.SelectedItem().(item); ok {
				m.selected = string(i)
			}
			return m, tea.Quit
		case "1", "2", "3", "4", "5", "6", "7", "8", "9":
			idx := int(key[0] - '1')
			items := m.list.Items()
			if idx >= 0 && idx < len(items) {
				if i, ok := items[idx].(item); ok {
					m.selected = string(i)
					return m, tea.Quit
				}
			}
		}
	case tea.WindowSizeMsg:
		m.list.SetWidth(msg.Width)
		return m, nil
	}

	var cmd tea.Cmd
	m.list, cmd = m.list.Update(msg)
	return m, cmd
}
View
Method

Returns

string
func (listModel) View() string
{
	return "\n" + m.list.View()
}

Fields

Name Type Description
list list.Model
selected string
err error
F
function

initialListModel

Parameters

prompt
string
options
[]string

Returns

pkg/v1/cli/prompt_select_option.go:62-79
func initialListModel(prompt string, options []string) listModel

{
	items := make([]list.Item, len(options))
	for i, opt := range options {
		items[i] = item(opt)
	}

	const defaultWidth = 20

	l := list.New(items, itemDelegate{}, defaultWidth, 14)
	l.Title = prompt
	l.SetShowStatusBar(false)
	l.SetFilteringEnabled(false)
	l.Styles.Title = titleStyle
	l.Styles.PaginationStyle = paginationStyle
	l.Styles.HelpStyle = helpStyle

	return listModel{list: l}
}
S
struct

SpinnerModel

pkg/v1/cli/prompt_spinner.go:20-23
type SpinnerModel struct

Methods

UpdateMessage
Method

UpdateMessage updates the spinner message dynamically.

Parameters

message string
func (*SpinnerModel) UpdateMessage(message string)
{
	if m.program != nil {
		m.program.Send(updateMsg(message))
	}
}
Example
spinner.UpdateMessage("Loading the batcave...")
Stop
Method
func (*SpinnerModel) Stop()
{
	if m.program != nil {
		m.program.Send(quitMsg{})
		// Wait for clear
		time.Sleep(100 * time.Millisecond)
	}
}

Fields

Name Type Description
program *tea.Program
quit chan struct{}
S
struct

spinnerComponent

pkg/v1/cli/prompt_spinner.go:25-29
type spinnerComponent struct

Methods

Init
Method

Returns

func (spinnerComponent) Init() tea.Cmd
{
	return m.spinner.Tick
}
Update
Method

Parameters

msg tea.Msg

Returns

func (spinnerComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd)
{
	switch msg := msg.(type) {
	case quitMsg:
		m.quitting = true
		return m, tea.Quit
	case updateMsg:
		m.message = string(msg)
		return m, nil
	}

	var cmd tea.Cmd
	m.spinner, cmd = m.spinner.Update(msg)
	return m, cmd
}
View
Method

Returns

string
func (spinnerComponent) View() string
{
	if m.quitting {
		return ""
	}
	return fmt.Sprintf("\n %s %s\n\n", m.spinner.View(), m.message)
}

Fields

Name Type Description
spinner spinner.Model
message string
quitting bool
S
struct

quitMsg

pkg/v1/cli/prompt_spinner.go:31-31
type quitMsg struct
T
type

updateMsg

pkg/v1/cli/prompt_spinner.go:33-33
type updateMsg string
F
function

initialSpinnerComponent

Parameters

message
string
pkg/v1/cli/prompt_spinner.go:35-43
func initialSpinnerComponent(message string) spinnerComponent

{
	s := spinner.New()
	s.Spinner = spinner.Dot
	s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
	return spinnerComponent{
		spinner: s,
		message: message,
	}
}
S
struct

textInputModel

pkg/v1/cli/prompt_text.go:19-23
type textInputModel struct

Methods

Init
Method

Returns

func (textInputModel) Init() tea.Cmd
{
	return textinput.Blink
}
Update
Method

Parameters

msg tea.Msg

Returns

func (textInputModel) Update(msg tea.Msg) (tea.Model, tea.Cmd)
{
	var cmd tea.Cmd

	switch msg := msg.(type) {
	case tea.KeyMsg:
		switch msg.Type {
		case tea.KeyCtrlC, tea.KeyEsc:
			m.err = fmt.Errorf("interrupted")
			return m, tea.Quit
		case tea.KeyEnter:
			return m, tea.Quit
		}

	case tea.WindowSizeMsg:
		m.textInput.Width = msg.Width
	}

	m.textInput, cmd = m.textInput.Update(msg)
	return m, cmd
}
View
Method

Returns

string
func (textInputModel) View() string
{
	var style = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
	return fmt.Sprintf(
		"%s\n%s\n",
		style.Bold(true).Render(m.prompt),
		m.textInput.View(),
	)
}

Fields

Name Type Description
textInput textinput.Model
err error
prompt string
F
function

initialTextInputModel

Parameters

prompt
string
placeholder
string

Returns

pkg/v1/cli/prompt_text.go:25-37
func initialTextInputModel(prompt, placeholder string) textInputModel

{
	ti := textinput.New()
	ti.Placeholder = placeholder
	ti.Focus()
	ti.CharLimit = 156
	ti.Width = 40
	ti.Prompt = "➜ "

	return textInputModel{
		textInput: ti,
		prompt:    prompt,
	}
}