tpcli

package module
v0.3.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 19, 2021 License: Apache-2.0 Imports: 7 Imported by: 1

README

Brief

tpcli is the Three-Panel Command-Line UI. It is both a golang module and an application using that module that can communicate with external applications.

The UI

The UI is terminal-based, and presents three panels stacked one atop the other. The three panels include: general output, error output/command-history and command input. The command input panel is a single row, and supports both bash-like keybinding (e.g., ^a to go to the beginning of a line, ^e to go to the end, ^k to remove from the cursor and beyond) and command-history scrolling with up- and down-arrow keys. The error output panel can either be used to display the recent command history (that is, as commands are entered into the command input panel, they appear in a scrolling list in this panel), or it can be used for error output (actually, since the UI itself has no notion of what an "error" is, it really is just another output display). The general output is used for general messages. The panels can be arranged in any order desired, and the error panel is optional. The only selectable panel is the command input panel. ^Q or escape will cause the UI to exit (presumably returning to a shell).

As a golang Module

First, the tpcli is constructed, then started in a goroutine. The UI goroutine will send both errors and user-inputed command strings over a channel. The contents of the command input panel can be changed from the connecting application, and additional text can be added to either the ouptut panel and the error panel. If the error panel is set to a command history, any output sent to the error panel is redirected to the output panel instead.

package main

import (
	"time"

	"github.com/blorticus/tpcli"
)

func main() {
	ui := tpcli.NewUI().ChangeStackingOrderTo(tpcli.CommandGeneralError)
	go ui.Start()

	for {
		switch <-ui.ChannelOfEnteredCommands() {
		case "quit":
			ui.Stop()
		case "time":
			hour, min, sec := time.Now().Clock()
			ui.FmtToGeneralOutput("The time of day is: %02d:%02d:%02d", hour, min, sec)
		default:
			ui.AddStringToErrorOutput("You can only ask for the time, I'm afraid.")
		}

	}
}

Note that ^q and the escape key will both cause the UI to exit.

As an Application

If the three-panel CLI is run as an application, it will bind to and listen on either a Unix (SOCK_STREAM) socket or a TCP socket. Messages are delivered over this socket. Messages sent from the application are commands that have been fully input (that is, some text was entered in the command input panel, and the user hit enter). Messages to the application are output to general output or the error ouput box (if the box isn't a command-history). A message is JSON encoded, as follows:

{
    "type": "$type",
    "message": "$message"
}

$type must be one of the following:

 protocol_error
 input_command_received
 input_command_replacement
 general_output
 error_output
 user_exited

The application will emit "protocol_error" and "input_command_received" messages, and will receive "input_command_replacement", "general_output" and "error_output". It will silently ignore any non-supported message type value and any message received that is intended only for output (i.e., "protocol_error" and "input_command_received").

A protocol_error is a general error message for the application's peer. The $message contains a text string for the error.

An input_command_received is a command that the user entered (including, possibly, the empty command). The $message is the command value. It excludes the trailing newline.

An input_command_replacement is text that should be placed in the command panel. The $message is the command, which will be interpretted as UTF-8. Any non-printable characters are ignored. A protocol_error is raised if it contains a newline and that newline is not the last character.

A general_output is UTF-8 text that is appended to the general output panel. A newline is appended to any existing text, then the new $message text is added. Newlines are permitted. Other non-printable characters are ignored.

An error_output is text that is appended to the error box. If the application is configured to use command history in that box, the message is delivered to the general output panel instead.

The application is invoked thusly:

tpcli <bind> [-order <panel_order>] [-debug <debug_file_path>]

where <bind> is either -unix <path/to/socket> or -tcp <ip>:<port>; <panel_order> is the order in which the panels are stacked. The default bind is -tcp localhost:6000. The <panel_order> is a three letter sequence, with c representing the command entry panel, h representing the command-history panel, e representing the error panel, and o representing the output panel. Thus, if one wishes to place the output panel first, then the history panel, then the command entry panel, one would provide -order ohc. ohc is the default. Only one of h or e can be provided, and each of the three letters must be unique (that is, a single panel type cannot be applied twice).

Messages as described above flow on the specified bound socket.

Documentation

Overview

Package tpcli is a golang package (and application) that provides a simple three panel terminal-based UI.

Overview

tpcli is the "Three Panel Command-line Interface". It is both a golang package and an application. This page documents the package.

The UI consists of three "panels": a command entry panel, a general output panel and third panel that is either for error output or which records the history of entered commands.

The command entry panel starts with focus. It is a single row panel which supports basic shell-emacs bindings (e.g., ^a to go to the start of the line, ^e to the end of the line) and arrow key readline-style history navigation.

The UI runs is started as a goroutine. When the user enters a string in the command entry panel and hits <enter>, the command string is delivered on a channel. Text may be written to the general output panel. The third panel serves one of two functions. By default, it is an error output panel. It is just like the general output panel, and differs only semantically. It may alternatively be set to command history panel. In this case, every time the user enters a command string, it is appended to this panel, providing a command history.

The user may use <tab> to switch between the panels. Only the command input panel will accept input. If either of the other two panels has focus, the arrow keys may be used to scroll up or down through the text output.

The panels may be stacked in any order desired. The default order places the output panel first, then the error output panel, then the command entry panel.

If the user hits <esc> or <ctrl>-q, the UI exits. This mean it Stop()s, and an additional function is called. By default, that function is os.Exit(0). However, this may be overridden via OnUIExit().

For convenience, there is also a CommandProcessor that allows you to define patterns for possible commands, and associate those will callback methods when the user enters those commands.

Example

ui := tpcli.NewUI().ChangeStackingOrderTo(tpcli.CommandGeneralError)
go ui.Start()
for {
    nextCommand := <-ui.ChannelOfEnteredCommands()
        switch nextCommand {
            "quit":
                ui.Stop()
            "time":
                hour, min, sec := time.Now().Clock()
                ui.FmtToGeneralOutput("The time of day is: %02d:%02d:%02d", hour, min, sec)
            default:
                ui.AddStringToErrorOutput("You can only ask for the time, I'm afraid.")
    }
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type CommandProcessor

type CommandProcessor struct {
	// contains filtered or unexported fields
}

CommandProcessor is a helper for processing user commands input in the Tpcli command entry panel. It is supplied regular expression matchers, and if any of them match a user string, an associated callback function is invoked. If none of the supplied matchers match the command string, this is indicated.

Example

A general example for CommandProcessor

package main

import (
	"fmt"
	"os"
	"regexp"
	"strconv"

	"github.com/blorticus/tpcli"
)

func main() {
	var ui *tpcli.Tpcli

	cp := tpcli.NewCommandProcessor().
		WhenCommandMatches(`add (\d+) to (\d+)`, func(matchGroups []string) error {
			x, err := strconv.Atoi(matchGroups[1])
			if err != nil {
				return err
			}

			y, err := strconv.Atoi(matchGroups[2])
			if err != nil {
				return nil
			}

			ui.AddStringToGeneralOutput(fmt.Sprintf("Sum is: %d", x+y))
			return nil
		}).
		WhenCommandMatches(regexp.MustCompile(`^quit$`), func(matchGroups []string) error {
			os.Exit(0)
			return nil
		})

	ui.Start()

	for {
		nextCommand := <-ui.ChannelOfEnteredCommands()
		commandMatchedSomething, errorReturnedByCallback := cp.ProcessCommandString(nextCommand)

		if !commandMatchedSomething {
			ui.AddStringToErrorOutput("Command not understood")
		}

		if errorReturnedByCallback != nil {
			ui.AddStringToErrorOutput(errorReturnedByCallback.Error())
		}
	}
}
Output:

func NewCommandProcessor

func NewCommandProcessor() *CommandProcessor

NewCommandProcessor creates a new, empty command processor

func (*CommandProcessor) ProcessCommandString

func (processor *CommandProcessor) ProcessCommandString(commandString string) (matchesAnyDefinedPattern bool, errorFromCallback error)

ProcessCommandString accepts a commandString and matches it against all matchers previously supplied to this ComamndProcessor, in the order that they were provided. On the first match, the asssociated callback is invoked and this method returns true and the error from the callback. If no pre-defined matchers match, then this method returns false and the string "Command not understood"

func (*CommandProcessor) WhenCommandMatches

func (processor *CommandProcessor) WhenCommandMatches(pattern interface{}, doCallback func([]string) error) *CommandProcessor

WhenCommandMatches adds a matcher with its callback. 'pattern' may be either a string or a *regexp.Regexp. If it is a string, then it is fed to regexp.MustCompile (and will panic if the compilation fails). If it is neither type, this method panics

type ReadlineHistory added in v0.3.0

type ReadlineHistory struct {
	// contains filtered or unexported fields
}

ReadlineHistory stores items in an inverted stack and allows moving up and down that stack without removing the stack items. One can move Up this inverted stack (toward the first entry) or Down this inverted stack (toward the last entry). These are "Up" and "Down" because they map to arrow keys used in a cli readline. The empty string is a not a valid entry and an effort to add an empty string is ignored. Logically, the stack always has at least one item, and the last item is always the empty string. If one moves Up from the first item, the first item is returned and the iterator doesn't move. If one moves Down from the real last item, the empty string entry is returned. If one attempts to move Down from this empty string, the empty string is returned and the iterator does not move. If items are added, ResetIteration() should be called before trying to move Up or Down. If this is not done, the results are undefined.

func NewReadlineHistory added in v0.3.0

func NewReadlineHistory(maximumHistoryEntries uint) *ReadlineHistory

NewReadlineHistory creates a ReadlineHistory which will contain up to maximumHistoryEntries items. If the stack already has that number of entries and an item is added (via AddItem), then the least recently added entry is popped off the stack before the new item is added.

func (*ReadlineHistory) AddItem added in v0.3.0

func (history *ReadlineHistory) AddItem(item string)

AddItem adds an item to the end of the inverted stack.

func (*ReadlineHistory) Down added in v0.3.0

func (history *ReadlineHistory) Down() string

Down moves the iterator down the inverted stack, returning the item at the new iterator location.

func (*ReadlineHistory) ResetIteration added in v0.3.0

func (history *ReadlineHistory) ResetIteration()

ResetIteration returns the iterator to the last entry in the inverted stack, which is always the implicit empty string entry.

func (*ReadlineHistory) Up added in v0.3.0

func (history *ReadlineHistory) Up() string

Up moves the iterator up the inverted stack, returning the item at the new iterator location.

type StackingOrder

type StackingOrder int

StackingOrder represents the order in which the three panels should be arranged vertically. All panels consume all horizontal space (i.e., they all use the same number of columns).

const (
	CommandErrorGeneral StackingOrder = iota
	CommandGeneralError
	GeneralCommandError
	GeneralErrorCommand
	ErrorCommandGeneral
	ErrorGeneralCommand
)

Various stacking orders. "Command" is the command input panel. "General" is the general output panel. "Error" is the error (or command-history) panel.

type Tpcli

type Tpcli struct {
	// contains filtered or unexported fields
}

Tpcli provides a terminal text interfaces. It creates three "panels": a command entry panel, a general output panel and third panel that is either for error output or which records the history of entered commands. The command entry panel supports basic shell-emacs bindings (e.g., ^a to go to the start of the line, ^e to the end of the line) and arrow key readline-style history navigation.

func NewUI

func NewUI() *Tpcli

NewUI constructs the UI interface elements for the Tpcli but does not start showing them (that happens on invocation of Start())

func (*Tpcli) AddStringToErrorOutput

func (ui *Tpcli) AddStringToErrorOutput(additionalContent string)

AddStringToErrorOutput appends additionalContent to the error panel in the same way that AddStringToGeneralOutput does. However, if UsingCommandHistoryPanel is invoked, then any additionalContent submitted here is instead written to the general output panel.

func (*Tpcli) AddStringToGeneralOutput

func (ui *Tpcli) AddStringToGeneralOutput(additionalContent string)

AddStringToGeneralOutput appends additionalContent to whatever text is currently in the general output panel. A newline (\n) is appended to the text that is already there first, then the new text is appended.

func (*Tpcli) ChangeStackingOrderTo

func (ui *Tpcli) ChangeStackingOrderTo(newOrder StackingOrder) *Tpcli

ChangeStackingOrderTo changes the panel stacking order to the provided ordering

func (*Tpcli) ChannelOfEnteredCommands

func (ui *Tpcli) ChannelOfEnteredCommands() <-chan string

ChannelOfEnteredCommands is a channel that emits the commands that the user enters in the command panel. A command is a string of UTF-8 text that ends with a newline (signaled by the <enter> key). The commands strings sent on this channel omit the trailing newline.

func (*Tpcli) FmtToErrorOutput

func (ui *Tpcli) FmtToErrorOutput(format string, a ...interface{})

FmtToErrorOutput is the same as AddStringToErrorOutput, but it takes fmt.Sprintf parameters and expands them using that mechanism

func (*Tpcli) FmtToGeneralOutput

func (ui *Tpcli) FmtToGeneralOutput(format string, a ...interface{})

FmtToGeneralOutput is the same as AddStringToGeneralOutput, but it takes fmt.Sprintf parameters and expands them using that mechanism

func (*Tpcli) OnUIExit

func (ui *Tpcli) OnUIExit(functionToExecuteAfterUIExits func()) *Tpcli

OnUIExit provides a function that is executed by the Tpcli immediately after it Stops, and the UI is terminated. This function is executed when a UI exit is provided, including ^q or <esc>.

func (*Tpcli) ReplaceCommandStringWith

func (ui *Tpcli) ReplaceCommandStringWith(newString string)

ReplaceCommandStringWith writes the newString to the command panel, replacing whatever is currently there.

func (*Tpcli) Start

func (ui *Tpcli) Start()

Start instructs Tpcli to draw the UI and start handling keyboard events. This should be invoked as a goroutine.

func (*Tpcli) Stop

func (ui *Tpcli) Stop()

Stop instructs Tpcli to stop the UI, clearing it. This is not the same as an exit triggered by ^q or <esc>. This only stops the UI. It does not exit the function provided by OnUIExit.

func (*Tpcli) UsingCommandHistoryPanel

func (ui *Tpcli) UsingCommandHistoryPanel() *Tpcli

UsingCommandHistoryPanel instructs the Tcpli to use the error panel as a command history. When this is set, any command entered in the command panel is copied here after the user hits <enter>. Any text that the caller attempts to write to the error panel is redirected to the general output panel

func (*Tpcli) Write added in v0.2.0

func (ui *Tpcli) Write(p []byte) (n int, err error)

Write allows an instance of tpcli to be used as a Writer. Any bytes provided will be interpreted as an ASCII string and will be written to the General Output panel.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL