mirror of https://github.com/Hilbis/Hilbish
172 lines
4.9 KiB
Go
172 lines
4.9 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/jessevdk/go-flags"
|
||
|
|
||
|
"github.com/maxlandon/readline"
|
||
|
"github.com/maxlandon/readline/completers"
|
||
|
)
|
||
|
|
||
|
// This file shows a typical way of using readline in a loop.
|
||
|
|
||
|
func main() {
|
||
|
// Instantiate a console object
|
||
|
console := newConsole()
|
||
|
|
||
|
// Bind commands to the console
|
||
|
bindCommands()
|
||
|
|
||
|
// Setup the console completers, prompts, and input modes
|
||
|
console.setup()
|
||
|
|
||
|
// Start the readline loop (blocking)
|
||
|
console.Start()
|
||
|
}
|
||
|
|
||
|
// newConsole - Instantiates a new console with some default behavior.
|
||
|
// We modify/add elements of behavior later in setup.
|
||
|
func newConsole() *console {
|
||
|
console := &console{
|
||
|
shell: readline.NewInstance(),
|
||
|
parser: commandParser,
|
||
|
}
|
||
|
return console
|
||
|
}
|
||
|
|
||
|
// console - A simple console example.
|
||
|
type console struct {
|
||
|
shell *readline.Instance
|
||
|
parser *flags.Parser
|
||
|
}
|
||
|
|
||
|
// setup - The console sets up various elements such as the completion system, hints,
|
||
|
// syntax highlighting, prompt system, commands binding, and client environment loading.
|
||
|
func (c *console) setup() (err error) {
|
||
|
|
||
|
// Input mode & defails
|
||
|
c.shell.InputMode = readline.Vim // Could be readline.Emacs for emacs input mode.
|
||
|
c.shell.ShowVimMode = true
|
||
|
c.shell.VimModeColorize = true
|
||
|
|
||
|
// Prompt: we want a two-line prompt, with a custom indicator after the Vim status
|
||
|
c.shell.SetPrompt("readline ")
|
||
|
c.shell.Multiline = true
|
||
|
c.shell.MultilinePrompt = " > "
|
||
|
|
||
|
// Instantiate a default completer associated with the parser
|
||
|
// declared in commands.go, and embedded into the console struct.
|
||
|
// The error is muted, because we don't pass an nil parser, therefore no problems.
|
||
|
defaultCompleter, _ := completers.NewCommandCompleter(c.parser)
|
||
|
|
||
|
// Register the completer for command/option completions, hints and syntax highlighting.
|
||
|
// The completer can handle all of them.
|
||
|
c.shell.TabCompleter = defaultCompleter.TabCompleter
|
||
|
c.shell.HintText = defaultCompleter.HintCompleter
|
||
|
c.shell.SyntaxHighlighter = defaultCompleter.SyntaxHighlighter
|
||
|
|
||
|
// History: by default the history is in-memory, use it with Ctrl-R
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Start - The console has a working RPC connection: we setup all
|
||
|
// things pertaining to the console itself, and start the input loop.
|
||
|
func (c *console) Start() (err error) {
|
||
|
|
||
|
// Setup console elements
|
||
|
err = c.setup()
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("Console setup failed: %s", err)
|
||
|
}
|
||
|
|
||
|
// Start input loop
|
||
|
for {
|
||
|
// Read input line
|
||
|
line, _ := c.Readline()
|
||
|
|
||
|
// Split and sanitize input
|
||
|
sanitized, empty := sanitizeInput(line)
|
||
|
if empty {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Process various tokens on input (environment variables, paths, etc.)
|
||
|
// These tokens will be expaneded by completers anyway, so this is not absolutely required.
|
||
|
envParsed, _ := completers.ParseEnvironmentVariables(sanitized)
|
||
|
|
||
|
// Other types of tokens, needed by commands who expect a certain type
|
||
|
// of arguments, such as paths with spaces.
|
||
|
tokenParsed := c.parseTokens(envParsed)
|
||
|
|
||
|
// Execute the command and print any errors
|
||
|
if _, parserErr := c.parser.ParseArgs(tokenParsed); parserErr != nil {
|
||
|
fmt.Println(readline.RED + "[Error] " + readline.RESET + parserErr.Error() + "\n")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Readline - Add an empty line between input line and command output.
|
||
|
func (c *console) Readline() (line string, err error) {
|
||
|
line, err = c.shell.Readline()
|
||
|
fmt.Println()
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// sanitizeInput - Trims spaces and other unwished elements from the input line.
|
||
|
func sanitizeInput(line string) (sanitized []string, empty bool) {
|
||
|
|
||
|
// Assume the input is not empty
|
||
|
empty = false
|
||
|
|
||
|
// Trim border spaces
|
||
|
trimmed := strings.TrimSpace(line)
|
||
|
if len(line) < 1 {
|
||
|
empty = true
|
||
|
return
|
||
|
}
|
||
|
unfiltered := strings.Split(trimmed, " ")
|
||
|
|
||
|
// Catch any eventual empty items
|
||
|
for _, arg := range unfiltered {
|
||
|
if arg != "" {
|
||
|
sanitized = append(sanitized, arg)
|
||
|
}
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// parseTokens - Parse and process any special tokens that are not treated by environment-like parsers.
|
||
|
func (c *console) parseTokens(sanitized []string) (parsed []string) {
|
||
|
|
||
|
// PATH SPACE TOKENS
|
||
|
// Catch \ tokens, which have been introduced in paths where some directories have spaces in name.
|
||
|
// For each of these splits, we concatenate them with the next string.
|
||
|
// This will also inspect commands/options/arguments, but there is no reason why a backlash should be present in them.
|
||
|
var pathAdjusted []string
|
||
|
var roll bool
|
||
|
var arg string
|
||
|
for i := range sanitized {
|
||
|
if strings.HasSuffix(sanitized[i], "\\") {
|
||
|
// If we find a suffix, replace with a space. Go on with next input
|
||
|
arg += strings.TrimSuffix(sanitized[i], "\\") + " "
|
||
|
roll = true
|
||
|
} else if roll {
|
||
|
// No suffix but part of previous input. Add it and go on.
|
||
|
arg += sanitized[i]
|
||
|
pathAdjusted = append(pathAdjusted, arg)
|
||
|
arg = ""
|
||
|
roll = false
|
||
|
} else {
|
||
|
// Default, we add our path and go on.
|
||
|
pathAdjusted = append(pathAdjusted, sanitized[i])
|
||
|
}
|
||
|
}
|
||
|
parsed = pathAdjusted
|
||
|
|
||
|
// Add new function here, act on parsed []string from now on, not sanitized
|
||
|
return
|
||
|
}
|