mirror of
https://github.com/Hilbis/Hilbish
synced 2025-04-16 10:33:23 +00:00
chore: delete some unneeded code and files
This commit is contained in:
parent
6cd294373c
commit
60edfc00ee
@ -1,23 +0,0 @@
|
|||||||
package completers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/jessevdk/go-flags"
|
|
||||||
|
|
||||||
"github.com/maxlandon/readline"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CompleteCommandArguments - Completes all values for arguments to a command.
|
|
||||||
// Arguments here are different from command options (--option).
|
|
||||||
// Many categories, from multiple sources in multiple contexts
|
|
||||||
func completeCommandArguments(cmd *flags.Command, arg string, lastWord string) (prefix string, completions []*readline.CompletionGroup) {
|
|
||||||
|
|
||||||
// the prefix is the last word, by default
|
|
||||||
prefix = lastWord
|
|
||||||
|
|
||||||
// SEE completeOptionArguments FOR A WAY TO ADD COMPLETIONS TO SPECIFIC ARGUMENTS ------------------------------
|
|
||||||
|
|
||||||
// found := argumentByName(cmd, arg)
|
|
||||||
// var comp *readline.CompletionGroup // This group is used as a buffer, to add groups to final completions
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,124 +0,0 @@
|
|||||||
package completers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/maxlandon/readline"
|
|
||||||
)
|
|
||||||
|
|
||||||
// completeEnvironmentVariables - Returns all environment variables as suggestions
|
|
||||||
func completeEnvironmentVariables(lastWord string) (last string, completions []*readline.CompletionGroup) {
|
|
||||||
|
|
||||||
// Check if last input is made of several different variables
|
|
||||||
allVars := strings.Split(lastWord, "/")
|
|
||||||
lastVar := allVars[len(allVars)-1]
|
|
||||||
|
|
||||||
var evaluated = map[string]string{}
|
|
||||||
|
|
||||||
grp := &readline.CompletionGroup{
|
|
||||||
Name: "console OS environment",
|
|
||||||
MaxLength: 5, // Should be plenty enough
|
|
||||||
DisplayType: readline.TabDisplayGrid,
|
|
||||||
TrimSlash: true, // Some variables can be paths
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range clientEnv {
|
|
||||||
if strings.HasPrefix("$"+k, lastVar) {
|
|
||||||
grp.Suggestions = append(grp.Suggestions, "$"+k+"/")
|
|
||||||
evaluated[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
completions = append(completions, grp)
|
|
||||||
|
|
||||||
return lastVar, completions
|
|
||||||
}
|
|
||||||
|
|
||||||
// clientEnv - Contains all OS environment variables, client-side.
|
|
||||||
// This is used for things like downloading/uploading files from localhost, etc.,
|
|
||||||
// therefore we need completion and parsing stuff, sometimes.
|
|
||||||
var clientEnv = map[string]string{}
|
|
||||||
|
|
||||||
// ParseEnvironmentVariables - Parses a line of input and replace detected environment variables with their values.
|
|
||||||
func ParseEnvironmentVariables(args []string) (processed []string, err error) {
|
|
||||||
|
|
||||||
for _, arg := range args {
|
|
||||||
|
|
||||||
// Anywhere a $ is assigned means there is an env variable
|
|
||||||
if strings.Contains(arg, "$") || strings.Contains(arg, "~") {
|
|
||||||
|
|
||||||
//Split in case env is embedded in path
|
|
||||||
envArgs := strings.Split(arg, "/")
|
|
||||||
|
|
||||||
// If its not a path
|
|
||||||
if len(envArgs) == 1 {
|
|
||||||
processed = append(processed, handleCuratedVar(arg))
|
|
||||||
}
|
|
||||||
|
|
||||||
// If len of the env var split is > 1, its a path
|
|
||||||
if len(envArgs) > 1 {
|
|
||||||
processed = append(processed, handleEmbeddedVar(arg))
|
|
||||||
}
|
|
||||||
} else if arg != "" && arg != " " {
|
|
||||||
// Else, if arg is not an environment variable, return it as is
|
|
||||||
processed = append(processed, arg)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleCuratedVar - Replace an environment variable alone and without any undesired characters attached
|
|
||||||
func handleCuratedVar(arg string) (value string) {
|
|
||||||
if strings.HasPrefix(arg, "$") && arg != "" && arg != "$" {
|
|
||||||
envVar := strings.TrimPrefix(arg, "$")
|
|
||||||
val, ok := clientEnv[envVar]
|
|
||||||
if !ok {
|
|
||||||
return envVar
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
if arg != "" && arg == "~" {
|
|
||||||
return clientEnv["HOME"]
|
|
||||||
}
|
|
||||||
|
|
||||||
return arg
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleEmbeddedVar - Replace an environment variable that is in the middle of a path, or other one-string combination
|
|
||||||
func handleEmbeddedVar(arg string) (value string) {
|
|
||||||
|
|
||||||
envArgs := strings.Split(arg, "/")
|
|
||||||
var path []string
|
|
||||||
|
|
||||||
for _, arg := range envArgs {
|
|
||||||
if strings.HasPrefix(arg, "$") && arg != "" && arg != "$" {
|
|
||||||
envVar := strings.TrimPrefix(arg, "$")
|
|
||||||
val, ok := clientEnv[envVar]
|
|
||||||
if !ok {
|
|
||||||
// Err will be caught when command is ran anyway, or completion will stop...
|
|
||||||
path = append(path, arg)
|
|
||||||
}
|
|
||||||
path = append(path, val)
|
|
||||||
} else if arg != "" && arg == "~" {
|
|
||||||
path = append(path, clientEnv["HOME"])
|
|
||||||
} else if arg != " " && arg != "" {
|
|
||||||
path = append(path, arg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(path, "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadClientEnv - Loads all user environment variables
|
|
||||||
func loadClientEnv() error {
|
|
||||||
env := os.Environ()
|
|
||||||
|
|
||||||
for _, kv := range env {
|
|
||||||
key := strings.Split(kv, "=")[0]
|
|
||||||
value := strings.Split(kv, "=")[1]
|
|
||||||
clientEnv[key] = value
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,180 +0,0 @@
|
|||||||
package completers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/jessevdk/go-flags"
|
|
||||||
|
|
||||||
"github.com/maxlandon/readline"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HintCompleter - Entrypoint to all hints in the Wiregost console
|
|
||||||
func (c *CommandCompleter) HintCompleter(line []rune, pos int) (hint []rune) {
|
|
||||||
|
|
||||||
// Format and sanitize input
|
|
||||||
// @args => All items of the input line
|
|
||||||
// @last => The last word detected in input line as []rune
|
|
||||||
// @lastWord => The last word detected in input as string
|
|
||||||
args, last, lastWord := formatInput(line)
|
|
||||||
|
|
||||||
// Detect base command automatically
|
|
||||||
var command = c.detectedCommand(args)
|
|
||||||
|
|
||||||
// Menu hints (command line is empty, or nothing recognized)
|
|
||||||
if noCommandOrEmpty(args, last, command) {
|
|
||||||
hint = MenuHint(args, last)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check environment variables
|
|
||||||
if envVarAsked(args, lastWord) {
|
|
||||||
return envVarHint(args, last)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command Hint
|
|
||||||
if commandFound(command) {
|
|
||||||
|
|
||||||
// Command hint by default (no space between cursor and last command character)
|
|
||||||
hint = CommandHint(command)
|
|
||||||
|
|
||||||
// Check environment variables
|
|
||||||
if envVarAsked(args, lastWord) {
|
|
||||||
return envVarHint(args, last)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If options are asked for root command, return commpletions.
|
|
||||||
if len(command.Groups()) > 0 {
|
|
||||||
for _, grp := range command.Groups() {
|
|
||||||
if opt, yes := optionArgRequired(args, last, grp); yes {
|
|
||||||
hint = OptionArgumentHint(args, last, opt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If command has args, hint for args
|
|
||||||
if arg, yes := commandArgumentRequired(lastWord, args, command); yes {
|
|
||||||
hint = []rune(CommandArgumentHints(args, last, command, arg))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Brief subcommand hint
|
|
||||||
if lastIsSubCommand(lastWord, command) {
|
|
||||||
hint = []rune(commandHint + command.Find(string(last)).ShortDescription)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle subcommand if found
|
|
||||||
if sub, ok := subCommandFound(lastWord, args, command); ok {
|
|
||||||
return HandleSubcommandHints(args, last, sub)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle system binaries, shell commands, etc...
|
|
||||||
if commandFoundInPath(args[0]) {
|
|
||||||
// hint = []rune(exeHint + util.ParseSummary(util.GetManPages(args[0])))
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommandHint - Yields the hint of a Wiregost command
|
|
||||||
func CommandHint(command *flags.Command) (hint []rune) {
|
|
||||||
return []rune(commandHint + command.ShortDescription)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleSubcommandHints - Handles hints for a subcommand and its arguments, options, etc.
|
|
||||||
func HandleSubcommandHints(args []string, last []rune, command *flags.Command) (hint []rune) {
|
|
||||||
|
|
||||||
// If command has args, hint for args
|
|
||||||
if arg, yes := commandArgumentRequired(string(last), args, command); yes {
|
|
||||||
hint = []rune(CommandArgumentHints(args, last, command, arg))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Environment variables
|
|
||||||
if envVarAsked(args, string(last)) {
|
|
||||||
hint = envVarHint(args, last)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the last word in input is an option --name, yield argument hint if needed
|
|
||||||
if len(command.Groups()) > 0 {
|
|
||||||
for _, grp := range command.Groups() {
|
|
||||||
if opt, yes := optionArgRequired(args, last, grp); yes {
|
|
||||||
hint = OptionArgumentHint(args, last, opt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If user asks for completions with "-" or "--".
|
|
||||||
// (Note: This takes precedence on any argument hints, as it is evaluated after them)
|
|
||||||
if commandOptionsAsked(args, string(last), command) {
|
|
||||||
return OptionHints(args, last, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommandArgumentHints - Yields hints for arguments to commands if they have some
|
|
||||||
func CommandArgumentHints(args []string, last []rune, command *flags.Command, arg string) (hint []rune) {
|
|
||||||
|
|
||||||
found := argumentByName(command, arg)
|
|
||||||
// Base Hint is just a description of the command argument
|
|
||||||
hint = []rune(argHint + found.Description)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModuleOptionHints - If the option being set has a description, show it
|
|
||||||
func ModuleOptionHints(opt string) (hint []rune) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// OptionHints - Yields hints for proposed options lists/groups
|
|
||||||
func OptionHints(args []string, last []rune, command *flags.Command) (hint []rune) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// OptionArgumentHint - Yields hints for arguments to an option (generally the last word in input)
|
|
||||||
func OptionArgumentHint(args []string, last []rune, opt *flags.Option) (hint []rune) {
|
|
||||||
return []rune(valueHint + opt.Description)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MenuHint - Returns the Hint for a given menu context
|
|
||||||
func MenuHint(args []string, current []rune) (hint []rune) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// SpecialCommandHint - Shows hints for Wiregost special commands
|
|
||||||
func SpecialCommandHint(args []string, current []rune) (hint []rune) {
|
|
||||||
return current
|
|
||||||
}
|
|
||||||
|
|
||||||
// envVarHint - Yields hints for environment variables
|
|
||||||
func envVarHint(args []string, last []rune) (hint []rune) {
|
|
||||||
// Trim last in case its a path with multiple vars
|
|
||||||
allVars := strings.Split(string(last), "/")
|
|
||||||
lastVar := allVars[len(allVars)-1]
|
|
||||||
|
|
||||||
// Base hint
|
|
||||||
hint = []rune(envHint + lastVar)
|
|
||||||
|
|
||||||
envVar := strings.TrimPrefix(lastVar, "$")
|
|
||||||
|
|
||||||
if v, ok := clientEnv[envVar]; ok {
|
|
||||||
if v != "" {
|
|
||||||
hintStr := string(hint) + " => " + clientEnv[envVar]
|
|
||||||
hint = []rune(hintStr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Hint signs
|
|
||||||
menuHint = readline.RESET + readline.DIM + readline.BOLD + " menu " + readline.RESET // Dim
|
|
||||||
envHint = readline.RESET + readline.GREEN + readline.BOLD + " env " + readline.RESET + readline.DIM + readline.GREEN // Green
|
|
||||||
commandHint = readline.RESET + readline.DIM + readline.BOLD + " command " + readline.RESET + readline.DIM + "\033[38;5;244m" // Cream
|
|
||||||
exeHint = readline.RESET + readline.DIM + readline.BOLD + " shell " + readline.RESET + readline.DIM // Dim
|
|
||||||
optionHint = "\033[38;5;222m" + readline.BOLD + " options " + readline.RESET + readline.DIM + "\033[38;5;222m" // Cream-Yellow
|
|
||||||
valueHint = readline.RESET + readline.DIM + readline.BOLD + " value " + readline.RESET + readline.DIM + "\033[38;5;244m" // Pink-Cream
|
|
||||||
// valueHint = "\033[38;5;217m" + readline.BOLD + " Value " + readline.RESET + readline.DIM + "\033[38;5;244m" // Pink-Cream
|
|
||||||
argHint = readline.DIM + "\033[38;5;217m" + readline.BOLD + " arg " + readline.RESET + readline.DIM + "\033[38;5;244m" // Pink-Cream
|
|
||||||
)
|
|
@ -1,205 +0,0 @@
|
|||||||
package completers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"os/user"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/maxlandon/readline"
|
|
||||||
)
|
|
||||||
|
|
||||||
func completeLocalPath(last string) (string, *readline.CompletionGroup) {
|
|
||||||
|
|
||||||
// Completions
|
|
||||||
completion := &readline.CompletionGroup{
|
|
||||||
Name: "(console) local path",
|
|
||||||
MaxLength: 10, // The grid system is not yet able to roll on comps if > MaxLength
|
|
||||||
DisplayType: readline.TabDisplayGrid,
|
|
||||||
TrimSlash: true,
|
|
||||||
}
|
|
||||||
var suggestions []string
|
|
||||||
|
|
||||||
// Any parsing error is silently ignored, for not messing the prompt
|
|
||||||
processedPath, _ := ParseEnvironmentVariables([]string{last})
|
|
||||||
|
|
||||||
// Check if processed input is empty
|
|
||||||
var inputPath string
|
|
||||||
if len(processedPath) == 1 {
|
|
||||||
inputPath = processedPath[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a slash if the raw input has one but not the processed input
|
|
||||||
if len(last) > 0 && last[len(last)-1] == '/' {
|
|
||||||
inputPath += "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
var linePath string // curated version of the inputPath
|
|
||||||
var absPath string // absolute path (excluding suffix) of the inputPath
|
|
||||||
var lastPath string // last directory in the input path
|
|
||||||
|
|
||||||
if strings.HasSuffix(string(inputPath), "/") {
|
|
||||||
linePath = filepath.Dir(string(inputPath))
|
|
||||||
absPath, _ = expand(string(linePath)) // Get absolute path
|
|
||||||
|
|
||||||
} else if string(inputPath) == "" {
|
|
||||||
linePath = "."
|
|
||||||
absPath, _ = expand(string(linePath))
|
|
||||||
} else {
|
|
||||||
linePath = filepath.Dir(string(inputPath))
|
|
||||||
absPath, _ = expand(string(linePath)) // Get absolute path
|
|
||||||
lastPath = filepath.Base(string(inputPath)) // Save filter
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2) We take the absolute path we found, and get all dirs in it.
|
|
||||||
var dirs []string
|
|
||||||
files, _ := ioutil.ReadDir(absPath)
|
|
||||||
for _, file := range files {
|
|
||||||
if file.IsDir() {
|
|
||||||
dirs = append(dirs, file.Name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch lastPath {
|
|
||||||
case "":
|
|
||||||
for _, dir := range dirs {
|
|
||||||
if strings.HasPrefix(dir, lastPath) || lastPath == dir {
|
|
||||||
tokenized := addSpaceTokens(dir)
|
|
||||||
suggestions = append(suggestions, tokenized+"/")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
filtered := []string{}
|
|
||||||
for _, dir := range dirs {
|
|
||||||
if strings.HasPrefix(dir, lastPath) {
|
|
||||||
filtered = append(filtered, dir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, dir := range filtered {
|
|
||||||
if !hasPrefix([]rune(lastPath), []rune(dir)) || lastPath == dir {
|
|
||||||
tokenized := addSpaceTokens(dir)
|
|
||||||
suggestions = append(suggestions, tokenized+"/")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
completion.Suggestions = suggestions
|
|
||||||
return string(lastPath), completion
|
|
||||||
}
|
|
||||||
|
|
||||||
func addSpaceTokens(in string) (path string) {
|
|
||||||
items := strings.Split(in, " ")
|
|
||||||
for i := range items {
|
|
||||||
if len(items) == i+1 { // If last one, no char, add and return
|
|
||||||
path += items[i]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
path += items[i] + "\\ " // By default add space char and roll
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func completeLocalPathAndFiles(last string) (string, *readline.CompletionGroup) {
|
|
||||||
|
|
||||||
// Completions
|
|
||||||
completion := &readline.CompletionGroup{
|
|
||||||
Name: "(console) local directory/files",
|
|
||||||
MaxLength: 10, // The grid system is not yet able to roll on comps if > MaxLength
|
|
||||||
DisplayType: readline.TabDisplayGrid,
|
|
||||||
TrimSlash: true,
|
|
||||||
}
|
|
||||||
var suggestions []string
|
|
||||||
|
|
||||||
// Any parsing error is silently ignored, for not messing the prompt
|
|
||||||
processedPath, _ := ParseEnvironmentVariables([]string{last})
|
|
||||||
|
|
||||||
// Check if processed input is empty
|
|
||||||
var inputPath string
|
|
||||||
if len(processedPath) == 1 {
|
|
||||||
inputPath = processedPath[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a slash if the raw input has one but not the processed input
|
|
||||||
if len(last) > 0 && last[len(last)-1] == '/' {
|
|
||||||
inputPath += "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
var linePath string // curated version of the inputPath
|
|
||||||
var absPath string // absolute path (excluding suffix) of the inputPath
|
|
||||||
var lastPath string // last directory in the input path
|
|
||||||
|
|
||||||
if strings.HasSuffix(string(inputPath), "/") {
|
|
||||||
linePath = filepath.Dir(string(inputPath)) // Trim the non needed slash
|
|
||||||
absPath, _ = expand(string(linePath)) // Get absolute path
|
|
||||||
|
|
||||||
} else if string(inputPath) == "" {
|
|
||||||
linePath = "."
|
|
||||||
absPath, _ = expand(string(linePath))
|
|
||||||
} else {
|
|
||||||
linePath = filepath.Dir(string(inputPath))
|
|
||||||
absPath, _ = expand(string(linePath)) // Get absolute path
|
|
||||||
lastPath = filepath.Base(string(inputPath)) // Save filter
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2) We take the absolute path we found, and get all dirs in it.
|
|
||||||
var dirs []string
|
|
||||||
files, _ := ioutil.ReadDir(absPath)
|
|
||||||
for _, file := range files {
|
|
||||||
if file.IsDir() {
|
|
||||||
dirs = append(dirs, file.Name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch lastPath {
|
|
||||||
case "":
|
|
||||||
for _, file := range files {
|
|
||||||
if strings.HasPrefix(file.Name(), lastPath) || lastPath == file.Name() {
|
|
||||||
if file.IsDir() {
|
|
||||||
suggestions = append(suggestions, file.Name()+"/")
|
|
||||||
} else {
|
|
||||||
suggestions = append(suggestions, file.Name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
filtered := []os.FileInfo{}
|
|
||||||
for _, file := range files {
|
|
||||||
if strings.HasPrefix(file.Name(), lastPath) {
|
|
||||||
filtered = append(filtered, file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range filtered {
|
|
||||||
if !hasPrefix([]rune(lastPath), []rune(file.Name())) || lastPath == file.Name() {
|
|
||||||
if file.IsDir() {
|
|
||||||
suggestions = append(suggestions, file.Name()+"/")
|
|
||||||
} else {
|
|
||||||
suggestions = append(suggestions, file.Name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
completion.Suggestions = suggestions
|
|
||||||
return string(lastPath), completion
|
|
||||||
}
|
|
||||||
|
|
||||||
// expand will expand a path with ~ to the $HOME of the current user.
|
|
||||||
func expand(path string) (string, error) {
|
|
||||||
if path == "" {
|
|
||||||
return path, nil
|
|
||||||
}
|
|
||||||
home := os.Getenv("HOME")
|
|
||||||
if home == "" {
|
|
||||||
usr, err := user.Current()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
home = usr.HomeDir
|
|
||||||
}
|
|
||||||
return filepath.Abs(strings.Replace(path, "~", home, 1))
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
package completers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/jessevdk/go-flags"
|
|
||||||
|
|
||||||
"github.com/maxlandon/readline"
|
|
||||||
)
|
|
||||||
|
|
||||||
// completeOptionArguments - Completes all values for arguments to a command. Arguments here are different from command options (--option).
|
|
||||||
// Many categories, from multiple sources in multiple contexts
|
|
||||||
func completeOptionArguments(cmd *flags.Command, opt *flags.Option, lastWord string) (prefix string, completions []*readline.CompletionGroup) {
|
|
||||||
|
|
||||||
// By default the last word is the prefix
|
|
||||||
prefix = lastWord
|
|
||||||
|
|
||||||
var comp *readline.CompletionGroup // This group is used as a buffer, to add groups to final completions
|
|
||||||
|
|
||||||
// First of all: some options, no matter their contexts and subject, have default values.
|
|
||||||
// When we have such an option, we don't bother analyzing context, we just build completions and return.
|
|
||||||
if len(opt.Choices) > 0 {
|
|
||||||
comp = &readline.CompletionGroup{
|
|
||||||
Name: opt.ValueName, // Value names are specified in struct metadata fields
|
|
||||||
DisplayType: readline.TabDisplayGrid,
|
|
||||||
}
|
|
||||||
for _, choice := range opt.Choices {
|
|
||||||
if strings.HasPrefix(choice, lastWord) {
|
|
||||||
comp.Suggestions = append(comp.Suggestions, choice)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
completions = append(completions, comp)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXAMPLE OF COMPLETING ARGUMENTS BASED ON THEIR NAMES -----------------------------------------------------------------------
|
|
||||||
// We have 3 words, potentially different, with which we can filter:
|
|
||||||
//
|
|
||||||
// 1) '--option-name' is the string typed as input.
|
|
||||||
// 2) 'OptionName' is the name of the struct/type for this option.
|
|
||||||
// 3) 'ValueName' is the name of the value we expect.
|
|
||||||
// var match = func(name string) bool {
|
|
||||||
// if strings.Contains(opt.Field().Name, name) {
|
|
||||||
// return true
|
|
||||||
// }
|
|
||||||
// return false
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Sessions
|
|
||||||
// if match("ImplantID") || match("SessionID") {
|
|
||||||
// completions = append(completions, sessionIDs(lastWord))
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Any arguments with a path name. Often we "save" files that need paths, certificates, etc
|
|
||||||
// if match("Path") || match("Save") || match("Certificate") || match("PrivateKey") {
|
|
||||||
// switch cmd.Name {
|
|
||||||
// case constants.WebContentTypeStr, constants.WebUpdateStr, constants.AddWebContentStr, constants.RmWebContentStr:
|
|
||||||
// // Make an exception for WebPath option in websites commands.
|
|
||||||
// default:
|
|
||||||
// switch opt.ValueName {
|
|
||||||
// case "local-path", "path":
|
|
||||||
// prefix, comp = completeLocalPath(lastWord)
|
|
||||||
// completions = append(completions, comp)
|
|
||||||
// case "local-file", "file":
|
|
||||||
// prefix, comp = completeLocalPathAndFiles(lastWord)
|
|
||||||
// completions = append(completions, comp)
|
|
||||||
// default:
|
|
||||||
// // We always have a default searching for files, locally
|
|
||||||
// prefix, comp = completeLocalPathAndFiles(lastWord)
|
|
||||||
// completions = append(completions, comp)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,548 +0,0 @@
|
|||||||
package completers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os/exec"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
|
|
||||||
"github.com/jessevdk/go-flags"
|
|
||||||
)
|
|
||||||
|
|
||||||
// These functions are just shorthands for checking various conditions on the input line.
|
|
||||||
// They make the main function more readable, which might be useful, should a logic error pop somewhere.
|
|
||||||
|
|
||||||
// [ Parser Commands & Options ] --------------------------------------------------------------------------
|
|
||||||
// ArgumentByName Get the name of a detected command's argument
|
|
||||||
func argumentByName(command *flags.Command, name string) *flags.Arg {
|
|
||||||
args := command.Args()
|
|
||||||
for _, arg := range args {
|
|
||||||
if arg.Name == name {
|
|
||||||
return arg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// optionByName - Returns an option for a command or a subcommand, identified by name
|
|
||||||
func optionByName(cmd *flags.Command, option string) *flags.Option {
|
|
||||||
|
|
||||||
if cmd == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Get all (root) option groups.
|
|
||||||
groups := cmd.Groups()
|
|
||||||
|
|
||||||
// For each group, build completions
|
|
||||||
for _, grp := range groups {
|
|
||||||
// Add each option to completion group
|
|
||||||
for _, opt := range grp.Options() {
|
|
||||||
if opt.LongName == option {
|
|
||||||
return opt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// [ Menus ] --------------------------------------------------------------------------------------------
|
|
||||||
// Is the input line is either empty, or without any detected command ?
|
|
||||||
func noCommandOrEmpty(args []string, last []rune, command *flags.Command) bool {
|
|
||||||
if len(args) == 0 || len(args) == 1 && command == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// [ Commands ] -------------------------------------------------------------------------------------
|
|
||||||
// detectedCommand - Returns the base command from parser if detected, depending on context
|
|
||||||
func (c *CommandCompleter) detectedCommand(args []string) (command *flags.Command) {
|
|
||||||
arg := strings.TrimSpace(args[0])
|
|
||||||
command = c.parser.Find(arg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// is the command a special command, usually not handled by parser ?
|
|
||||||
func isSpecialCommand(args []string, command *flags.Command) bool {
|
|
||||||
|
|
||||||
// If command is not nil, return
|
|
||||||
if command == nil {
|
|
||||||
// Shell
|
|
||||||
if args[0] == "!" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// Exit
|
|
||||||
if args[0] == "exit" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// The commmand has been found
|
|
||||||
func commandFound(command *flags.Command) bool {
|
|
||||||
if command != nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search for input in $PATH
|
|
||||||
func commandFoundInPath(input string) bool {
|
|
||||||
_, err := exec.LookPath(input)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// [ SubCommands ]-------------------------------------------------------------------------------------
|
|
||||||
// Does the command have subcommands ?
|
|
||||||
func hasSubCommands(command *flags.Command, args []string) bool {
|
|
||||||
if len(args) < 2 || command == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(command.Commands()) != 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Does the input has a subcommand in it ?
|
|
||||||
func subCommandFound(lastWord string, raw []string, command *flags.Command) (sub *flags.Command, ok bool) {
|
|
||||||
// First, filter redundant spaces. This does not modify the actual line
|
|
||||||
args := ignoreRedundantSpaces(raw)
|
|
||||||
|
|
||||||
if len(args) <= 1 || command == nil {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
sub = command.Find(args[1])
|
|
||||||
if sub != nil {
|
|
||||||
return sub, true
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is the last input PRECISELY a subcommand. This is used as a brief hint for the subcommand
|
|
||||||
func lastIsSubCommand(lastWord string, command *flags.Command) bool {
|
|
||||||
if sub := command.Find(lastWord); sub != nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// [ Arguments ]-------------------------------------------------------------------------------------
|
|
||||||
// Does the command have arguments ?
|
|
||||||
func hasArgs(command *flags.Command) bool {
|
|
||||||
if len(command.Args()) != 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// commandArgumentRequired - Analyses input and sends back the next argument name to provide completion for
|
|
||||||
func commandArgumentRequired(lastWord string, raw []string, command *flags.Command) (name string, yes bool) {
|
|
||||||
|
|
||||||
// First, filter redundant spaces. This does not modify the actual line
|
|
||||||
args := ignoreRedundantSpaces(raw)
|
|
||||||
|
|
||||||
// Trim command and subcommand args
|
|
||||||
var remain []string
|
|
||||||
if args[0] == command.Name {
|
|
||||||
remain = args[1:]
|
|
||||||
}
|
|
||||||
if len(args) > 1 && args[1] == command.Name {
|
|
||||||
remain = args[2:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// The remain may include a "" as a last element,
|
|
||||||
// which we don't consider as a real remain, so we move it away
|
|
||||||
switch lastWord {
|
|
||||||
case "":
|
|
||||||
case command.Name:
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trim all --option flags and their arguments if they have
|
|
||||||
remain = filterOptions(remain, command)
|
|
||||||
|
|
||||||
// For each argument, check if needs completion. If not continue, if yes return.
|
|
||||||
// The arguments remainder is popped according to the number of values expected.
|
|
||||||
for i, arg := range command.Args() {
|
|
||||||
|
|
||||||
// If it's required and has one argument, check filled.
|
|
||||||
if arg.Required == 1 && arg.RequiredMaximum == 1 {
|
|
||||||
|
|
||||||
// If last word is the argument, and we are
|
|
||||||
// last arg in: line keep completing.
|
|
||||||
if len(remain) < 1 {
|
|
||||||
return arg.Name, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the we are still writing the argument
|
|
||||||
if len(remain) == 1 {
|
|
||||||
if lastWord != "" {
|
|
||||||
return arg.Name, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If filed and we are not last arg, continue
|
|
||||||
if len(remain) > 1 && i < (len(command.Args())-1) {
|
|
||||||
remain = remain[1:]
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we need more than one value and we knwo the maximum,
|
|
||||||
// either return or pop the remain.
|
|
||||||
if arg.Required > 0 && arg.RequiredMaximum > 1 {
|
|
||||||
// Pop the corresponding amount of arguments.
|
|
||||||
var found int
|
|
||||||
for i := 0; i < len(remain) && i < arg.RequiredMaximum; i++ {
|
|
||||||
remain = remain[1:]
|
|
||||||
found++
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we still need values:
|
|
||||||
if len(remain) == 0 && found <= arg.RequiredMaximum {
|
|
||||||
if lastWord == "" { // We are done, no more completions.
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
return arg.Name, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Else go on with the next argument
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// If has required arguments, with no limit of needs, return true
|
|
||||||
if arg.Required > 0 && arg.RequiredMaximum == -1 {
|
|
||||||
return arg.Name, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Else, if no requirements and the command has subcommands,
|
|
||||||
// return so that we complete subcommands
|
|
||||||
if arg.Required == -1 && len(command.Commands()) > 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Else, return this argument
|
|
||||||
// NOTE: This block is after because we always use []type arguments
|
|
||||||
// AFTER individual argument fields. Thus blocks any args that have
|
|
||||||
// not been processed.
|
|
||||||
if arg.Required == -1 {
|
|
||||||
return arg.Name, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Once we exited the loop, it means that none of the arguments require completion:
|
|
||||||
// They are all either optional, or fullfiled according to their required numbers.
|
|
||||||
// Thus we return none
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
// getRemainingArgs - Filters the input slice from commands and detected option:value pairs, and returns args
|
|
||||||
func getRemainingArgs(args []string, last []rune, command *flags.Command) (remain []string) {
|
|
||||||
|
|
||||||
var input []string
|
|
||||||
// Clean subcommand name
|
|
||||||
if args[0] == command.Name && len(args) >= 2 {
|
|
||||||
input = args[1:]
|
|
||||||
} else if len(args) == 1 {
|
|
||||||
input = args
|
|
||||||
}
|
|
||||||
|
|
||||||
// For each each argument
|
|
||||||
for i := 0; i < len(input); i++ {
|
|
||||||
// Check option prefix
|
|
||||||
if strings.HasPrefix(input[i], "-") || strings.HasPrefix(input[i], "--") {
|
|
||||||
// Clean it
|
|
||||||
cur := strings.TrimPrefix(input[i], "--")
|
|
||||||
cur = strings.TrimPrefix(cur, "-")
|
|
||||||
|
|
||||||
// Check if option matches any command option
|
|
||||||
if opt := command.FindOptionByLongName(cur); opt != nil {
|
|
||||||
boolean := true
|
|
||||||
if opt.Field().Type == reflect.TypeOf(boolean) {
|
|
||||||
continue // If option is boolean, don't skip an argument
|
|
||||||
}
|
|
||||||
i++ // Else skip next arg in input
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Safety check
|
|
||||||
if input[i] == "" || input[i] == " " {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
remain = append(remain, input[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// [ Options ]-------------------------------------------------------------------------------------
|
|
||||||
// commandOptionsAsked - Does the user asks for options in a root command ?
|
|
||||||
func commandOptionsAsked(args []string, lastWord string, command *flags.Command) bool {
|
|
||||||
if len(args) >= 2 && (strings.HasPrefix(lastWord, "-") || strings.HasPrefix(lastWord, "--")) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// commandOptionsAsked - Does the user asks for options in a subcommand ?
|
|
||||||
func subCommandOptionsAsked(args []string, lastWord string, command *flags.Command) bool {
|
|
||||||
if len(args) > 2 && (strings.HasPrefix(lastWord, "-") || strings.HasPrefix(lastWord, "--")) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is the last input argument is a dash ?
|
|
||||||
func isOptionDash(args []string, last []rune) bool {
|
|
||||||
if len(args) > 2 && (strings.HasPrefix(string(last), "-") || strings.HasPrefix(string(last), "--")) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// optionIsAlreadySet - Detects in input if an option is already set
|
|
||||||
func optionIsAlreadySet(args []string, lastWord string, opt *flags.Option) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if option type allows for repetition
|
|
||||||
func optionNotRepeatable(opt *flags.Option) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// [ Option Values ]-------------------------------------------------------------------------------------
|
|
||||||
// Is the last input word an option name (--option) ?
|
|
||||||
func optionArgRequired(args []string, last []rune, group *flags.Group) (opt *flags.Option, yes bool) {
|
|
||||||
|
|
||||||
var lastItem string
|
|
||||||
var lastOption string
|
|
||||||
var option *flags.Option
|
|
||||||
|
|
||||||
// If there is argument required we must have 1) command 2) --option inputs at least.
|
|
||||||
if len(args) <= 2 {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for last two arguments in input
|
|
||||||
if strings.HasPrefix(args[len(args)-2], "-") || strings.HasPrefix(args[len(args)-2], "--") {
|
|
||||||
|
|
||||||
// Long opts
|
|
||||||
if strings.HasPrefix(args[len(args)-2], "--") {
|
|
||||||
lastOption = strings.TrimPrefix(args[len(args)-2], "--")
|
|
||||||
if opt := group.FindOptionByLongName(lastOption); opt != nil {
|
|
||||||
option = opt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Short opts
|
|
||||||
} else if strings.HasPrefix(args[len(args)-2], "-") {
|
|
||||||
lastOption = strings.TrimPrefix(args[len(args)-2], "-")
|
|
||||||
if len(lastOption) > 0 {
|
|
||||||
if opt := group.FindOptionByShortName(rune(lastOption[0])); opt != nil {
|
|
||||||
option = opt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// If option is found, and we still are in writing the argument
|
|
||||||
if (lastItem == "" && option != nil) || option != nil {
|
|
||||||
// Check if option is a boolean, if yes return false
|
|
||||||
boolean := true
|
|
||||||
if option.Field().Type == reflect.TypeOf(boolean) {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return option, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for previous argument
|
|
||||||
if lastItem != "" && option == nil {
|
|
||||||
if strings.HasPrefix(args[len(args)-2], "-") || strings.HasPrefix(args[len(args)-2], "--") {
|
|
||||||
|
|
||||||
// Long opts
|
|
||||||
if strings.HasPrefix(args[len(args)-2], "--") {
|
|
||||||
lastOption = strings.TrimPrefix(args[len(args)-2], "--")
|
|
||||||
if opt := group.FindOptionByLongName(lastOption); opt != nil {
|
|
||||||
option = opt
|
|
||||||
return option, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Short opts
|
|
||||||
} else if strings.HasPrefix(args[len(args)-2], "-") {
|
|
||||||
lastOption = strings.TrimPrefix(args[len(args)-2], "-")
|
|
||||||
if opt := group.FindOptionByShortName(rune(lastOption[0])); opt != nil {
|
|
||||||
option = opt
|
|
||||||
return option, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// [ Other ]-------------------------------------------------------------------------------------
|
|
||||||
// Does the user asks for Environment variables ?
|
|
||||||
func envVarAsked(args []string, lastWord string) bool {
|
|
||||||
|
|
||||||
// Check if the current word is an environment variable, or if the last part of it is a variable
|
|
||||||
if len(lastWord) > 1 && strings.HasPrefix(lastWord, "$") {
|
|
||||||
if strings.LastIndex(lastWord, "/") < strings.LastIndex(lastWord, "$") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if env var is asked in a path or something
|
|
||||||
if len(lastWord) > 1 {
|
|
||||||
// If last is a path, it cannot be an env var anymore
|
|
||||||
if lastWord[len(lastWord)-1] == '/' {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if lastWord[len(lastWord)-1] == '$' {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we are at the beginning of an env var
|
|
||||||
if len(lastWord) > 0 && lastWord[len(lastWord)-1] == '$' {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// filterOptions - Check various elements of an option and return a list
|
|
||||||
func filterOptions(args []string, command *flags.Command) (processed []string) {
|
|
||||||
|
|
||||||
for i := 0; i < len(args); i++ {
|
|
||||||
arg := args[i]
|
|
||||||
// --long-name options
|
|
||||||
if strings.HasPrefix(arg, "--") {
|
|
||||||
name := strings.TrimPrefix(arg, "--")
|
|
||||||
if opt := optionByName(command, name); opt != nil {
|
|
||||||
var boolean = true
|
|
||||||
if opt.Field().Type == reflect.TypeOf(boolean) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Else skip the option argument (next item)
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// -s short options
|
|
||||||
if strings.HasPrefix(arg, "-") {
|
|
||||||
name := strings.TrimPrefix(arg, "-")
|
|
||||||
if opt := optionByName(command, name); opt != nil {
|
|
||||||
var boolean = true
|
|
||||||
if opt.Field().Type == reflect.TypeOf(boolean) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Else skip the option argument (next item)
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
processed = append(processed, arg)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Other Functions -------------------------------------------------------------------------------------------------------------//
|
|
||||||
|
|
||||||
// formatInput - Formats & sanitize the command line input
|
|
||||||
func formatInput(line []rune) (args []string, last []rune, lastWord string) {
|
|
||||||
args = strings.Split(string(line), " ") // The readline input as a []string
|
|
||||||
last = trimSpaceLeft([]rune(args[len(args)-1])) // The last char in input
|
|
||||||
lastWord = string(last)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormatInput - Formats & sanitize the command line input
|
|
||||||
func formatInputHighlighter(line []rune) (args []string, last []rune, lastWord string) {
|
|
||||||
args = strings.SplitN(string(line), " ", -1)
|
|
||||||
last = trimSpaceLeft([]rune(args[len(args)-1])) // The last char in input
|
|
||||||
lastWord = string(last)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignoreRedundantSpaces - We might have several spaces between each real arguments.
|
|
||||||
// However these indivual spaces are counted as args themselves.
|
|
||||||
// For each space arg found, verify that no space args follow,
|
|
||||||
// and if some are found, delete them.
|
|
||||||
func ignoreRedundantSpaces(raw []string) (args []string) {
|
|
||||||
|
|
||||||
for i := 0; i < len(raw); i++ {
|
|
||||||
// Catch a space argument.
|
|
||||||
if raw[i] == "" {
|
|
||||||
// The arg evaulated is always kept, because we just adjusted
|
|
||||||
// the indexing to avoid the ones we don't need
|
|
||||||
// args = append(args, raw[i])
|
|
||||||
|
|
||||||
for y, next := range raw[i:] {
|
|
||||||
if next != "" {
|
|
||||||
i += y - 1
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// If we come to the end while not breaking
|
|
||||||
// we push the outer loop straight to the end.
|
|
||||||
if y == len(raw[i:])-1 {
|
|
||||||
i += y
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// The arg evaulated is always kept, because we just adjusted
|
|
||||||
// the indexing to avoid the ones we don't need
|
|
||||||
args = append(args, raw[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func trimSpaceLeft(in []rune) []rune {
|
|
||||||
firstIndex := len(in)
|
|
||||||
for i, r := range in {
|
|
||||||
if unicode.IsSpace(r) == false {
|
|
||||||
firstIndex = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return in[firstIndex:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func equal(a, b []rune) bool {
|
|
||||||
if len(a) != len(b) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i := 0; i < len(a); i++ {
|
|
||||||
if a[i] != b[i] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasPrefix(r, prefix []rune) bool {
|
|
||||||
if len(r) < len(prefix) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return equal(r[:len(prefix)], prefix)
|
|
||||||
}
|
|
@ -1,151 +0,0 @@
|
|||||||
package completers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/jessevdk/go-flags"
|
|
||||||
|
|
||||||
"github.com/maxlandon/readline"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SyntaxHighlighter - Entrypoint to all input syntax highlighting in the Wiregost console
|
|
||||||
func (c *CommandCompleter) SyntaxHighlighter(input []rune) (line string) {
|
|
||||||
|
|
||||||
// Format and sanitize input
|
|
||||||
args, last, lastWord := formatInputHighlighter(input)
|
|
||||||
|
|
||||||
// Remain is all arguments that have not been highlighted, we need it for completing long commands
|
|
||||||
var remain = args
|
|
||||||
|
|
||||||
// Detect base command automatically
|
|
||||||
var command = c.detectedCommand(args)
|
|
||||||
|
|
||||||
// Return input as is
|
|
||||||
if noCommandOrEmpty(remain, last, command) {
|
|
||||||
return string(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Base command
|
|
||||||
if commandFound(command) {
|
|
||||||
line, remain = highlightCommand(remain, command)
|
|
||||||
|
|
||||||
// SubCommand
|
|
||||||
if sub, ok := subCommandFound(lastWord, args, command); ok {
|
|
||||||
line, remain = highlightSubCommand(line, remain, sub)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
line = processRemain(line, remain)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func highlightCommand(args []string, command *flags.Command) (line string, remain []string) {
|
|
||||||
line = readline.BOLD + args[0] + readline.RESET + " "
|
|
||||||
remain = args[1:]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func highlightSubCommand(input string, args []string, command *flags.Command) (line string, remain []string) {
|
|
||||||
line = input
|
|
||||||
line += readline.BOLD + args[0] + readline.RESET + " "
|
|
||||||
remain = args[1:]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func processRemain(input string, remain []string) (line string) {
|
|
||||||
|
|
||||||
// Check the last is not the last space in input
|
|
||||||
if len(remain) == 1 && remain[0] == " " {
|
|
||||||
return input
|
|
||||||
}
|
|
||||||
|
|
||||||
line = input + strings.Join(remain, " ")
|
|
||||||
// line = processEnvVars(input, remain)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// processEnvVars - Highlights environment variables. NOTE: Rewrite with logic from console/env.go
|
|
||||||
func processEnvVars(input string, remain []string) (line string) {
|
|
||||||
|
|
||||||
var processed []string
|
|
||||||
|
|
||||||
inputSlice := strings.Split(input, " ")
|
|
||||||
|
|
||||||
// Check already processed input
|
|
||||||
for _, arg := range inputSlice {
|
|
||||||
if arg == "" || arg == " " {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(arg, "$") { // It is an env var.
|
|
||||||
if args := strings.Split(arg, "/"); len(args) > 1 {
|
|
||||||
for _, a := range args {
|
|
||||||
fmt.Println(a)
|
|
||||||
if strings.HasPrefix(a, "$") && a != " " { // It is an env var.
|
|
||||||
processed = append(processed, "\033[38;5;108m"+readline.DIM+a+readline.RESET)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
processed = append(processed, "\033[38;5;108m"+readline.DIM+arg+readline.RESET)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
processed = append(processed, arg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check remaining args (non-processed)
|
|
||||||
for _, arg := range remain {
|
|
||||||
if arg == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(arg, "$") && arg != "$" { // It is an env var.
|
|
||||||
var full string
|
|
||||||
args := strings.Split(arg, "/")
|
|
||||||
if len(args) == 1 {
|
|
||||||
if strings.HasPrefix(args[0], "$") && args[0] != "" && args[0] != "$" { // It is an env var.
|
|
||||||
full += "\033[38;5;108m" + readline.DIM + args[0] + readline.RESET
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(args) > 1 {
|
|
||||||
var counter int
|
|
||||||
for _, arg := range args {
|
|
||||||
// If var is an env var
|
|
||||||
if strings.HasPrefix(arg, "$") && arg != "" && arg != "$" {
|
|
||||||
if counter < len(args)-1 {
|
|
||||||
full += "\033[38;5;108m" + readline.DIM + args[0] + readline.RESET + "/"
|
|
||||||
counter++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if counter == len(args)-1 {
|
|
||||||
full += "\033[38;5;108m" + readline.DIM + args[0] + readline.RESET
|
|
||||||
counter++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Else, if we are not at the end of array
|
|
||||||
if counter < len(args)-1 && arg != "" {
|
|
||||||
full += arg + "/"
|
|
||||||
counter++
|
|
||||||
}
|
|
||||||
if counter == len(args)-1 {
|
|
||||||
full += arg
|
|
||||||
counter++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Else add first var
|
|
||||||
processed = append(processed, full)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
line = strings.Join(processed, " ")
|
|
||||||
|
|
||||||
// Very important, keeps the line clear when erasing
|
|
||||||
// line += " "
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,289 +0,0 @@
|
|||||||
package completers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/jessevdk/go-flags"
|
|
||||||
|
|
||||||
"github.com/maxlandon/readline"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CommandCompleter - A completer using a github.com/jessevdk/go-flags Command Parser, in order
|
|
||||||
// to build completions for commands, arguments, options and their arguments as well.
|
|
||||||
// This completer needs to be instantiated with its constructor, in order to ensure the parser is not nil.
|
|
||||||
type CommandCompleter struct {
|
|
||||||
parser *flags.Parser
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCommandCompleter - Instantiate a new tab completer using a github.com/jessevdk/go-flags Command Parser.
|
|
||||||
func NewCommandCompleter(parser *flags.Parser) (completer *CommandCompleter, err error) {
|
|
||||||
if parser == nil {
|
|
||||||
return nil, errors.New("command completer was instantiated with a nil parser")
|
|
||||||
}
|
|
||||||
return &CommandCompleter{parser: parser}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TabCompleter - A default tab completer working with a github.com/jessevdk/go-flags parser.
|
|
||||||
func (c *CommandCompleter) TabCompleter(line []rune, pos int, dtc readline.DelayedTabContext) (lastWord string, completions []*readline.CompletionGroup) {
|
|
||||||
|
|
||||||
// Format and sanitize input
|
|
||||||
// @args => All items of the input line
|
|
||||||
// @last => The last word detected in input line as []rune
|
|
||||||
// @lastWord => The last word detected in input as string
|
|
||||||
args, last, lastWord := formatInput(line)
|
|
||||||
|
|
||||||
// Detect base command automatically
|
|
||||||
var command = c.detectedCommand(args)
|
|
||||||
|
|
||||||
// Propose commands
|
|
||||||
if noCommandOrEmpty(args, last, command) {
|
|
||||||
return c.completeMenuCommands(lastWord, pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check environment variables
|
|
||||||
if envVarAsked(args, lastWord) {
|
|
||||||
completeEnvironmentVariables(lastWord)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Base command has been identified
|
|
||||||
if commandFound(command) {
|
|
||||||
// Check environment variables again
|
|
||||||
if envVarAsked(args, lastWord) {
|
|
||||||
return completeEnvironmentVariables(lastWord)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If options are asked for root command, return commpletions.
|
|
||||||
if len(command.Groups()) > 0 {
|
|
||||||
for _, grp := range command.Groups() {
|
|
||||||
if opt, yes := optionArgRequired(args, last, grp); yes {
|
|
||||||
return completeOptionArguments(command, opt, lastWord)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then propose subcommands. We don't return from here, otherwise it always skips the next steps.
|
|
||||||
if hasSubCommands(command, args) {
|
|
||||||
completions = completeSubCommands(args, lastWord, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle subcommand if found (maybe we should rewrite this function and use it also for base command)
|
|
||||||
if sub, ok := subCommandFound(lastWord, args, command); ok {
|
|
||||||
return handleSubCommand(line, pos, sub)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If user asks for completions with "-" / "--", show command options.
|
|
||||||
// We ask this here, after having ensured there is no subcommand invoked.
|
|
||||||
// This prevails over command arguments, even if they are required.
|
|
||||||
if commandOptionsAsked(args, lastWord, command) {
|
|
||||||
return completeCommandOptions(args, lastWord, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Propose argument completion before anything, and if needed
|
|
||||||
if arg, yes := commandArgumentRequired(lastWord, args, command); yes {
|
|
||||||
return completeCommandArguments(command, arg, lastWord)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// [ Main Completion Functions ] -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// completeMenuCommands - Selects all commands available in a given context and returns them as suggestions
|
|
||||||
// Many categories, all from command parsers.
|
|
||||||
func (c *CommandCompleter) completeMenuCommands(lastWord string, pos int) (prefix string, completions []*readline.CompletionGroup) {
|
|
||||||
|
|
||||||
prefix = lastWord // We only return the PREFIX for readline to correctly show suggestions.
|
|
||||||
|
|
||||||
// Check their namespace (which should be their "group" (like utils, core, Jobs, etc))
|
|
||||||
for _, cmd := range c.parser.Commands() {
|
|
||||||
// If command matches readline input
|
|
||||||
if strings.HasPrefix(cmd.Name, lastWord) {
|
|
||||||
// Check command group: add to existing group if found
|
|
||||||
var found bool
|
|
||||||
for _, grp := range completions {
|
|
||||||
if grp.Name == cmd.Aliases[0] {
|
|
||||||
found = true
|
|
||||||
grp.Suggestions = append(grp.Suggestions, cmd.Name)
|
|
||||||
grp.Descriptions[cmd.Name] = readline.Dim(cmd.ShortDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add a new group if not found
|
|
||||||
if !found {
|
|
||||||
grp := &readline.CompletionGroup{
|
|
||||||
Name: cmd.Aliases[0],
|
|
||||||
Suggestions: []string{cmd.Name},
|
|
||||||
Descriptions: map[string]string{
|
|
||||||
cmd.Name: readline.Dim(cmd.ShortDescription),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
completions = append(completions, grp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make adjustments to the CompletionGroup list: set maxlength depending on items, check descriptions, etc.
|
|
||||||
for _, grp := range completions {
|
|
||||||
// If the length of suggestions is too long and we have
|
|
||||||
// many groups, use grid display.
|
|
||||||
if len(completions) >= 10 && len(grp.Suggestions) >= 7 {
|
|
||||||
grp.DisplayType = readline.TabDisplayGrid
|
|
||||||
} else {
|
|
||||||
// By default, we use a map of command to descriptions
|
|
||||||
grp.DisplayType = readline.TabDisplayList
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// completeSubCommands - Takes subcommands and gives them as suggestions
|
|
||||||
// One category, from one source (a parent command).
|
|
||||||
func completeSubCommands(args []string, lastWord string, command *flags.Command) (completions []*readline.CompletionGroup) {
|
|
||||||
|
|
||||||
group := &readline.CompletionGroup{
|
|
||||||
Name: command.Name,
|
|
||||||
Suggestions: []string{},
|
|
||||||
Descriptions: map[string]string{},
|
|
||||||
DisplayType: readline.TabDisplayList,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, sub := range command.Commands() {
|
|
||||||
if strings.HasPrefix(sub.Name, lastWord) {
|
|
||||||
group.Suggestions = append(group.Suggestions, sub.Name)
|
|
||||||
group.Descriptions[sub.Name] = readline.DIM + sub.ShortDescription + readline.RESET
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
completions = append(completions, group)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleSubCommand - Handles completion for subcommand options and arguments, + any option value related completion
|
|
||||||
// Many categories, from many sources: this function calls the same functions as the ones previously called for completing its parent command.
|
|
||||||
func handleSubCommand(line []rune, pos int, command *flags.Command) (lastWord string, completions []*readline.CompletionGroup) {
|
|
||||||
|
|
||||||
args, last, lastWord := formatInput(line)
|
|
||||||
|
|
||||||
// Check environment variables
|
|
||||||
if envVarAsked(args, lastWord) {
|
|
||||||
completeEnvironmentVariables(lastWord)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check argument options
|
|
||||||
if len(command.Groups()) > 0 {
|
|
||||||
for _, grp := range command.Groups() {
|
|
||||||
if opt, yes := optionArgRequired(args, last, grp); yes {
|
|
||||||
return completeOptionArguments(command, opt, lastWord)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If user asks for completions with "-" or "--". This must take precedence on arguments.
|
|
||||||
if subCommandOptionsAsked(args, lastWord, command) {
|
|
||||||
return completeCommandOptions(args, lastWord, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If command has non-filled arguments, propose them first
|
|
||||||
if arg, yes := commandArgumentRequired(lastWord, args, command); yes {
|
|
||||||
return completeCommandArguments(command, arg, lastWord)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// completeCommandOptions - Yields completion for options of a command, with various decorators
|
|
||||||
// Many categories, from one source (a command)
|
|
||||||
func completeCommandOptions(args []string, lastWord string, cmd *flags.Command) (prefix string, completions []*readline.CompletionGroup) {
|
|
||||||
|
|
||||||
prefix = lastWord // We only return the PREFIX for readline to correctly show suggestions.
|
|
||||||
|
|
||||||
// Get all (root) option groups.
|
|
||||||
groups := cmd.Groups()
|
|
||||||
|
|
||||||
// Append command options not gathered in groups
|
|
||||||
groups = append(groups, cmd.Group)
|
|
||||||
|
|
||||||
// For each group, build completions
|
|
||||||
for _, grp := range groups {
|
|
||||||
|
|
||||||
_, comp := completeOptionGroup(lastWord, grp, "")
|
|
||||||
|
|
||||||
// No need to add empty groups, will screw the completion system.
|
|
||||||
if len(comp.Suggestions) > 0 {
|
|
||||||
completions = append(completions, comp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do the same for global options, which are not part of any group "per-se"
|
|
||||||
_, gcomp := completeOptionGroup(lastWord, cmd.Group, "global options")
|
|
||||||
if len(gcomp.Suggestions) > 0 {
|
|
||||||
completions = append(completions, gcomp)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// completeOptionGroup - make completions for a single group of options. Title is optional, not used if empty.
|
|
||||||
func completeOptionGroup(lastWord string, grp *flags.Group, title string) (prefix string, compGrp *readline.CompletionGroup) {
|
|
||||||
|
|
||||||
compGrp = &readline.CompletionGroup{
|
|
||||||
Name: grp.ShortDescription,
|
|
||||||
Descriptions: map[string]string{},
|
|
||||||
DisplayType: readline.TabDisplayList,
|
|
||||||
Aliases: map[string]string{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// An optional title for this comp group.
|
|
||||||
// Used by global flag options, added to all commands.
|
|
||||||
if title != "" {
|
|
||||||
compGrp.Name = title
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add each option to completion group
|
|
||||||
for _, opt := range grp.Options() {
|
|
||||||
|
|
||||||
// Check if option is already set, next option if yes
|
|
||||||
// if optionNotRepeatable(opt) && optionIsAlreadySet(args, lastWord, opt) {
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Depending on the current last word, either build a group with option longs only, or with shorts
|
|
||||||
if strings.HasPrefix("--"+opt.LongName, lastWord) {
|
|
||||||
optName := "--" + opt.LongName
|
|
||||||
compGrp.Suggestions = append(compGrp.Suggestions, optName)
|
|
||||||
|
|
||||||
// Add short if there is, and that the prefix is only one dash
|
|
||||||
if strings.HasPrefix("-", lastWord) {
|
|
||||||
if opt.ShortName != 0 {
|
|
||||||
compGrp.Aliases[optName] = "-" + string(opt.ShortName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Option default value if any
|
|
||||||
var def string
|
|
||||||
if len(opt.Default) > 0 {
|
|
||||||
def = " (default:"
|
|
||||||
for _, d := range opt.Default {
|
|
||||||
def += " " + d + ","
|
|
||||||
}
|
|
||||||
def = strings.TrimSuffix(def, ",")
|
|
||||||
def += ")"
|
|
||||||
}
|
|
||||||
|
|
||||||
desc := fmt.Sprintf(" -- %s%s%s", opt.Description, def, readline.RESET)
|
|
||||||
compGrp.Descriptions[optName] = desc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecursiveGroupCompletion - Handles recursive completion for nested option groups
|
|
||||||
// Many categories, one source (a command's root option group). Called by the function just above.
|
|
||||||
func RecursiveGroupCompletion(args []string, last []rune, group *flags.Group) (lastWord string, completions []*readline.CompletionGroup) {
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,109 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
// This file defines a few argument choices for commands
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/jessevdk/go-flags"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Command/option argument choices
|
|
||||||
var (
|
|
||||||
// Logs & components
|
|
||||||
logLevels = []string{"trace", "debug", "info", "warning", "error"}
|
|
||||||
loggers = []string{"client", "comm"}
|
|
||||||
|
|
||||||
// Stages / Stagers
|
|
||||||
implantOS = []string{"windows", "linux", "darwin"}
|
|
||||||
implantArch = []string{"amd64", "x86"}
|
|
||||||
implantFmt = []string{"exe", "shared", "service", "shellcode"}
|
|
||||||
|
|
||||||
stageListenerProtocols = []string{"tcp", "http", "https"}
|
|
||||||
|
|
||||||
// MSF
|
|
||||||
msfStagerProtocols = []string{"tcp", "http", "https"}
|
|
||||||
msfTransformFormats = []string{
|
|
||||||
"bash",
|
|
||||||
"c",
|
|
||||||
"csharp",
|
|
||||||
"dw",
|
|
||||||
"dword",
|
|
||||||
"hex",
|
|
||||||
"java",
|
|
||||||
"js_be",
|
|
||||||
"js_le",
|
|
||||||
"num",
|
|
||||||
"perl",
|
|
||||||
"pl",
|
|
||||||
"powershell",
|
|
||||||
"ps1",
|
|
||||||
"py",
|
|
||||||
"python",
|
|
||||||
"raw",
|
|
||||||
"rb",
|
|
||||||
"ruby",
|
|
||||||
"sh",
|
|
||||||
"vbapplication",
|
|
||||||
"vbscript",
|
|
||||||
}
|
|
||||||
|
|
||||||
msfEncoders = []string{
|
|
||||||
"x86/shikata_ga_nai",
|
|
||||||
"x64/xor_dynamic",
|
|
||||||
}
|
|
||||||
|
|
||||||
msfPayloads = map[string][]string{
|
|
||||||
"windows": windowsMsfPayloads,
|
|
||||||
"linux": linuxMsfPayloads,
|
|
||||||
"osx": osxMsfPayloads,
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidPayloads - Valid payloads and OS combos
|
|
||||||
windowsMsfPayloads = []string{
|
|
||||||
"meterpreter_reverse_http",
|
|
||||||
"meterpreter_reverse_https",
|
|
||||||
"meterpreter_reverse_tcp",
|
|
||||||
"meterpreter/reverse_tcp",
|
|
||||||
"meterpreter/reverse_http",
|
|
||||||
"meterpreter/reverse_https",
|
|
||||||
}
|
|
||||||
linuxMsfPayloads = []string{
|
|
||||||
"meterpreter_reverse_http",
|
|
||||||
"meterpreter_reverse_https",
|
|
||||||
"meterpreter_reverse_tcp",
|
|
||||||
}
|
|
||||||
osxMsfPayloads = []string{
|
|
||||||
"meterpreter_reverse_http",
|
|
||||||
"meterpreter_reverse_https",
|
|
||||||
"meterpreter_reverse_tcp",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Comm network protocols
|
|
||||||
portfwdProtocols = []string{"tcp", "udp"}
|
|
||||||
transportProtocols = []string{"tcp", "udp", "ip"}
|
|
||||||
applicationProtocols = []string{"http", "https", "mtls", "quic", "http3", "dns", "named_pipe"}
|
|
||||||
)
|
|
||||||
|
|
||||||
// loadArgumentCompletions - Adds a bunch of choices for command arguments (and their completions.)
|
|
||||||
func loadArgumentCompletions(parser *flags.Parser) {
|
|
||||||
if parser == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
serverCompsAddtional(parser)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Additional completion mappings for command in the server context
|
|
||||||
func serverCompsAddtional(parser *flags.Parser) {
|
|
||||||
|
|
||||||
// Stage options
|
|
||||||
g := parser.Find("generate")
|
|
||||||
g.FindOptionByLongName("os").Choices = implantOS
|
|
||||||
g.FindOptionByLongName("arch").Choices = implantArch
|
|
||||||
g.FindOptionByLongName("format").Choices = implantFmt
|
|
||||||
|
|
||||||
// Stager options (mostly MSF)
|
|
||||||
gs := g.Find("stager")
|
|
||||||
gs.FindOptionByLongName("os").Choices = implantOS
|
|
||||||
gs.FindOptionByLongName("arch").Choices = implantArch
|
|
||||||
gs.FindOptionByLongName("protocol").Choices = msfStagerProtocols
|
|
||||||
gs.FindOptionByLongName("msf-format").Choices = msfTransformFormats
|
|
||||||
}
|
|
@ -1,315 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"os/user"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/jessevdk/go-flags"
|
|
||||||
|
|
||||||
"github.com/maxlandon/readline"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This file declares a go-flags parser and a few commands.
|
|
||||||
|
|
||||||
var (
|
|
||||||
// commandParser - The command parser used by the example console.
|
|
||||||
commandParser = flags.NewNamedParser("example", flags.IgnoreUnknown)
|
|
||||||
)
|
|
||||||
|
|
||||||
func bindCommands() (err error) {
|
|
||||||
|
|
||||||
// core console
|
|
||||||
// ----------------------------------------------------------------------------------------
|
|
||||||
ex, err := commandParser.AddCommand("exit", // Command string
|
|
||||||
"Exit from the client/server console", // Description (completions, help usage)
|
|
||||||
"", // Long description
|
|
||||||
&Exit{}) // Command implementation
|
|
||||||
ex.Aliases = []string{"core"}
|
|
||||||
|
|
||||||
cd, err := commandParser.AddCommand("cd",
|
|
||||||
"Change client working directory",
|
|
||||||
"",
|
|
||||||
&ChangeClientDirectory{})
|
|
||||||
cd.Aliases = []string{"core"}
|
|
||||||
|
|
||||||
ls, err := commandParser.AddCommand("ls",
|
|
||||||
"List directory contents",
|
|
||||||
"",
|
|
||||||
&ListClientDirectories{})
|
|
||||||
ls.Aliases = []string{"core"}
|
|
||||||
|
|
||||||
// Log
|
|
||||||
log, err := commandParser.AddCommand("log",
|
|
||||||
"Manage log levels of one or more components",
|
|
||||||
"",
|
|
||||||
&Log{})
|
|
||||||
log.Aliases = []string{"core"}
|
|
||||||
|
|
||||||
// Implant generation
|
|
||||||
// ----------------------------------------------------------------------------------------
|
|
||||||
g, err := commandParser.AddCommand("generate",
|
|
||||||
"Configure and compile an implant (staged or stager)",
|
|
||||||
"",
|
|
||||||
&Generate{})
|
|
||||||
g.Aliases = []string{"builds"}
|
|
||||||
g.SubcommandsOptional = true
|
|
||||||
|
|
||||||
_, err = g.AddCommand("stager",
|
|
||||||
"Generate a stager shellcode payload using MSFVenom, (to file: --save, to stdout: --format",
|
|
||||||
"",
|
|
||||||
&GenerateStager{})
|
|
||||||
|
|
||||||
r, err := commandParser.AddCommand("regenerate",
|
|
||||||
"Recompile an implant by name, passed as argument (completed)",
|
|
||||||
"",
|
|
||||||
&Regenerate{})
|
|
||||||
r.Aliases = []string{"builds"}
|
|
||||||
|
|
||||||
// Add choices completions (and therefore completions) to some of these commands.
|
|
||||||
loadArgumentCompletions(commandParser)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exit - Kill the current client console
|
|
||||||
type Exit struct{}
|
|
||||||
|
|
||||||
// Execute - Run
|
|
||||||
func (e *Exit) Execute(args []string) (err error) {
|
|
||||||
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
|
||||||
fmt.Print("Confirm exit (Y/y): ")
|
|
||||||
text, _ := reader.ReadString('\n')
|
|
||||||
answer := strings.TrimSpace(text)
|
|
||||||
|
|
||||||
if (answer == "Y") || (answer == "y") {
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChangeClientDirectory - Change the working directory of the client console
|
|
||||||
type ChangeClientDirectory struct {
|
|
||||||
Positional struct {
|
|
||||||
Path string `description:"local path" required:"1-1"`
|
|
||||||
} `positional-args:"yes" required:"yes"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute - Handler for ChangeDirectory
|
|
||||||
func (cd *ChangeClientDirectory) Execute(args []string) (err error) {
|
|
||||||
|
|
||||||
dir, err := expand(cd.Positional.Path)
|
|
||||||
|
|
||||||
err = os.Chdir(dir)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf(CommandError+"%s \n", err)
|
|
||||||
} else {
|
|
||||||
fmt.Printf(Info+"Changed directory to %s \n", dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListClientDirectories - List directory contents
|
|
||||||
type ListClientDirectories struct {
|
|
||||||
Positional struct {
|
|
||||||
Path []string `description:"local directory/file"`
|
|
||||||
} `positional-args:"yes"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute - Command
|
|
||||||
func (ls *ListClientDirectories) Execute(args []string) error {
|
|
||||||
|
|
||||||
base := []string{"ls", "--color", "-l"}
|
|
||||||
|
|
||||||
if len(ls.Positional.Path) == 0 {
|
|
||||||
ls.Positional.Path = []string{"."}
|
|
||||||
}
|
|
||||||
|
|
||||||
fullPaths := []string{}
|
|
||||||
for _, path := range ls.Positional.Path {
|
|
||||||
full, _ := expand(path)
|
|
||||||
fullPaths = append(fullPaths, full)
|
|
||||||
}
|
|
||||||
base = append(base, fullPaths...)
|
|
||||||
|
|
||||||
// Print output
|
|
||||||
out, err := shellExec(base[0], base[1:])
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf(CommandError+"%s \n", err.Error())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print output
|
|
||||||
fmt.Println(out)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// shellExec - Execute a program
|
|
||||||
func shellExec(executable string, args []string) (string, error) {
|
|
||||||
path, err := exec.LookPath(executable)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(path, args...)
|
|
||||||
|
|
||||||
// Load OS environment
|
|
||||||
cmd.Env = os.Environ()
|
|
||||||
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return strings.Trim(string(out), "/"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate - Configure and compile an implant
|
|
||||||
type Generate struct {
|
|
||||||
StageOptions // Command makes use of full stage options
|
|
||||||
}
|
|
||||||
|
|
||||||
// StageOptions - All these options, regrouped by area, are used by any command that needs full
|
|
||||||
// configuration information for a stage Sliver implant.
|
|
||||||
type StageOptions struct {
|
|
||||||
// CoreOptions - All options about OS/arch, files to save, debugs, etc.
|
|
||||||
CoreOptions struct {
|
|
||||||
OS string `long:"os" short:"o" description:"target host operating system" default:"windows" value-name:"stage OS"`
|
|
||||||
Arch string `long:"arch" short:"a" description:"target host CPU architecture" default:"amd64" value-name:"stage architectures"`
|
|
||||||
Format string `long:"format" short:"f" description:"output formats (exe, shared (DLL), service (see 'psexec' for info), shellcode (Windows only)" default:"exe" value-name:"stage formats"`
|
|
||||||
Profile string `long:"profile-name" description:"implant profile name to use (use with generate-profile)"`
|
|
||||||
Name string `long:"name" short:"N" description:"implant name to use (overrides random name generation)"`
|
|
||||||
Save string `long:"save" short:"s" description:"directory/file where to save binary"`
|
|
||||||
Debug bool `long:"debug" short:"d" description:"enable debug features (incompatible with obfuscation, and prevailing)"`
|
|
||||||
} `group:"core options"`
|
|
||||||
|
|
||||||
// TransportOptions - All options pertaining to transport/RPC matters
|
|
||||||
TransportOptions struct {
|
|
||||||
MTLS []string `long:"mtls" short:"m" description:"mTLS C2 domain(s), comma-separated (ex: mtls://host:port)" env-delim:","`
|
|
||||||
DNS []string `long:"dns" short:"n" description:"DNS C2 domain(s), comma-separated (ex: dns://mydomain.com)" env-delim:","`
|
|
||||||
HTTP []string `long:"http" short:"h" description:"HTTP(S) C2 domain(s)" env-delim:","`
|
|
||||||
NamedPipe []string `long:"named-pipe" short:"p" description:"Named pipe transport strings, comma-separated" env-delim:","`
|
|
||||||
TCPPivot []string `long:"tcp-pivot" short:"i" description:"TCP pivot transport strings, comma-separated" env-delim:","`
|
|
||||||
Reconnect int `long:"reconnect" short:"j" description:"attempt to reconnect every n second(s)" default:"60"`
|
|
||||||
MaxErrors int `long:"max-errors" short:"k" description:"max number of transport errors" default:"10"`
|
|
||||||
} `group:"transport options"`
|
|
||||||
|
|
||||||
// SecurityOptions - All security-oriented options like restrictions.
|
|
||||||
SecurityOptions struct {
|
|
||||||
LimitDatetime string `long:"limit-datetime" short:"w" description:"limit execution to before datetime"`
|
|
||||||
LimitDomain bool `long:"limit-domain-joined" short:"D" description:"limit execution to domain joined machines"`
|
|
||||||
LimitUsername string `long:"limit-username" short:"U" description:"limit execution to specified username"`
|
|
||||||
LimitHosname string `long:"limit-hostname" short:"H" description:"limit execution to specified hostname"`
|
|
||||||
LimitFileExits string `long:"limit-file-exists" short:"F" description:"limit execution to hosts with this file in the filesystem"`
|
|
||||||
} `group:"security options"`
|
|
||||||
|
|
||||||
// EvasionOptions - All proactive security options (obfuscation, evasion, etc)
|
|
||||||
EvasionOptions struct {
|
|
||||||
Canary []string `long:"canary" short:"c" description:"DNS canary domain strings, comma-separated" env-delim:","`
|
|
||||||
SkipSymbols bool `long:"skip-obfuscation" short:"b" description:"skip binary/symbol obfuscation"`
|
|
||||||
Evasion bool `long:"evasion" short:"e" description:"enable evasion features"`
|
|
||||||
} `group:"evasion options"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute - Configure and compile an implant
|
|
||||||
func (g *Generate) Execute(args []string) (err error) {
|
|
||||||
save := g.CoreOptions.Save
|
|
||||||
if save == "" {
|
|
||||||
save, _ = os.Getwd()
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Executed 'generate' command. ")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regenerate - Recompile an implant by name, passed as argument (completed)
|
|
||||||
type Regenerate struct {
|
|
||||||
Positional struct {
|
|
||||||
ImplantName string `description:"Name of Sliver implant to recompile" required:"1-1"`
|
|
||||||
} `positional-args:"yes" required:"yes"`
|
|
||||||
Save string `long:"save" short:"s" description:"Directory/file where to save binary"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute - Recompile an implant with a given profile
|
|
||||||
func (r *Regenerate) Execute(args []string) (err error) {
|
|
||||||
fmt.Println("Executed 'regenerate' command. ")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateStager - Generate a stager payload using MSFVenom
|
|
||||||
type GenerateStager struct {
|
|
||||||
PayloadOptions struct {
|
|
||||||
OS string `long:"os" short:"o" description:"target host operating system" default:"windows" value-name:"stage OS"`
|
|
||||||
Arch string `long:"arch" short:"a" description:"target host CPU architecture" default:"amd64" value-name:"stage architectures"`
|
|
||||||
Format string `long:"msf-format" short:"f" description:"output format (MSF Venom formats). List is auto-completed" default:"raw" value-name:"MSF Venom transform formats"`
|
|
||||||
BadChars string `long:"badchars" short:"b" description:"bytes to exclude from stage shellcode"`
|
|
||||||
Save string `long:"save" short:"s" description:"directory to save the generated stager to"`
|
|
||||||
} `group:"payload options"`
|
|
||||||
TransportOptions struct {
|
|
||||||
LHost string `long:"lhost" short:"l" description:"listening host address" required:"true"`
|
|
||||||
LPort int `long:"lport" short:"p" description:"listening host port" default:"8443"`
|
|
||||||
Protocol string `long:"protocol" short:"P" description:"staging protocol (tcp/http/https)" default:"tcp" value-name:"stager protocol"`
|
|
||||||
} `group:"transport options"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute - Generate a stager payload using MSFVenom
|
|
||||||
func (g *GenerateStager) Execute(args []string) (err error) {
|
|
||||||
fmt.Println("Executed 'generate stager' subcommand. ")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log - Log management commands. Sets log level by default.
|
|
||||||
type Log struct {
|
|
||||||
Positional struct {
|
|
||||||
Level string `description:"log level to filter by" required:"1-1"`
|
|
||||||
Components []string `description:"components on which to apply log filter" required:"1"`
|
|
||||||
} `positional-args:"yes" required:"true"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute - Set the log level of one or more components
|
|
||||||
func (l *Log) Execute(args []string) (err error) {
|
|
||||||
fmt.Println("Executed 'log' command. ")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
Info = fmt.Sprintf("%s[-]%s ", readline.BLUE, readline.RESET)
|
|
||||||
Warn = fmt.Sprintf("%s[!]%s ", readline.YELLOW, readline.RESET)
|
|
||||||
Error = fmt.Sprintf("%s[!]%s ", readline.RED, readline.RESET)
|
|
||||||
Success = fmt.Sprintf("%s[*]%s ", readline.GREEN, readline.RESET)
|
|
||||||
|
|
||||||
Infof = fmt.Sprintf("%s[-] ", readline.BLUE) // Infof - formatted
|
|
||||||
Warnf = fmt.Sprintf("%s[!] ", readline.YELLOW) // Warnf - formatted
|
|
||||||
Errorf = fmt.Sprintf("%s[!] ", readline.RED) // Errorf - formatted
|
|
||||||
Sucessf = fmt.Sprintf("%s[*] ", readline.GREEN) // Sucessf - formatted
|
|
||||||
|
|
||||||
RPCError = fmt.Sprintf("%s[RPC Error]%s ", readline.RED, readline.RESET)
|
|
||||||
CommandError = fmt.Sprintf("%s[Command Error]%s ", readline.RED, readline.RESET)
|
|
||||||
ParserError = fmt.Sprintf("%s[Parser Error]%s ", readline.RED, readline.RESET)
|
|
||||||
DBError = fmt.Sprintf("%s[DB Error]%s ", readline.RED, readline.RESET)
|
|
||||||
)
|
|
||||||
|
|
||||||
// expand will expand a path with ~ to the $HOME of the current user.
|
|
||||||
func expand(path string) (string, error) {
|
|
||||||
if path == "" {
|
|
||||||
return path, nil
|
|
||||||
}
|
|
||||||
home := os.Getenv("HOME")
|
|
||||||
if home == "" {
|
|
||||||
usr, err := user.Current()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
home = usr.HomeDir
|
|
||||||
}
|
|
||||||
return filepath.Abs(strings.Replace(path, "~", home, 1))
|
|
||||||
}
|
|
@ -1,171 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
rt "github.com/arnodel/golua/runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Loader(rtm *rt.Runtime) rt.Value {
|
|
||||||
return rt.StringValue("hello world!")
|
|
||||||
}
|
|
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user