New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

git.sr.ht/~slukits/term

Package Overview
Dependencies
Alerts
File Explorer
Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

git.sr.ht/~slukits/term

  • v0.0.0-20250208161133-10cece688a7e
  • Go
  • Socket score

Version published
Created
Source

term -- for simple terminal apps

This package augment go's term package to provide a context oriented, REPL-like, self explaining and easy to consume terminal ui api. E.g.:

    package main

    import (
        "fmt"
        "log"

        "git.sr.ht/~slukits/term"
    )

    func init() { log.Default().SetFlags(0) }

    var root = term.Node{
        Label: "terminal ui", // the initial prompt
        Callback: func(_ term.Terminal) (term.Nodes, term.Inputs) {
            return term.Nodes{{
                Label: "hallo",
                Help: "prints 'world'",
                Callback: helloEndpoint,
            }, {
                Label: "echo",
                Help: "repeats a user's input",
                Callback: echoEndpoint
            }}, nil
        },
    }

    func helloEndpoint(trm term.Terminal) (term.Nodes, term.Inputs) {
        fmt.Fprintln(trm, "world")
        return nil, nil
    }

    func echoEndpoint(trm term.Terminal) (term.Nodes, term.Inputs) {
        return nil, echoInput
    }

    func echoInput(ii term.Inputs) Nodes {
        ii.SetPromptSuffix("your input please")
        ii.OnInput(func(ii term.Inputs) term.Node {
            ii.SetPromptSuffix("press any key")
            fmt.Fprintf(ii.Terminal(), 
                "your input was: '%s'\n", ii.String())
            ii.OnKeyPress(func(ii term.Inputs) term.Node {
                return root
            })
        })
    }

    func main() {
        // StartCallbackLoop blocks until user inserts ctrl-c/ctrl-d
        if err:= term.Start(root); err != nil {
            log.Fatal(err)
        }
    }

Above is the term-version of a hello world program including basic input processing. The basic idea is that a consumer of this package only needs to implement endpoints where one can choose to provide further nodes or not or an input collector. Is no input collector and no nodes returned the node is called a command node, it is called a context node if nodes are returned but no input collector and it is called an input node if an input collector is returned. I.e. helloEndpoint is a command node and echoEndpoint is an input node while root is a context node. Since an endpoint gets an Terminal implementation to write to, one can easily test endpoints against one owns implementations. Of course in this case it is advisable to define ones own interface to not have the test implementations brake if the Terminal interface gets extended.

Note in the term-ui the question mark user input shows the ui-help display while the pressing enter without any other input shows the context-help display. The ui supports auto-completion and cycling through autocompletion along with the feature of golang.org/x/term.

Specifics

  • a consumer of the term package ideally doesn't need to write functional ui-code, i.e. the ui is data driven.

  • the terminal ui should be able to make hundreds of features available with a few keystrokes.

  • the terminal ui should be self explaining.

The data structure and its interpretation

uiDef := term.Node {
    Label: "prompt",
    Short: "prmpt",
    Help: "the first node of an ui-definition is its root node",
    Callback: func(_ term.Terminal) term.Nodes {
        return term.Nodes{
            {Label: "node_1"}, /* ... */, {Label: "node_n"}}
    },
}

The Node type is the central (data) type that drives a term-ui. Let n be a term.Node.

n is called invalid if its label is zero or if its callback is zero or if two nodes n_i, n_j in n.Callback's return value exist with n_i =/= n_j and n_i.Label == n_j.Label or n_i.Short == n_j.Short.

For the following definitions it is assumed that a node is valid.

Let n be a term.Node then n is called a command-node iff n.Callback's return value is zero.

Let n be a term.Node then n is called a context-switch iff n.Callback's return value is not zero. A node n' which is in the return value of n.Callback is called context node of n. Note n.Callback denotes the set of n's callback nodes.

Let n and m be term.Nodes then it is said that m branches of from n iff m is in n.Callback.

Let n and m be term.Nodes then m is reachable from n iff a sequence of term.Nodes exists n(1), ..., n(p) with n(1) == n, n(p) == m and for n(i), n(i+1) branches n(i+1) of from n(i) while i in {1, ..., p-1}. Then the sequence n(1), ..., n(p) is called the path from n to m and denoted with P(n, m).

Is P(n, m) == {n(1), ..., n(p)} with p > 1 a path from n to m then the joining of the strings s(1), ..., s(p) with the separator : is called the string representation of P(n, m) denoted by S(P(n, m)) iff an s(i) with i in {1, ..., p-1} is n(i).Short if n(i).Short is not zero and n(i).Label otherwise while s(p) == n(p).Label.

Note S(P(n, n)) == n.Label.

Navigating the ui

////////////////////////////////////////////////////////////////////////
//////////////////////// display-content ///////////////////////////////
////////////////////////////////////////////////////////////////////////
prompt > user-input

Above sketches the terminal ui where user-input is mapped to its callback that sets typically the display-content or switches the context. Next to mapping input to callbacks also maintaining the prompt is done by the term-package.

Let r be a term.Node then r is called an term-ui's root node or its root context iff it is passed into term.StartCallbackLoop. term.StartCallbackLoop will fail if r is invalid or r.Callback is zero. The string representation of P(r, r) is called the initial prompt and is set as ui prompt for the user's first input.

Note the prompt suffix -- > in above example -- is a setting of the term-package and not part of a path's string representation.

Let r be the root node of a term-ui and ui(1) is the first user input in the root context. Exists a context node n of r with n.Label == ui(1) it is said the user has n selected.

Note if the user inputs the first letter the first found node label of the current context which starts with that letter is auto completed. Pressing the tab-key provides the next label with the same first letter. Is the tab key pressed without any other input the ui starts cycling through all context nodes labels.

Note a node's context nodes are always processed alphabetically ordered by the ui.

Let r be the root node of a term-ui and n a selected node of r's context. Then n.Callback is executed. Is its return value not zero a context switch happens:

  • the prompt r.Label > is transformed to r.Short: n(i).Label > (is r.Short unset r.Label is used instead).

  • n(i) context nodes become selectable while nodes from the root context can't be selected anymore.

  • the backspace key becomes available to go back to the root context.

Then we call P(r, n) the current context and its string representation the name, label or prompt of the current context.

Is P(r, m) the current context with the prompt S(P(r, m)) > , ui a user input that selects the node n of m's context. Then n's callback is executed. Is its return value not zero S(P(r, m)): n.Label > becomes the prompt and n's context-nodes become selectable while m's context nodes are not selectable anymore. Is the next user input a backspace a context switch back from P(r, n) to P(r, m) happens.

Printing to the ui

A callback is provided with a term.Terminal implementation which is also an alias for an io.Writer, i.e. the callback can write to the terminal. When the callback returns no more writes to the terminal are accepted. A terminal also provides width and height for layout calculations. term also provides control sequences like term.NL or term.Cell to create (tabular) formatting.

Has the write to the terminal more lines than its height the print to the physical terminal stops at its full height and further writes are cached. Cached terminal content is available through the PgDown key.

Help display

Let n be a node and n' one of its context nodes. Then

        n'.Label - n'.Help

is called a context help item of n. Note a context help item's print ends in a new-line. The alphabetic sequence of n's context help items prefixed by a context help header is called n's context help (section).

A keyboard key k is called special if it is either the return key, the tab key, the backspace key, the PgDown key, the PgUp key or the question mark key.

Note special keys control the ui under specific circumstances. E.g.: tab cycles through auto completions, backspace switches to the parent context or PgDown that scrolls the terminal content.

Let k be a special key then

        k.String() - ui help-text for k

is called an ui help item. Note a ui help item print ends in a new-line. The alphabetic sequence of calculated ui help items prefixed by a ui help header is called n's ui help (section).

Displaying the help display

The context help display is always automatically displayed if a context is entered the first time. It can be requested by the user pressing the enter key while the ui help display is printed if the user inserts the question mark.

FAQs

Package last updated on 08 Feb 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc