mirror of
https://github.com/Hilbis/Hilbish
synced 2025-04-21 04:53:24 +00:00
Compare commits
27 Commits
Author | SHA1 | Date | |
---|---|---|---|
6827940466 | |||
fde615ff3f | |||
d002c82271 | |||
f64229b52c | |||
|
3d5766ac33 | ||
60edfc00ee | |||
6cd294373c | |||
02c89b99dd | |||
fe4e972fbe | |||
e4a9e06d2a | |||
487b5fa4f8 | |||
|
946e8e7228 | ||
04206b6a14 | |||
9ea2a2f332 | |||
|
364cb1ca2e | ||
|
2ba878713c | ||
2678ec723e | |||
dbf2d80863 | |||
fab98bc613 | |||
|
7b16cde540 | ||
4150001d8b | |||
32ed0d2348 | |||
8731b1a7d1 | |||
4743222044 | |||
a02cd1d7ef | |||
c969f5ed15 | |||
|
36ce05e85a |
11
.github/workflows/docs.yml
vendored
11
.github/workflows/docs.yml
vendored
@ -10,9 +10,18 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
fetch-depth: 0
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
- name: Run docgen
|
- name: Download Task
|
||||||
|
run: 'sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d'
|
||||||
|
- name: Build
|
||||||
|
run: ./bin/task
|
||||||
|
- name: Run docgen (go-written)
|
||||||
run: go run cmd/docgen/docgen.go
|
run: go run cmd/docgen/docgen.go
|
||||||
|
- name: Run docgen (lua-written)
|
||||||
|
run: ./hilbish cmd/docgen/docgen.lua
|
||||||
- name: Commit new docs
|
- name: Commit new docs
|
||||||
uses: stefanzweifel/git-auto-commit-action@v4
|
uses: stefanzweifel/git-auto-commit-action@v4
|
||||||
with:
|
with:
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
# 🎀 Changelog
|
# 🎀 Changelog
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
### Added
|
||||||
|
- Forward/Right arrow key will fill in hint text (#327)
|
||||||
|
|
||||||
## [2.3.4] - 2024-12-28
|
## [2.3.4] - 2024-12-28
|
||||||
### Fixed
|
### Fixed
|
||||||
- Skip over file and prevent panic if info cannot be retrieved during file completion (due to permission error or anything else)
|
- Skip over file and prevent panic if info cannot be retrieved during file completion (due to permission error or anything else)
|
||||||
- Apply environment variables properly after 2.3 shell interpreter changes
|
- Apply environment variables properly after 2.3 shell interpreter changes
|
||||||
|
- hilbish.sink.readAll() function now reads data that doesn't end in a newline
|
||||||
|
|
||||||
## [2.3.3] - 2024-11-04
|
## [2.3.3] - 2024-11-04
|
||||||
### Fixed
|
### Fixed
|
||||||
|
152
api.go
152
api.go
@ -13,10 +13,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
//"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
@ -28,9 +27,9 @@ import (
|
|||||||
|
|
||||||
rt "github.com/arnodel/golua/runtime"
|
rt "github.com/arnodel/golua/runtime"
|
||||||
"github.com/arnodel/golua/lib/packagelib"
|
"github.com/arnodel/golua/lib/packagelib"
|
||||||
"github.com/arnodel/golua/lib/iolib"
|
//"github.com/arnodel/golua/lib/iolib"
|
||||||
"github.com/maxlandon/readline"
|
"github.com/maxlandon/readline"
|
||||||
"mvdan.cc/sh/v3/interp"
|
//"mvdan.cc/sh/v3/interp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var exports = map[string]util.LuaExport{
|
var exports = map[string]util.LuaExport{
|
||||||
@ -39,7 +38,6 @@ var exports = map[string]util.LuaExport{
|
|||||||
"complete": {hlcomplete, 2, false},
|
"complete": {hlcomplete, 2, false},
|
||||||
"cwd": {hlcwd, 0, false},
|
"cwd": {hlcwd, 0, false},
|
||||||
"exec": {hlexec, 1, false},
|
"exec": {hlexec, 1, false},
|
||||||
"runnerMode": {hlrunnerMode, 1, false},
|
|
||||||
"goro": {hlgoro, 1, true},
|
"goro": {hlgoro, 1, true},
|
||||||
"highlighter": {hlhighlighter, 1, false},
|
"highlighter": {hlhighlighter, 1, false},
|
||||||
"hinter": {hlhinter, 1, false},
|
"hinter": {hlhinter, 1, false},
|
||||||
@ -49,7 +47,6 @@ var exports = map[string]util.LuaExport{
|
|||||||
"inputMode": {hlinputMode, 1, false},
|
"inputMode": {hlinputMode, 1, false},
|
||||||
"interval": {hlinterval, 2, false},
|
"interval": {hlinterval, 2, false},
|
||||||
"read": {hlread, 1, false},
|
"read": {hlread, 1, false},
|
||||||
"run": {hlrun, 1, true},
|
|
||||||
"timeout": {hltimeout, 2, false},
|
"timeout": {hltimeout, 2, false},
|
||||||
"which": {hlwhich, 1, false},
|
"which": {hlwhich, 1, false},
|
||||||
}
|
}
|
||||||
@ -134,6 +131,9 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
|
|||||||
pluginModule := moduleLoader(rtm)
|
pluginModule := moduleLoader(rtm)
|
||||||
mod.Set(rt.StringValue("module"), rt.TableValue(pluginModule))
|
mod.Set(rt.StringValue("module"), rt.TableValue(pluginModule))
|
||||||
|
|
||||||
|
sinkModule := util.SinkLoader(l)
|
||||||
|
mod.Set(rt.StringValue("sink"), rt.TableValue(sinkModule))
|
||||||
|
|
||||||
return rt.TableValue(mod), nil
|
return rt.TableValue(mod), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,6 +154,7 @@ func unsetVimMode() {
|
|||||||
util.SetField(l, hshMod, "vimMode", rt.NilValue)
|
util.SetField(l, hshMod, "vimMode", rt.NilValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
func handleStream(v rt.Value, strms *streams, errStream bool) error {
|
func handleStream(v rt.Value, strms *streams, errStream bool) error {
|
||||||
ud, ok := v.TryUserData()
|
ud, ok := v.TryUserData()
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -182,112 +183,7 @@ func handleStream(v rt.Value, strms *streams, errStream bool) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// run(cmd, streams) -> exitCode (number), stdout (string), stderr (string)
|
|
||||||
// Runs `cmd` in Hilbish's shell script interpreter.
|
|
||||||
// The `streams` parameter specifies the output and input streams the command should use.
|
|
||||||
// For example, to write command output to a sink.
|
|
||||||
// As a table, the caller can directly specify the standard output, error, and input
|
|
||||||
// streams of the command with the table keys `out`, `err`, and `input` respectively.
|
|
||||||
// As a boolean, it specifies whether the command should use standard output or return its output streams.
|
|
||||||
// #param cmd string
|
|
||||||
// #param streams table|boolean
|
|
||||||
// #returns number, string, string
|
|
||||||
// #example
|
|
||||||
/*
|
|
||||||
// This code is the same as `ls -l | wc -l`
|
|
||||||
local fs = require 'fs'
|
|
||||||
local pr, pw = fs.pipe()
|
|
||||||
hilbish.run('ls -l', {
|
|
||||||
stdout = pw,
|
|
||||||
stderr = pw,
|
|
||||||
})
|
|
||||||
|
|
||||||
pw:close()
|
|
||||||
|
|
||||||
hilbish.run('wc -l', {
|
|
||||||
stdin = pr
|
|
||||||
})
|
|
||||||
*/
|
*/
|
||||||
// #example
|
|
||||||
func hlrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
||||||
// TODO: ON BREAKING RELEASE, DO NOT ACCEPT `streams` AS A BOOLEAN.
|
|
||||||
if err := c.Check1Arg(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cmd, err := c.StringArg(0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
strms := &streams{}
|
|
||||||
var terminalOut bool
|
|
||||||
if len(c.Etc()) != 0 {
|
|
||||||
tout := c.Etc()[0]
|
|
||||||
|
|
||||||
var ok bool
|
|
||||||
terminalOut, ok = tout.TryBool()
|
|
||||||
if !ok {
|
|
||||||
luastreams, ok := tout.TryTable()
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("bad argument to run (expected boolean or table, got " + tout.TypeName() + ")")
|
|
||||||
}
|
|
||||||
|
|
||||||
handleStream(luastreams.Get(rt.StringValue("out")), strms, false)
|
|
||||||
handleStream(luastreams.Get(rt.StringValue("err")), strms, true)
|
|
||||||
|
|
||||||
stdinstrm := luastreams.Get(rt.StringValue("input"))
|
|
||||||
if !stdinstrm.IsNil() {
|
|
||||||
ud, ok := stdinstrm.TryUserData()
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("bad type as run stdin stream (expected userdata as either sink or file, got " + stdinstrm.TypeName() + ")")
|
|
||||||
}
|
|
||||||
|
|
||||||
val := ud.Value()
|
|
||||||
var varstrm io.Reader
|
|
||||||
if f, ok := val.(*iolib.File); ok {
|
|
||||||
varstrm = f.Handle()
|
|
||||||
}
|
|
||||||
|
|
||||||
if f, ok := val.(*sink); ok {
|
|
||||||
varstrm = f.reader
|
|
||||||
}
|
|
||||||
|
|
||||||
if varstrm == nil {
|
|
||||||
return nil, errors.New("bad type as run stdin stream (expected userdata as either sink or file)")
|
|
||||||
}
|
|
||||||
|
|
||||||
strms.stdin = varstrm
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if !terminalOut {
|
|
||||||
strms = &streams{
|
|
||||||
stdout: new(bytes.Buffer),
|
|
||||||
stderr: new(bytes.Buffer),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var exitcode uint8
|
|
||||||
stdout, stderr, err := execCommand(cmd, strms)
|
|
||||||
|
|
||||||
if code, ok := interp.IsExitStatus(err); ok {
|
|
||||||
exitcode = code
|
|
||||||
} else if err != nil {
|
|
||||||
exitcode = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
var stdoutStr, stderrStr string
|
|
||||||
if stdoutBuf, ok := stdout.(*bytes.Buffer); ok {
|
|
||||||
stdoutStr = stdoutBuf.String()
|
|
||||||
}
|
|
||||||
if stderrBuf, ok := stderr.(*bytes.Buffer); ok {
|
|
||||||
stderrStr = stderrBuf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.PushingNext(t.Runtime, rt.IntValue(int64(exitcode)), rt.StringValue(stdoutStr), rt.StringValue(stderrStr)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cwd() -> string
|
// cwd() -> string
|
||||||
// Returns the current directory of the shell.
|
// Returns the current directory of the shell.
|
||||||
@ -404,7 +300,7 @@ hilbish.multiprompt '-->'
|
|||||||
*/
|
*/
|
||||||
func hlmultiprompt(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func hlmultiprompt(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
if err := c.Check1Arg(); err != nil {
|
if err := c.Check1Arg(); err != nil {
|
||||||
return nil, err
|
return c.PushingNext1(t.Runtime, rt.StringValue(multilinePrompt)), nil
|
||||||
}
|
}
|
||||||
prompt, err := c.StringArg(0)
|
prompt, err := c.StringArg(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -508,7 +404,7 @@ func hlexec(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
}
|
}
|
||||||
cmdArgs, _ := splitInput(cmd)
|
cmdArgs, _ := splitInput(cmd)
|
||||||
if runtime.GOOS != "windows" {
|
if runtime.GOOS != "windows" {
|
||||||
cmdPath, err := exec.LookPath(cmdArgs[0])
|
cmdPath, err := util.LookPath(cmdArgs[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
// if we get here, cmdPath will be nothing
|
// if we get here, cmdPath will be nothing
|
||||||
@ -706,7 +602,7 @@ func hlwhich(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
return c.PushingNext1(t.Runtime, rt.StringValue(cmd)), nil
|
return c.PushingNext1(t.Runtime, rt.StringValue(cmd)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
path, err := exec.LookPath(cmd)
|
path, err := util.LookPath(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Next(), nil
|
return c.Next(), nil
|
||||||
}
|
}
|
||||||
@ -742,34 +638,6 @@ func hlinputMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
return c.Next(), nil
|
return c.Next(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// runnerMode(mode)
|
|
||||||
// Sets the execution/runner mode for interactive Hilbish.
|
|
||||||
// This determines whether Hilbish wll try to run input as Lua
|
|
||||||
// and/or sh or only do one of either.
|
|
||||||
// Accepted values for mode are hybrid (the default), hybridRev (sh first then Lua),
|
|
||||||
// sh, and lua. It also accepts a function, to which if it is passed one
|
|
||||||
// will call it to execute user input instead.
|
|
||||||
// Read [about runner mode](../features/runner-mode) for more information.
|
|
||||||
// #param mode string|function
|
|
||||||
func hlrunnerMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
||||||
if err := c.Check1Arg(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mode := c.Arg(0)
|
|
||||||
|
|
||||||
switch mode.Type() {
|
|
||||||
case rt.StringType:
|
|
||||||
switch mode.AsString() {
|
|
||||||
case "hybrid", "hybridRev", "lua", "sh": runnerMode = mode
|
|
||||||
default: return nil, errors.New("execMode: expected either a function or hybrid, hybridRev, lua, sh. Received " + mode.AsString())
|
|
||||||
}
|
|
||||||
case rt.FunctionType: runnerMode = mode
|
|
||||||
default: return nil, errors.New("execMode: expected either a function or hybrid, hybridRev, lua, sh. Received " + mode.TypeName())
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Next(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// hinter(line, pos)
|
// hinter(line, pos)
|
||||||
// The command line hint handler. It gets called on every key insert to
|
// The command line hint handler. It gets called on every key insert to
|
||||||
// determine what text to use as an inline hint. It is passed the current
|
// determine what text to use as an inline hint. It is passed the current
|
||||||
|
@ -84,6 +84,7 @@ var prefix = map[string]string{
|
|||||||
"commander": "c",
|
"commander": "c",
|
||||||
"bait": "b",
|
"bait": "b",
|
||||||
"terminal": "term",
|
"terminal": "term",
|
||||||
|
"snail": "snail",
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTagsAndDocs(docs string) (map[string][]tag, []string) {
|
func getTagsAndDocs(docs string) (map[string][]tag, []string) {
|
||||||
@ -208,6 +209,10 @@ func setupDocType(mod string, typ *doc.Type) *docPiece {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func setupDoc(mod string, fun *doc.Func) *docPiece {
|
func setupDoc(mod string, fun *doc.Func) *docPiece {
|
||||||
|
if fun.Doc == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
docs := strings.TrimSpace(fun.Doc)
|
docs := strings.TrimSpace(fun.Doc)
|
||||||
tags, parts := getTagsAndDocs(docs)
|
tags, parts := getTagsAndDocs(docs)
|
||||||
|
|
||||||
@ -299,10 +304,28 @@ start:
|
|||||||
func main() {
|
func main() {
|
||||||
fset := token.NewFileSet()
|
fset := token.NewFileSet()
|
||||||
os.Mkdir("docs", 0777)
|
os.Mkdir("docs", 0777)
|
||||||
|
os.RemoveAll("docs/api")
|
||||||
os.Mkdir("docs/api", 0777)
|
os.Mkdir("docs/api", 0777)
|
||||||
|
|
||||||
|
f, err := os.Create("docs/api/_index.md")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
f.WriteString(`---
|
||||||
|
title: API
|
||||||
|
layout: doc
|
||||||
|
weight: -100
|
||||||
|
menu: docs
|
||||||
|
---
|
||||||
|
|
||||||
|
Welcome to the API documentation for Hilbish. This documents Lua functions
|
||||||
|
provided by Hilbish.
|
||||||
|
`)
|
||||||
|
f.Close()
|
||||||
|
|
||||||
os.Mkdir("emmyLuaDocs", 0777)
|
os.Mkdir("emmyLuaDocs", 0777)
|
||||||
|
|
||||||
dirs := []string{"./"}
|
dirs := []string{"./", "./util"}
|
||||||
filepath.Walk("golibs/", func (path string, info os.FileInfo, err error) error {
|
filepath.Walk("golibs/", func (path string, info os.FileInfo, err error) error {
|
||||||
if !info.IsDir() {
|
if !info.IsDir() {
|
||||||
return nil
|
return nil
|
||||||
@ -329,7 +352,7 @@ func main() {
|
|||||||
pieces := []docPiece{}
|
pieces := []docPiece{}
|
||||||
typePieces := []docPiece{}
|
typePieces := []docPiece{}
|
||||||
mod := l
|
mod := l
|
||||||
if mod == "main" {
|
if mod == "main" || mod == "util" {
|
||||||
mod = "hilbish"
|
mod = "hilbish"
|
||||||
}
|
}
|
||||||
var hasInterfaces bool
|
var hasInterfaces bool
|
||||||
@ -413,6 +436,14 @@ func main() {
|
|||||||
interfaceModules[modname].Types = append(interfaceModules[modname].Types, piece)
|
interfaceModules[modname].Types = append(interfaceModules[modname].Types, piece)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println(filteredTypePieces)
|
||||||
|
if newDoc, ok := docs[mod]; ok {
|
||||||
|
oldMod := docs[mod]
|
||||||
|
newDoc.Types = append(filteredTypePieces, oldMod.Types...)
|
||||||
|
newDoc.Docs = append(filteredPieces, oldMod.Docs...)
|
||||||
|
|
||||||
|
docs[mod] = newDoc
|
||||||
|
} else {
|
||||||
docs[mod] = module{
|
docs[mod] = module{
|
||||||
Types: filteredTypePieces,
|
Types: filteredTypePieces,
|
||||||
Docs: filteredPieces,
|
Docs: filteredPieces,
|
||||||
@ -423,6 +454,7 @@ func main() {
|
|||||||
Fields: docPieceTag("field", tags),
|
Fields: docPieceTag("field", tags),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for key, mod := range interfaceModules {
|
for key, mod := range interfaceModules {
|
||||||
docs[key] = *mod
|
docs[key] = *mod
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
local fs = require 'fs'
|
local fs = require 'fs'
|
||||||
local emmyPattern = '^%-%-%- (.+)'
|
local emmyPattern = '^%-%-%- (.+)'
|
||||||
local modpattern = '^%-+ @module (%w+)'
|
local emmyPattern2 = '^%-%- (.+)'
|
||||||
|
local modpattern = '^%-+ @module (.+)'
|
||||||
local pieces = {}
|
local pieces = {}
|
||||||
|
local descriptions = {}
|
||||||
|
|
||||||
local files = fs.readdir 'nature'
|
local files = fs.readdir 'nature'
|
||||||
for _, fname in ipairs(files) do
|
for _, fname in ipairs(files) do
|
||||||
@ -13,18 +15,25 @@ for _, fname in ipairs(files) do
|
|||||||
local mod = header:match(modpattern)
|
local mod = header:match(modpattern)
|
||||||
if not mod then goto continue end
|
if not mod then goto continue end
|
||||||
|
|
||||||
print(fname, mod)
|
|
||||||
pieces[mod] = {}
|
pieces[mod] = {}
|
||||||
|
descriptions[mod] = {}
|
||||||
|
|
||||||
local docPiece = {}
|
local docPiece = {}
|
||||||
local lines = {}
|
local lines = {}
|
||||||
local lineno = 0
|
local lineno = 0
|
||||||
|
local doingDescription = true
|
||||||
|
|
||||||
for line in f:lines() do
|
for line in f:lines() do
|
||||||
lineno = lineno + 1
|
lineno = lineno + 1
|
||||||
lines[lineno] = line
|
lines[lineno] = line
|
||||||
|
|
||||||
if line == header then goto continue2 end
|
if line == header then goto continue2 end
|
||||||
if not line:match(emmyPattern) then
|
if line:match(emmyPattern) or line:match(emmyPattern2) then
|
||||||
|
if doingDescription then
|
||||||
|
table.insert(descriptions[mod], line:match(emmyPattern) or line:match(emmyPattern2))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
doingDescription = false
|
||||||
if line:match '^function' then
|
if line:match '^function' then
|
||||||
local pattern = (string.format('^function %s%%.', mod) .. '(%w+)')
|
local pattern = (string.format('^function %s%%.', mod) .. '(%w+)')
|
||||||
local funcName = line:match(pattern)
|
local funcName = line:match(pattern)
|
||||||
@ -32,10 +41,12 @@ for _, fname in ipairs(files) do
|
|||||||
|
|
||||||
local dps = {
|
local dps = {
|
||||||
description = {},
|
description = {},
|
||||||
|
example = {},
|
||||||
params = {}
|
params = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
local offset = 1
|
local offset = 1
|
||||||
|
local doingExample = false
|
||||||
while true do
|
while true do
|
||||||
local prev = lines[lineno - offset]
|
local prev = lines[lineno - offset]
|
||||||
|
|
||||||
@ -51,19 +62,31 @@ for _, fname in ipairs(files) do
|
|||||||
if emmy == 'param' then
|
if emmy == 'param' then
|
||||||
table.insert(dps.params, 1, {
|
table.insert(dps.params, 1, {
|
||||||
name = emmythings[1],
|
name = emmythings[1],
|
||||||
type = emmythings[2]
|
type = emmythings[2],
|
||||||
|
-- the +1 accounts for space.
|
||||||
|
description = table.concat(emmythings, ' '):sub(emmythings[1]:len() + 1 + emmythings[2]:len() + 1)
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
if docline:match '#example' then
|
||||||
|
doingExample = not doingExample
|
||||||
|
end
|
||||||
|
|
||||||
|
if not docline:match '#example' then
|
||||||
|
if doingExample then
|
||||||
|
table.insert(dps.example, 1, docline)
|
||||||
else
|
else
|
||||||
table.insert(dps.description, 1, docline)
|
table.insert(dps.description, 1, docline)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
offset = offset + 1
|
offset = offset + 1
|
||||||
else
|
else
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
pieces[mod][funcName] = dps
|
table.insert(pieces[mod], {funcName, dps})
|
||||||
end
|
end
|
||||||
docPiece = {}
|
docPiece = {}
|
||||||
goto continue2
|
goto continue2
|
||||||
@ -81,29 +104,82 @@ description: %s
|
|||||||
layout: doc
|
layout: doc
|
||||||
menu:
|
menu:
|
||||||
docs:
|
docs:
|
||||||
parent: "Nature"
|
parent: "%s"
|
||||||
---
|
---
|
||||||
|
|
||||||
]]
|
]]
|
||||||
|
|
||||||
for iface, dps in pairs(pieces) do
|
for iface, dps in pairs(pieces) do
|
||||||
local mod = iface:match '(%w+)%.' or 'nature'
|
local mod = iface:match '(%w+)%.' or 'nature'
|
||||||
local path = string.format('docs/%s/%s.md', mod, iface)
|
local docParent = 'Nature'
|
||||||
|
|
||||||
|
path = string.format('docs/%s/%s.md', mod, iface)
|
||||||
|
if mod ~= 'nature' then
|
||||||
|
docParent = "API"
|
||||||
|
path = string.format('docs/api/%s/%s.md', mod, iface)
|
||||||
|
end
|
||||||
|
if iface == 'hilbish' then
|
||||||
|
docParent = "API"
|
||||||
|
path = string.format('docs/api/hilbish/_index.md', mod, iface)
|
||||||
|
end
|
||||||
|
|
||||||
fs.mkdir(fs.dir(path), true)
|
fs.mkdir(fs.dir(path), true)
|
||||||
local f <close> = io.open(path, 'w')
|
|
||||||
f:write(string.format(header, 'Module', iface, 'No description.'))
|
|
||||||
print(f)
|
|
||||||
|
|
||||||
print(mod, path)
|
local exists = pcall(fs.stat, path)
|
||||||
|
local newOrNotNature = (exists and mod ~= 'nature') or iface == 'hilbish'
|
||||||
|
|
||||||
for func, docs in pairs(dps) do
|
local f <close> = io.open(path, newOrNotNature and 'r+' or 'w+')
|
||||||
f:write(string.format('<hr>\n<div id=\'%s\'>', func))
|
local tocPos
|
||||||
|
if not newOrNotNature then
|
||||||
|
f:write(string.format(header, 'Module', iface, (descriptions[iface] and #descriptions[iface] > 0) and descriptions[iface][1] or 'No description.', docParent))
|
||||||
|
if descriptions[iface] and #descriptions[iface] > 0 then
|
||||||
|
table.remove(descriptions[iface], 1)
|
||||||
|
f:write(string.format('\n## Introduction\n%s\n\n', table.concat(descriptions[iface], '\n')))
|
||||||
|
f:write('## Functions\n')
|
||||||
|
f:write([[|||
|
||||||
|
|----|----|
|
||||||
|
]])
|
||||||
|
tocPos = f:seek()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local tocSearch = false
|
||||||
|
for line in f:lines() do
|
||||||
|
if line:match '^## Functions' then
|
||||||
|
tocSearch = true
|
||||||
|
end
|
||||||
|
if tocSearch and line == '' then
|
||||||
|
tocSearch = false
|
||||||
|
tocPos = f:seek() - 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(dps, function(a, b) return a[1] < b[1] end)
|
||||||
|
for _, piece in pairs(dps) do
|
||||||
|
local func = piece[1]
|
||||||
|
local docs = piece[2]
|
||||||
local sig = string.format('%s.%s(', iface, func)
|
local sig = string.format('%s.%s(', iface, func)
|
||||||
|
local params = ''
|
||||||
for idx, param in ipairs(docs.params) do
|
for idx, param in ipairs(docs.params) do
|
||||||
sig = sig .. ((param.name:gsub('%?$', '')))
|
sig = sig .. param.name:gsub('%?$', '')
|
||||||
if idx ~= #docs.params then sig = sig .. ', ' end
|
params = params .. param.name:gsub('%?$', '')
|
||||||
|
if idx ~= #docs.params then
|
||||||
|
sig = sig .. ', '
|
||||||
|
params = params .. ', '
|
||||||
|
end
|
||||||
end
|
end
|
||||||
sig = sig .. ')'
|
sig = sig .. ')'
|
||||||
|
|
||||||
|
if tocPos then
|
||||||
|
f:seek('set', tocPos)
|
||||||
|
local contents = f:read '*a'
|
||||||
|
f:seek('set', tocPos)
|
||||||
|
local tocLine = string.format('|<a href="#%s">%s</a>|%s|\n', func, string.format('%s(%s)', func, params), docs.description[1])
|
||||||
|
f:write(tocLine .. contents)
|
||||||
|
f:seek 'end'
|
||||||
|
end
|
||||||
|
|
||||||
|
f:write(string.format('<hr>\n<div id=\'%s\'>\n', func))
|
||||||
f:write(string.format([[
|
f:write(string.format([[
|
||||||
<h4 class='heading'>
|
<h4 class='heading'>
|
||||||
%s
|
%s
|
||||||
@ -120,7 +196,12 @@ for iface, dps in pairs(pieces) do
|
|||||||
f:write 'This function has no parameters. \n'
|
f:write 'This function has no parameters. \n'
|
||||||
end
|
end
|
||||||
for _, param in ipairs(docs.params) do
|
for _, param in ipairs(docs.params) do
|
||||||
f:write(string.format('`%s` **`%s`**\n', param.name:gsub('%?$', ''), param.type))
|
f:write(string.format('`%s` **`%s`** \n', param.name:gsub('%?$', ''), param.type))
|
||||||
|
f:write(string.format('%s\n\n', param.description))
|
||||||
|
end
|
||||||
|
if #docs.example ~= 0 then
|
||||||
|
f:write '#### Example\n'
|
||||||
|
f:write(string.format('```lua\n%s\n```\n', table.concat(docs.example, '\n')))
|
||||||
end
|
end
|
||||||
--[[
|
--[[
|
||||||
local params = table.filter(docs, function(t)
|
local params = table.filter(docs, function(t)
|
||||||
|
@ -98,7 +98,7 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) {
|
|||||||
if len(fileCompletions) != 0 {
|
if len(fileCompletions) != 0 {
|
||||||
for _, f := range fileCompletions {
|
for _, f := range fileCompletions {
|
||||||
fullPath, _ := filepath.Abs(util.ExpandHome(query + strings.TrimPrefix(f, filePref)))
|
fullPath, _ := filepath.Abs(util.ExpandHome(query + strings.TrimPrefix(f, filePref)))
|
||||||
if err := findExecutable(escapeInvertReplaer.Replace(fullPath), false, true); err != nil {
|
if err := util.FindExecutable(escapeInvertReplaer.Replace(fullPath), false, true); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
completions = append(completions, f)
|
completions = append(completions, f)
|
||||||
@ -115,7 +115,7 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) {
|
|||||||
// get basename from matches
|
// get basename from matches
|
||||||
for _, match := range matches {
|
for _, match := range matches {
|
||||||
// check if we have execute permissions for our match
|
// check if we have execute permissions for our match
|
||||||
err := findExecutable(match, true, false)
|
err := util.FindExecutable(match, true, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -28,10 +28,10 @@ interfaces and functions which directly relate to shell functionality.
|
|||||||
|<a href="#prependPath">prependPath(dir)</a>|Prepends `dir` to $PATH.|
|
|<a href="#prependPath">prependPath(dir)</a>|Prepends `dir` to $PATH.|
|
||||||
|<a href="#prompt">prompt(str, typ)</a>|Changes the shell prompt to the provided string.|
|
|<a href="#prompt">prompt(str, typ)</a>|Changes the shell prompt to the provided string.|
|
||||||
|<a href="#read">read(prompt) -> input (string)</a>|Read input from the user, using Hilbish's line editor/input reader.|
|
|<a href="#read">read(prompt) -> input (string)</a>|Read input from the user, using Hilbish's line editor/input reader.|
|
||||||
|<a href="#run">run(cmd, streams) -> exitCode (number), stdout (string), stderr (string)</a>|Runs `cmd` in Hilbish's shell script interpreter.|
|
|
||||||
|<a href="#runnerMode">runnerMode(mode)</a>|Sets the execution/runner mode for interactive Hilbish.|
|
|
||||||
|<a href="#timeout">timeout(cb, time) -> @Timer</a>|Executed the `cb` function after a period of `time`.|
|
|<a href="#timeout">timeout(cb, time) -> @Timer</a>|Executed the `cb` function after a period of `time`.|
|
||||||
|<a href="#which">which(name) -> string</a>|Checks if `name` is a valid command.|
|
|<a href="#which">which(name) -> string</a>|Checks if `name` is a valid command.|
|
||||||
|
|<a href="#runnerMode">runnerMode(mode)</a>|Sets the execution/runner mode for interactive Hilbish.|
|
||||||
|
|<a href="#run">run(cmd, streams)</a>|Runs `cmd` in Hilbish's shell script interpreter.|
|
||||||
|
|
||||||
## Static module fields
|
## Static module fields
|
||||||
|||
|
|||
|
||||||
@ -408,72 +408,6 @@ Returns `input`, will be nil if Ctrl-D is pressed, or an error occurs.
|
|||||||
`string` **`prompt?`**
|
`string` **`prompt?`**
|
||||||
Text to print before input, can be empty.
|
Text to print before input, can be empty.
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
<div id='run'>
|
|
||||||
<h4 class='heading'>
|
|
||||||
hilbish.run(cmd, streams) -> exitCode (number), stdout (string), stderr (string)
|
|
||||||
<a href="#run" class='heading-link'>
|
|
||||||
<i class="fas fa-paperclip"></i>
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
Runs `cmd` in Hilbish's shell script interpreter.
|
|
||||||
The `streams` parameter specifies the output and input streams the command should use.
|
|
||||||
For example, to write command output to a sink.
|
|
||||||
As a table, the caller can directly specify the standard output, error, and input
|
|
||||||
streams of the command with the table keys `out`, `err`, and `input` respectively.
|
|
||||||
As a boolean, it specifies whether the command should use standard output or return its output streams.
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
`string` **`cmd`**
|
|
||||||
|
|
||||||
|
|
||||||
`table|boolean` **`streams`**
|
|
||||||
|
|
||||||
|
|
||||||
#### Example
|
|
||||||
```lua
|
|
||||||
|
|
||||||
// This code is the same as `ls -l | wc -l`
|
|
||||||
local fs = require 'fs'
|
|
||||||
local pr, pw = fs.pipe()
|
|
||||||
hilbish.run('ls -l', {
|
|
||||||
stdout = pw,
|
|
||||||
stderr = pw,
|
|
||||||
})
|
|
||||||
|
|
||||||
pw:close()
|
|
||||||
|
|
||||||
hilbish.run('wc -l', {
|
|
||||||
stdin = pr
|
|
||||||
})
|
|
||||||
|
|
||||||
```
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
<div id='runnerMode'>
|
|
||||||
<h4 class='heading'>
|
|
||||||
hilbish.runnerMode(mode)
|
|
||||||
<a href="#runnerMode" class='heading-link'>
|
|
||||||
<i class="fas fa-paperclip"></i>
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
Sets the execution/runner mode for interactive Hilbish.
|
|
||||||
This determines whether Hilbish wll try to run input as Lua
|
|
||||||
and/or sh or only do one of either.
|
|
||||||
Accepted values for mode are hybrid (the default), hybridRev (sh first then Lua),
|
|
||||||
sh, and lua. It also accepts a function, to which if it is passed one
|
|
||||||
will call it to execute user input instead.
|
|
||||||
Read [about runner mode](../features/runner-mode) for more information.
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
`string|function` **`mode`**
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
@ -519,8 +453,7 @@ Will return the path of the binary, or a basename if it's a commander.
|
|||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
## Sink
|
## Sink
|
||||||
A sink is a structure that has input and/or output to/from
|
A sink is a structure that has input and/or output to/from a desination.
|
||||||
a desination.
|
|
||||||
|
|
||||||
### Methods
|
### Methods
|
||||||
#### autoFlush(auto)
|
#### autoFlush(auto)
|
||||||
@ -542,3 +475,65 @@ Writes data to a sink.
|
|||||||
#### writeln(str)
|
#### writeln(str)
|
||||||
Writes data to a sink with a newline at the end.
|
Writes data to a sink with a newline at the end.
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='run'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.run(cmd, streams)
|
||||||
|
<a href="#run" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Runs `cmd` in Hilbish's shell script interpreter.
|
||||||
|
The `streams` parameter specifies the output and input streams the command should use.
|
||||||
|
For example, to write command output to a sink.
|
||||||
|
As a table, the caller can directly specify the standard output, error, and input
|
||||||
|
streams of the command with the table keys `out`, `err`, and `input` respectively.
|
||||||
|
As a boolean, it specifies whether the command should use standard output or return its output streams.
|
||||||
|
#### Parameters
|
||||||
|
`cmd` **`string`**
|
||||||
|
|
||||||
|
|
||||||
|
`streams` **`table|boolean`**
|
||||||
|
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
```lua
|
||||||
|
-- This code is the same as `ls -l | wc -l`
|
||||||
|
local fs = require 'fs'
|
||||||
|
local pr, pw = fs.pipe()
|
||||||
|
hilbish.run('ls -l', {
|
||||||
|
stdout = pw,
|
||||||
|
stderr = pw,
|
||||||
|
})
|
||||||
|
pw:close()
|
||||||
|
hilbish.run('wc -l', {
|
||||||
|
stdin = pr
|
||||||
|
})
|
||||||
|
```
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='runnerMode'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.runnerMode(mode)
|
||||||
|
<a href="#runnerMode" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Sets the execution/runner mode for interactive Hilbish.
|
||||||
|
**NOTE: This function is deprecated and will be removed in 3.0**
|
||||||
|
Use `hilbish.runner.setCurrent` instead.
|
||||||
|
This determines whether Hilbish wll try to run input as Lua
|
||||||
|
and/or sh or only do one of either.
|
||||||
|
Accepted values for mode are hybrid (the default), hybridRev (sh first then Lua),
|
||||||
|
sh, and lua. It also accepts a function, to which if it is passed one
|
||||||
|
will call it to execute user input instead.
|
||||||
|
Read [about runner mode](../features/runner-mode) for more information.
|
||||||
|
#### Parameters
|
||||||
|
`mode` **`string|function`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
67
docs/api/hilbish/hilbish.abbr.md
Normal file
67
docs/api/hilbish/hilbish.abbr.md
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
---
|
||||||
|
title: Module hilbish.abbr
|
||||||
|
description: command line abbreviations
|
||||||
|
layout: doc
|
||||||
|
menu:
|
||||||
|
docs:
|
||||||
|
parent: "API"
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
The abbr module manages Hilbish abbreviations. These are words that can be replaced
|
||||||
|
with longer command line strings when entered.
|
||||||
|
As an example, `git push` can be abbreviated to `gp`. When the user types
|
||||||
|
`gp` into the command line, after hitting space or enter, it will expand to `git push`.
|
||||||
|
Abbreviations can be used as an alternative to aliases. They are saved entirely in the history
|
||||||
|
Instead of the aliased form of the same command.
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|||
|
||||||
|
|----|----|
|
||||||
|
|<a href="#remove">remove(abbr)</a>|Removes the named `abbr`.|
|
||||||
|
|<a href="#add">add(abbr, expanded|function, opts)</a>|Adds an abbreviation. The `abbr` is the abbreviation itself,|
|
||||||
|
<hr>
|
||||||
|
<div id='add'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.abbr.add(abbr, expanded|function, opts)
|
||||||
|
<a href="#add" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Adds an abbreviation. The `abbr` is the abbreviation itself,
|
||||||
|
while `expanded` is what the abbreviation should expand to.
|
||||||
|
It can be either a function or a string. If it is a function, it will expand to what
|
||||||
|
the function returns.
|
||||||
|
`opts` is a table that accepts 1 key: `anywhere`.
|
||||||
|
`opts.anywhere` defines whether the abbr expands anywhere in the command line or not,
|
||||||
|
whereas the default behavior is only at the beginning of the line
|
||||||
|
#### Parameters
|
||||||
|
`abbr` **`string`**
|
||||||
|
|
||||||
|
|
||||||
|
`expanded|function` **`string`**
|
||||||
|
|
||||||
|
|
||||||
|
`opts` **`table`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='remove'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.abbr.remove(abbr)
|
||||||
|
<a href="#remove" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Removes the named `abbr`.
|
||||||
|
#### Parameters
|
||||||
|
`abbr` **`string`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
@ -14,12 +14,30 @@ directly interact with the line editor in use.
|
|||||||
## Functions
|
## Functions
|
||||||
|||
|
|||
|
||||||
|----|----|
|
|----|----|
|
||||||
|
|<a href="#editor.deleteByAmount">deleteByAmount(amount)</a>|Deletes characters in the line by the given amount.|
|
||||||
|<a href="#editor.getLine">getLine() -> string</a>|Returns the current input line.|
|
|<a href="#editor.getLine">getLine() -> string</a>|Returns the current input line.|
|
||||||
|<a href="#editor.getVimRegister">getVimRegister(register) -> string</a>|Returns the text that is at the register.|
|
|<a href="#editor.getVimRegister">getVimRegister(register) -> string</a>|Returns the text that is at the register.|
|
||||||
|<a href="#editor.insert">insert(text)</a>|Inserts text into the Hilbish command line.|
|
|<a href="#editor.insert">insert(text)</a>|Inserts text into the Hilbish command line.|
|
||||||
|<a href="#editor.getChar">getChar() -> string</a>|Reads a keystroke from the user. This is in a format of something like Ctrl-L.|
|
|<a href="#editor.getChar">getChar() -> string</a>|Reads a keystroke from the user. This is in a format of something like Ctrl-L.|
|
||||||
|<a href="#editor.setVimRegister">setVimRegister(register, text)</a>|Sets the vim register at `register` to hold the passed text.|
|
|<a href="#editor.setVimRegister">setVimRegister(register, text)</a>|Sets the vim register at `register` to hold the passed text.|
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='editor.deleteByAmount'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.editor.deleteByAmount(amount)
|
||||||
|
<a href="#editor.deleteByAmount" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Deletes characters in the line by the given amount.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
`number` **`amount`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<div id='editor.getLine'>
|
<div id='editor.getLine'>
|
||||||
<h4 class='heading'>
|
<h4 class='heading'>
|
||||||
@ -96,6 +114,9 @@ hilbish.editor.setVimRegister(register, text)
|
|||||||
Sets the vim register at `register` to hold the passed text.
|
Sets the vim register at `register` to hold the passed text.
|
||||||
|
|
||||||
#### Parameters
|
#### Parameters
|
||||||
|
`string` **`register`**
|
||||||
|
|
||||||
|
|
||||||
`string` **`text`**
|
`string` **`text`**
|
||||||
|
|
||||||
|
|
||||||
|
135
docs/api/hilbish/hilbish.messages.md
Normal file
135
docs/api/hilbish/hilbish.messages.md
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
---
|
||||||
|
title: Module hilbish.messages
|
||||||
|
description: simplistic message passing
|
||||||
|
layout: doc
|
||||||
|
menu:
|
||||||
|
docs:
|
||||||
|
parent: "API"
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
The messages interface defines a way for Hilbish-integrated commands,
|
||||||
|
user config and other tasks to send notifications to alert the user.z
|
||||||
|
The `hilbish.message` type is a table with the following keys:
|
||||||
|
`title` (string): A title for the message notification.
|
||||||
|
`text` (string): The contents of the message.
|
||||||
|
`channel` (string): States the origin of the message, `hilbish.*` is reserved for Hilbish tasks.
|
||||||
|
`summary` (string): A short summary of the `text`.
|
||||||
|
`icon` (string): Unicode (preferably standard emoji) icon for the message notification
|
||||||
|
`read` (boolean): Whether the full message has been read or not.
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|||
|
||||||
|
|----|----|
|
||||||
|
|<a href="#unreadCount">unreadCount()</a>|Returns the amount of unread messages.|
|
||||||
|
|<a href="#send">send(message)</a>|Sends a message.|
|
||||||
|
|<a href="#readAll">readAll()</a>|Marks all messages as read.|
|
||||||
|
|<a href="#read">read(idx)</a>|Marks a message at `idx` as read.|
|
||||||
|
|<a href="#delete">delete(idx)</a>|Deletes the message at `idx`.|
|
||||||
|
|<a href="#clear">clear()</a>|Deletes all messages.|
|
||||||
|
|<a href="#all">all()</a>|Returns all messages.|
|
||||||
|
<hr>
|
||||||
|
<div id='all'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.messages.all()
|
||||||
|
<a href="#all" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Returns all messages.
|
||||||
|
#### Parameters
|
||||||
|
This function has no parameters.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='clear'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.messages.clear()
|
||||||
|
<a href="#clear" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Deletes all messages.
|
||||||
|
#### Parameters
|
||||||
|
This function has no parameters.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='delete'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.messages.delete(idx)
|
||||||
|
<a href="#delete" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Deletes the message at `idx`.
|
||||||
|
#### Parameters
|
||||||
|
`idx` **`number`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='read'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.messages.read(idx)
|
||||||
|
<a href="#read" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Marks a message at `idx` as read.
|
||||||
|
#### Parameters
|
||||||
|
`idx` **`number`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='readAll'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.messages.readAll()
|
||||||
|
<a href="#readAll" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Marks all messages as read.
|
||||||
|
#### Parameters
|
||||||
|
This function has no parameters.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='send'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.messages.send(message)
|
||||||
|
<a href="#send" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Sends a message.
|
||||||
|
#### Parameters
|
||||||
|
`message` **`hilbish.message`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='unreadCount'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.messages.unreadCount()
|
||||||
|
<a href="#unreadCount" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Returns the amount of unread messages.
|
||||||
|
#### Parameters
|
||||||
|
This function has no parameters.
|
||||||
|
</div>
|
||||||
|
|
@ -54,29 +54,16 @@ end)
|
|||||||
## Functions
|
## Functions
|
||||||
|||
|
|||
|
||||||
|----|----|
|
|----|----|
|
||||||
|<a href="#runner.setMode">setMode(cb)</a>|This is the same as the `hilbish.runnerMode` function.|
|
|
||||||
|<a href="#runner.lua">lua(cmd)</a>|Evaluates `cmd` as Lua input. This is the same as using `dofile`|
|
|<a href="#runner.lua">lua(cmd)</a>|Evaluates `cmd` as Lua input. This is the same as using `dofile`|
|
||||||
|<a href="#runner.sh">sh(cmd)</a>|Runs a command in Hilbish's shell script interpreter.|
|
|<a href="#sh">sh()</a>|nil|
|
||||||
|
|<a href="#setMode">setMode(mode)</a>|**NOTE: This function is deprecated and will be removed in 3.0**|
|
||||||
<hr>
|
|<a href="#setCurrent">setCurrent(name)</a>|Sets Hilbish's runner mode by name.|
|
||||||
<div id='runner.setMode'>
|
|<a href="#set">set(name, runner)</a>|*Sets* a runner by name. The difference between this function and|
|
||||||
<h4 class='heading'>
|
|<a href="#run">run(input, priv)</a>|Runs `input` with the currently set Hilbish runner.|
|
||||||
hilbish.runner.setMode(cb)
|
|<a href="#getCurrent">getCurrent()</a>|Returns the current runner by name.|
|
||||||
<a href="#runner.setMode" class='heading-link'>
|
|<a href="#get">get(name)</a>|Get a runner by name.|
|
||||||
<i class="fas fa-paperclip"></i>
|
|<a href="#exec">exec(cmd, runnerName)</a>|Executes `cmd` with a runner.|
|
||||||
</a>
|
|<a href="#add">add(name, runner)</a>|Adds a runner to the table of available runners.|
|
||||||
</h4>
|
|
||||||
|
|
||||||
This is the same as the `hilbish.runnerMode` function.
|
|
||||||
It takes a callback, which will be used to execute all interactive input.
|
|
||||||
In normal cases, neither callbacks should be overrided by the user,
|
|
||||||
as the higher level functions listed below this will handle it.
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
`function` **`cb`**
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<div id='runner.lua'>
|
<div id='runner.lua'>
|
||||||
@ -97,20 +84,164 @@ or `load`, but is appropriated for the runner interface.
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<div id='runner.sh'>
|
<div id='add'>
|
||||||
<h4 class='heading'>
|
<h4 class='heading'>
|
||||||
hilbish.runner.sh(cmd)
|
hilbish.runner.add(name, runner)
|
||||||
<a href="#runner.sh" class='heading-link'>
|
<a href="#add" class='heading-link'>
|
||||||
<i class="fas fa-paperclip"></i>
|
<i class="fas fa-paperclip"></i>
|
||||||
</a>
|
</a>
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
Runs a command in Hilbish's shell script interpreter.
|
Adds a runner to the table of available runners.
|
||||||
This is the equivalent of using `source`.
|
If runner is a table, it must have the run function in it.
|
||||||
|
|
||||||
#### Parameters
|
#### Parameters
|
||||||
`string` **`cmd`**
|
`name` **`string`**
|
||||||
|
Name of the runner
|
||||||
|
|
||||||
|
`runner` **`function|table`**
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='exec'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.runner.exec(cmd, runnerName)
|
||||||
|
<a href="#exec" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Executes `cmd` with a runner.
|
||||||
|
If `runnerName` is not specified, it uses the default Hilbish runner.
|
||||||
|
#### Parameters
|
||||||
|
`cmd` **`string`**
|
||||||
|
|
||||||
|
|
||||||
|
`runnerName` **`string?`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='get'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.runner.get(name)
|
||||||
|
<a href="#get" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Get a runner by name.
|
||||||
|
#### Parameters
|
||||||
|
`name` **`string`**
|
||||||
|
Name of the runner to retrieve.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='getCurrent'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.runner.getCurrent()
|
||||||
|
<a href="#getCurrent" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Returns the current runner by name.
|
||||||
|
#### Parameters
|
||||||
|
This function has no parameters.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='run'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.runner.run(input, priv)
|
||||||
|
<a href="#run" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Runs `input` with the currently set Hilbish runner.
|
||||||
|
This method is how Hilbish executes commands.
|
||||||
|
`priv` is an optional boolean used to state if the input should be saved to history.
|
||||||
|
#### Parameters
|
||||||
|
`input` **`string`**
|
||||||
|
|
||||||
|
|
||||||
|
`priv` **`bool`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='set'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.runner.set(name, runner)
|
||||||
|
<a href="#set" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
*Sets* a runner by name. The difference between this function and
|
||||||
|
add, is set will *not* check if the named runner exists.
|
||||||
|
The runner table must have the run function in it.
|
||||||
|
#### Parameters
|
||||||
|
`name` **`string`**
|
||||||
|
|
||||||
|
|
||||||
|
`runner` **`table`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='setCurrent'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.runner.setCurrent(name)
|
||||||
|
<a href="#setCurrent" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Sets Hilbish's runner mode by name.
|
||||||
|
#### Parameters
|
||||||
|
`name` **`string`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='setMode'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.runner.setMode(mode)
|
||||||
|
<a href="#setMode" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
**NOTE: This function is deprecated and will be removed in 3.0**
|
||||||
|
Use `hilbish.runner.setCurrent` instead.
|
||||||
|
This is the same as the `hilbish.runnerMode` function.
|
||||||
|
It takes a callback, which will be used to execute all interactive input.
|
||||||
|
Or a string which names the runner mode to use.
|
||||||
|
#### Parameters
|
||||||
|
`mode` **`string|function`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='sh'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.runner.sh()
|
||||||
|
<a href="#sh" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
This function has no parameters.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
50
docs/api/snail.md
Normal file
50
docs/api/snail.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
---
|
||||||
|
title: Module snail
|
||||||
|
description: shell script interpreter library
|
||||||
|
layout: doc
|
||||||
|
menu:
|
||||||
|
docs:
|
||||||
|
parent: "API"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
The snail library houses Hilbish's Lua wrapper of its shell script interpreter.
|
||||||
|
It's not very useful other than running shell scripts, which can be done with other
|
||||||
|
Hilbish functions.
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|||
|
||||||
|
|----|----|
|
||||||
|
|<a href="#new">new() -> @Snail</a>|Creates a new Snail instance.|
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='new'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
snail.new() -> <a href="/Hilbish/docs/api/snail/#snail" style="text-decoration: none;" id="lol">Snail</a>
|
||||||
|
<a href="#new" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Creates a new Snail instance.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
This function has no parameters.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Types
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
## Snail
|
||||||
|
A Snail is a shell script interpreter instance.
|
||||||
|
|
||||||
|
### Methods
|
||||||
|
#### dir(path)
|
||||||
|
Changes the directory of the snail instance.
|
||||||
|
The interpreter keeps its set directory even when the Hilbish process changes
|
||||||
|
directory, so this should be called on the `hilbish.cd` hook.
|
||||||
|
|
||||||
|
#### run(command, streams)
|
||||||
|
Runs a shell command. Works the same as `hilbish.run`, but only accepts a table of streams.
|
||||||
|
|
@ -53,8 +53,39 @@ which follows XDG on Linux and MacOS, and is located in %APPDATA% on Windows.
|
|||||||
As the directory is usually `~/.config` on Linux, you can run this command to copy it:
|
As the directory is usually `~/.config` on Linux, you can run this command to copy it:
|
||||||
`cp /usr/share/hilbish/.hilbishrc.lua ~/.config/hilbish/init.lua`
|
`cp /usr/share/hilbish/.hilbishrc.lua ~/.config/hilbish/init.lua`
|
||||||
|
|
||||||
Now you can get to editing it. Since it's just a Lua file, having basic
|
Now we can get to customization!
|
||||||
knowledge of Lua would help. All of Lua's standard libraries and functions
|
|
||||||
from Lua 5.4 are available. Hilbish has some custom and modules that are
|
If we closely examine a small snippet of the default config:
|
||||||
available. To see them, you can run the `doc` command. This also works as
|
```lua
|
||||||
general documentation for other things.
|
-- Default Hilbish config
|
||||||
|
-- .. with some omitted code .. --
|
||||||
|
|
||||||
|
local function doPrompt(fail)
|
||||||
|
hilbish.prompt(lunacolors.format(
|
||||||
|
'{blue}%u {cyan}%d ' .. (fail and '{red}' or '{green}') .. '∆ '
|
||||||
|
))
|
||||||
|
end
|
||||||
|
|
||||||
|
doPrompt()
|
||||||
|
|
||||||
|
bait.catch('command.exit', function(code)
|
||||||
|
doPrompt(code ~= 0)
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
|
We see a whopping **three** Hilbish libraries being used in this part of code.
|
||||||
|
First is of course, named after the shell itself, [`hilbish`](../api/hilbish). This is kind of a
|
||||||
|
"catch-all" namespace for functions that directly related to shell functionality/settings.
|
||||||
|
|
||||||
|
And as we can see, the [hilbish.prompt](../api/hilbish/#prompt) function is used
|
||||||
|
to change our prompt. Change our prompt to what, exactly?
|
||||||
|
|
||||||
|
The doc for the function states that the verbs `%u` and `%d`are used for username and current directory
|
||||||
|
of the shell, respectively.
|
||||||
|
|
||||||
|
We wrap this in the [`lunacolors.format`](../lunacolors) function, to give
|
||||||
|
our prompt some nice color.
|
||||||
|
|
||||||
|
But you might have also noticed that this is in the `doPrompt` function, which is called once,
|
||||||
|
and then used again in a [bait](../api/bait) hook. Specifically, the `command.exit` hook,
|
||||||
|
which is called after a command exits, so when it finishes running.
|
||||||
|
@ -43,5 +43,29 @@ The notification. The properties are defined in the link above.
|
|||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
+ `hilbish.vimAction` -> actionName, args > Sent when the user does a "vim action," being something
|
## hilbish.cd
|
||||||
like yanking or pasting text. See `doc vim-mode actions` for more info.
|
Sent when the current directory of the shell is changed (via interactive means.)
|
||||||
|
If you are implementing a custom command that changes the directory of the shell,
|
||||||
|
you must throw this hook manually for correctness.
|
||||||
|
|
||||||
|
#### Variables
|
||||||
|
`string` **`path`**
|
||||||
|
Absolute path of the directory that was changed to.
|
||||||
|
|
||||||
|
`string` **`oldPath`**
|
||||||
|
Absolute path of the directory Hilbish *was* in.
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
## hilbish.vimAction
|
||||||
|
Sent when the user does a "vim action," being something like yanking or pasting text.
|
||||||
|
See `doc vim-mode actions` for more info.
|
||||||
|
|
||||||
|
#### Variables
|
||||||
|
`string` **`actionName`**
|
||||||
|
Absolute path of the directory that was changed to.
|
||||||
|
|
||||||
|
`table` **`args`**
|
||||||
|
Table of args relating to the Vim action.
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
@ -1,40 +1,25 @@
|
|||||||
---
|
---
|
||||||
title: Module dirs
|
title: Module dirs
|
||||||
description: No description.
|
description: internal directory management
|
||||||
layout: doc
|
layout: doc
|
||||||
menu:
|
menu:
|
||||||
docs:
|
docs:
|
||||||
parent: "Nature"
|
parent: "Nature"
|
||||||
---
|
---
|
||||||
|
|
||||||
<hr>
|
|
||||||
<div id='setOld'>
|
|
||||||
<h4 class='heading'>
|
|
||||||
dirs.setOld(d)
|
|
||||||
<a href="#setOld" class='heading-link'>
|
|
||||||
<i class="fas fa-paperclip"></i>
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
Sets the old directory string.
|
## Introduction
|
||||||
#### Parameters
|
The dirs module defines a small set of functions to store and manage
|
||||||
`d` **`string`**
|
directories.
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
<div id='push'>
|
|
||||||
<h4 class='heading'>
|
|
||||||
dirs.push()
|
|
||||||
<a href="#push" class='heading-link'>
|
|
||||||
<i class="fas fa-paperclip"></i>
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
Add `d` to the recent directories list.
|
|
||||||
#### Parameters
|
|
||||||
This function has no parameters.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|||
|
||||||
|
|----|----|
|
||||||
|
|<a href="#setOld">setOld(d)</a>|Sets the old directory string.|
|
||||||
|
|<a href="#recent">recent(idx)</a>|Get entry from recent directories list based on index.|
|
||||||
|
|<a href="#push">push(dir)</a>|Add `dir` to the recent directories list.|
|
||||||
|
|<a href="#pop">pop(num)</a>|Remove the specified amount of dirs from the recent directories list.|
|
||||||
|
|<a href="#peak">peak(num)</a>|Look at `num` amount of recent directories, starting from the latest.|
|
||||||
<hr>
|
<hr>
|
||||||
<div id='peak'>
|
<div id='peak'>
|
||||||
<h4 class='heading'>
|
<h4 class='heading'>
|
||||||
@ -45,8 +30,11 @@ dirs.peak(num)
|
|||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
Look at `num` amount of recent directories, starting from the latest.
|
Look at `num` amount of recent directories, starting from the latest.
|
||||||
|
This returns a table of recent directories, up to the `num` amount.
|
||||||
#### Parameters
|
#### Parameters
|
||||||
`num` **`number`**
|
`num` **`number`**
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
@ -61,6 +49,24 @@ dirs.pop(num)
|
|||||||
Remove the specified amount of dirs from the recent directories list.
|
Remove the specified amount of dirs from the recent directories list.
|
||||||
#### Parameters
|
#### Parameters
|
||||||
`num` **`number`**
|
`num` **`number`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='push'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
dirs.push(dir)
|
||||||
|
<a href="#push" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Add `dir` to the recent directories list.
|
||||||
|
#### Parameters
|
||||||
|
`dir` **`string`**
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
@ -75,5 +81,23 @@ dirs.recent(idx)
|
|||||||
Get entry from recent directories list based on index.
|
Get entry from recent directories list based on index.
|
||||||
#### Parameters
|
#### Parameters
|
||||||
`idx` **`number`**
|
`idx` **`number`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='setOld'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
dirs.setOld(d)
|
||||||
|
<a href="#setOld" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Sets the old directory string.
|
||||||
|
#### Parameters
|
||||||
|
`d` **`string`**
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
76
docs/nature/doc.md
Normal file
76
docs/nature/doc.md
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
---
|
||||||
|
title: Module doc
|
||||||
|
description: command-line doc rendering
|
||||||
|
layout: doc
|
||||||
|
menu:
|
||||||
|
docs:
|
||||||
|
parent: "Nature"
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
The doc module contains a small set of functions
|
||||||
|
used by the Greenhouse pager to render parts of the documentation pages.
|
||||||
|
This is only documented for the sake of it. It's only intended use
|
||||||
|
is by the Greenhouse pager.
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|||
|
||||||
|
|----|----|
|
||||||
|
|<a href="#renderInfoBlock">renderInfoBlock(type, text)</a>|Renders an info block. An info block is a block of text with|
|
||||||
|
|<a href="#renderCodeBlock">renderCodeBlock(text)</a>|Assembles and renders a code block. This returns|
|
||||||
|
|<a href="#highlight">highlight(text)</a>|Performs basic Lua code highlighting.|
|
||||||
|
<hr>
|
||||||
|
<div id='highlight'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
doc.highlight(text)
|
||||||
|
<a href="#highlight" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Performs basic Lua code highlighting.
|
||||||
|
#### Parameters
|
||||||
|
`text` **`string`**
|
||||||
|
Code/text to do highlighting on.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='renderCodeBlock'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
doc.renderCodeBlock(text)
|
||||||
|
<a href="#renderCodeBlock" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Assembles and renders a code block. This returns
|
||||||
|
the supplied text based on the number of command line columns,
|
||||||
|
and styles it to resemble a code block.
|
||||||
|
#### Parameters
|
||||||
|
`text` **`string`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='renderInfoBlock'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
doc.renderInfoBlock(type, text)
|
||||||
|
<a href="#renderInfoBlock" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Renders an info block. An info block is a block of text with
|
||||||
|
an icon and styled text block.
|
||||||
|
#### Parameters
|
||||||
|
`type` **`string`**
|
||||||
|
Type of info block. The only one specially styled is the `warning`.
|
||||||
|
|
||||||
|
`text` **`string`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
22
editor.go
22
editor.go
@ -17,6 +17,7 @@ func editorLoader(rtm *rt.Runtime) *rt.Table {
|
|||||||
"getVimRegister": {editorGetRegister, 2, false},
|
"getVimRegister": {editorGetRegister, 2, false},
|
||||||
"getLine": {editorGetLine, 0, false},
|
"getLine": {editorGetLine, 0, false},
|
||||||
"readChar": {editorReadChar, 0, false},
|
"readChar": {editorReadChar, 0, false},
|
||||||
|
"deleteByAmount": {editorDeleteByAmount, 1, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
mod := rt.NewTable()
|
mod := rt.NewTable()
|
||||||
@ -47,7 +48,7 @@ func editorInsert(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
// #interface editor
|
// #interface editor
|
||||||
// setVimRegister(register, text)
|
// setVimRegister(register, text)
|
||||||
// Sets the vim register at `register` to hold the passed text.
|
// Sets the vim register at `register` to hold the passed text.
|
||||||
// #aram register string
|
// #param register string
|
||||||
// #param text string
|
// #param text string
|
||||||
func editorSetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func editorSetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
if err := c.Check1Arg(); err != nil {
|
if err := c.Check1Arg(); err != nil {
|
||||||
@ -106,3 +107,22 @@ func editorReadChar(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
|
|
||||||
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
|
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #interface editor
|
||||||
|
// deleteByAmount(amount)
|
||||||
|
// Deletes characters in the line by the given amount.
|
||||||
|
// #param amount number
|
||||||
|
func editorDeleteByAmount(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
if err := c.Check1Arg(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
amount, err := c.IntArg(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lr.rl.DeleteByAmount(int(amount))
|
||||||
|
|
||||||
|
return c.Next(), nil
|
||||||
|
}
|
||||||
|
@ -7,11 +7,8 @@ local hilbish = {}
|
|||||||
--- @param cmd string
|
--- @param cmd string
|
||||||
function hilbish.aliases.add(alias, cmd) end
|
function hilbish.aliases.add(alias, cmd) end
|
||||||
|
|
||||||
--- This is the same as the `hilbish.runnerMode` function.
|
--- Deletes characters in the line by the given amount.
|
||||||
--- It takes a callback, which will be used to execute all interactive input.
|
function hilbish.editor.deleteByAmount(amount) end
|
||||||
--- In normal cases, neither callbacks should be overrided by the user,
|
|
||||||
--- as the higher level functions listed below this will handle it.
|
|
||||||
function hilbish.runner.setMode(cb) end
|
|
||||||
|
|
||||||
--- Returns the current input line.
|
--- Returns the current input line.
|
||||||
function hilbish.editor.getLine() end
|
function hilbish.editor.getLine() end
|
||||||
@ -131,24 +128,6 @@ function hilbish.prompt(str, typ) end
|
|||||||
--- Returns `input`, will be nil if Ctrl-D is pressed, or an error occurs.
|
--- Returns `input`, will be nil if Ctrl-D is pressed, or an error occurs.
|
||||||
function hilbish.read(prompt) end
|
function hilbish.read(prompt) end
|
||||||
|
|
||||||
--- Runs `cmd` in Hilbish's shell script interpreter.
|
|
||||||
--- The `streams` parameter specifies the output and input streams the command should use.
|
|
||||||
--- For example, to write command output to a sink.
|
|
||||||
--- As a table, the caller can directly specify the standard output, error, and input
|
|
||||||
--- streams of the command with the table keys `out`, `err`, and `input` respectively.
|
|
||||||
--- As a boolean, it specifies whether the command should use standard output or return its output streams.
|
|
||||||
---
|
|
||||||
function hilbish.run(cmd, streams) end
|
|
||||||
|
|
||||||
--- Sets the execution/runner mode for interactive Hilbish.
|
|
||||||
--- This determines whether Hilbish wll try to run input as Lua
|
|
||||||
--- and/or sh or only do one of either.
|
|
||||||
--- Accepted values for mode are hybrid (the default), hybridRev (sh first then Lua),
|
|
||||||
--- sh, and lua. It also accepts a function, to which if it is passed one
|
|
||||||
--- will call it to execute user input instead.
|
|
||||||
--- Read [about runner mode](../features/runner-mode) for more information.
|
|
||||||
function hilbish.runnerMode(mode) end
|
|
||||||
|
|
||||||
--- Executed the `cb` function after a period of `time`.
|
--- Executed the `cb` function after a period of `time`.
|
||||||
--- This creates a Timer that starts ticking immediately.
|
--- This creates a Timer that starts ticking immediately.
|
||||||
function hilbish.timeout(cb, time) end
|
function hilbish.timeout(cb, time) end
|
||||||
@ -168,28 +147,6 @@ function hilbish.jobs:foreground() end
|
|||||||
--- or `load`, but is appropriated for the runner interface.
|
--- or `load`, but is appropriated for the runner interface.
|
||||||
function hilbish.runner.lua(cmd) end
|
function hilbish.runner.lua(cmd) end
|
||||||
|
|
||||||
--- Sets/toggles the option of automatically flushing output.
|
|
||||||
--- A call with no argument will toggle the value.
|
|
||||||
--- @param auto boolean|nil
|
|
||||||
function hilbish:autoFlush(auto) end
|
|
||||||
|
|
||||||
--- Flush writes all buffered input to the sink.
|
|
||||||
function hilbish:flush() end
|
|
||||||
|
|
||||||
--- Reads a liine of input from the sink.
|
|
||||||
--- @returns string
|
|
||||||
function hilbish:read() end
|
|
||||||
|
|
||||||
--- Reads all input from the sink.
|
|
||||||
--- @returns string
|
|
||||||
function hilbish:readAll() end
|
|
||||||
|
|
||||||
--- Writes data to a sink.
|
|
||||||
function hilbish:write(str) end
|
|
||||||
|
|
||||||
--- Writes data to a sink with a newline at the end.
|
|
||||||
function hilbish:writeln(str) end
|
|
||||||
|
|
||||||
--- Starts running the job.
|
--- Starts running the job.
|
||||||
function hilbish.jobs:start() end
|
function hilbish.jobs:start() end
|
||||||
|
|
||||||
@ -200,10 +157,6 @@ function hilbish.jobs:stop() end
|
|||||||
--- It will throw if any error occurs.
|
--- It will throw if any error occurs.
|
||||||
function hilbish.module.load(path) end
|
function hilbish.module.load(path) end
|
||||||
|
|
||||||
--- Runs a command in Hilbish's shell script interpreter.
|
|
||||||
--- This is the equivalent of using `source`.
|
|
||||||
function hilbish.runner.sh(cmd) end
|
|
||||||
|
|
||||||
--- Starts a timer.
|
--- Starts a timer.
|
||||||
function hilbish.timers:start() end
|
function hilbish.timers:start() end
|
||||||
|
|
||||||
@ -262,4 +215,26 @@ function hilbish.timers.create(type, time, callback) end
|
|||||||
--- Retrieves a timer via its ID.
|
--- Retrieves a timer via its ID.
|
||||||
function hilbish.timers.get(id) end
|
function hilbish.timers.get(id) end
|
||||||
|
|
||||||
|
--- Sets/toggles the option of automatically flushing output.
|
||||||
|
--- A call with no argument will toggle the value.
|
||||||
|
--- @param auto boolean|nil
|
||||||
|
function hilbish:autoFlush(auto) end
|
||||||
|
|
||||||
|
--- Flush writes all buffered input to the sink.
|
||||||
|
function hilbish:flush() end
|
||||||
|
|
||||||
|
--- Reads a liine of input from the sink.
|
||||||
|
--- @returns string
|
||||||
|
function hilbish:read() end
|
||||||
|
|
||||||
|
--- Reads all input from the sink.
|
||||||
|
--- @returns string
|
||||||
|
function hilbish:readAll() end
|
||||||
|
|
||||||
|
--- Writes data to a sink.
|
||||||
|
function hilbish:write(str) end
|
||||||
|
|
||||||
|
--- Writes data to a sink with a newline at the end.
|
||||||
|
function hilbish:writeln(str) end
|
||||||
|
|
||||||
return hilbish
|
return hilbish
|
||||||
|
16
emmyLuaDocs/snail.lua
Normal file
16
emmyLuaDocs/snail.lua
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
--- @meta
|
||||||
|
|
||||||
|
local snail = {}
|
||||||
|
|
||||||
|
--- Changes the directory of the snail instance.
|
||||||
|
--- The interpreter keeps its set directory even when the Hilbish process changes
|
||||||
|
--- directory, so this should be called on the `hilbish.cd` hook.
|
||||||
|
function snail:dir(path) end
|
||||||
|
|
||||||
|
--- Creates a new Snail instance.
|
||||||
|
function snail.new() end
|
||||||
|
|
||||||
|
--- Runs a shell command. Works the same as `hilbish.run`, but only accepts a table of streams.
|
||||||
|
function snail:run(command, streams) end
|
||||||
|
|
||||||
|
return snail
|
83
emmyLuaDocs/util.lua
Normal file
83
emmyLuaDocs/util.lua
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
--- @meta
|
||||||
|
|
||||||
|
local util = {}
|
||||||
|
|
||||||
|
---
|
||||||
|
function util.AbbrevHome changes the user's home directory in the path string to ~ (tilde) end
|
||||||
|
|
||||||
|
---
|
||||||
|
function util. end
|
||||||
|
|
||||||
|
---
|
||||||
|
function util.DoFile runs the contents of the file in the Lua runtime. end
|
||||||
|
|
||||||
|
---
|
||||||
|
function util.DoString runs the code string in the Lua runtime. end
|
||||||
|
|
||||||
|
--- directory.
|
||||||
|
function util.ExpandHome expands ~ (tilde) in the path, changing it to the user home end
|
||||||
|
|
||||||
|
---
|
||||||
|
function util. end
|
||||||
|
|
||||||
|
---
|
||||||
|
function util.ForEach loops through a Lua table. end
|
||||||
|
|
||||||
|
---
|
||||||
|
function util. end
|
||||||
|
|
||||||
|
--- a string and a closure.
|
||||||
|
function util.HandleStrCallback handles function parameters for Go functions which take end
|
||||||
|
|
||||||
|
---
|
||||||
|
function util. end
|
||||||
|
|
||||||
|
---
|
||||||
|
function util. end
|
||||||
|
|
||||||
|
---
|
||||||
|
function util.SetExports puts the Lua function exports in the table. end
|
||||||
|
|
||||||
|
--- It is accessible via the __docProp metatable. It is a table of the names of the fields.
|
||||||
|
function util.SetField sets a field in a table, adding docs for it. end
|
||||||
|
|
||||||
|
--- is one which has a metatable proxy to ensure no overrides happen to it.
|
||||||
|
--- It sets the field in the table and sets the __docProp metatable on the
|
||||||
|
--- user facing table.
|
||||||
|
function util.SetFieldProtected sets a field in a protected table. A protected table end
|
||||||
|
|
||||||
|
--- Sets/toggles the option of automatically flushing output.
|
||||||
|
--- A call with no argument will toggle the value.
|
||||||
|
--- @param auto boolean|nil
|
||||||
|
function util:autoFlush(auto) end
|
||||||
|
|
||||||
|
--- Flush writes all buffered input to the sink.
|
||||||
|
function util:flush() end
|
||||||
|
|
||||||
|
---
|
||||||
|
function util. end
|
||||||
|
|
||||||
|
--- Reads a liine of input from the sink.
|
||||||
|
--- @returns string
|
||||||
|
function util:read() end
|
||||||
|
|
||||||
|
--- Reads all input from the sink.
|
||||||
|
--- @returns string
|
||||||
|
function util:readAll() end
|
||||||
|
|
||||||
|
--- Writes data to a sink.
|
||||||
|
function util:write(str) end
|
||||||
|
|
||||||
|
--- Writes data to a sink with a newline at the end.
|
||||||
|
function util:writeln(str) end
|
||||||
|
|
||||||
|
---
|
||||||
|
function util. end
|
||||||
|
|
||||||
|
---
|
||||||
|
function util. end
|
||||||
|
|
||||||
|
---
|
||||||
|
function util. end
|
||||||
|
|
||||||
|
return util
|
532
exec.go
532
exec.go
@ -1,215 +1,26 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"os/exec"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"hilbish/util"
|
|
||||||
|
|
||||||
rt "github.com/arnodel/golua/runtime"
|
rt "github.com/arnodel/golua/runtime"
|
||||||
"mvdan.cc/sh/v3/shell"
|
|
||||||
//"github.com/yuin/gopher-lua/parse"
|
//"github.com/yuin/gopher-lua/parse"
|
||||||
"mvdan.cc/sh/v3/interp"
|
|
||||||
"mvdan.cc/sh/v3/syntax"
|
|
||||||
"mvdan.cc/sh/v3/expand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var errNotExec = errors.New("not executable")
|
var errNotExec = errors.New("not executable")
|
||||||
var errNotFound = errors.New("not found")
|
var errNotFound = errors.New("not found")
|
||||||
var runnerMode rt.Value = rt.StringValue("hybrid")
|
var runnerMode rt.Value = rt.NilValue
|
||||||
|
|
||||||
type streams struct {
|
|
||||||
stdout io.Writer
|
|
||||||
stderr io.Writer
|
|
||||||
stdin io.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
type execError struct{
|
|
||||||
typ string
|
|
||||||
cmd string
|
|
||||||
code int
|
|
||||||
colon bool
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e execError) Error() string {
|
|
||||||
return fmt.Sprintf("%s: %s", e.cmd, e.typ)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e execError) sprint() error {
|
|
||||||
sep := " "
|
|
||||||
if e.colon {
|
|
||||||
sep = ": "
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("hilbish: %s%s%s", e.cmd, sep, e.err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
func isExecError(err error) (execError, bool) {
|
|
||||||
if exErr, ok := err.(execError); ok {
|
|
||||||
return exErr, true
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := strings.Split(err.Error(), ": ")
|
|
||||||
knownTypes := []string{
|
|
||||||
"not-found",
|
|
||||||
"not-executable",
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(fields) > 1 && contains(knownTypes, fields[1]) {
|
|
||||||
var colon bool
|
|
||||||
var e error
|
|
||||||
switch fields[1] {
|
|
||||||
case "not-found":
|
|
||||||
e = errNotFound
|
|
||||||
case "not-executable":
|
|
||||||
colon = true
|
|
||||||
e = errNotExec
|
|
||||||
}
|
|
||||||
|
|
||||||
return execError{
|
|
||||||
cmd: fields[0],
|
|
||||||
typ: fields[1],
|
|
||||||
colon: colon,
|
|
||||||
err: e,
|
|
||||||
}, true
|
|
||||||
}
|
|
||||||
|
|
||||||
return execError{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func runInput(input string, priv bool) {
|
func runInput(input string, priv bool) {
|
||||||
running = true
|
running = true
|
||||||
cmdString := aliases.Resolve(input)
|
runnerRun := hshMod.Get(rt.StringValue("runner")).AsTable().Get(rt.StringValue("run"))
|
||||||
hooks.Emit("command.preexec", input, cmdString)
|
_, err := rt.Call1(l.MainThread(), runnerRun, rt.StringValue(input), rt.BoolValue(priv))
|
||||||
|
|
||||||
rerun:
|
|
||||||
var exitCode uint8
|
|
||||||
var err error
|
|
||||||
var cont bool
|
|
||||||
var newline bool
|
|
||||||
// save incase it changes while prompting (For some reason)
|
|
||||||
currentRunner := runnerMode
|
|
||||||
if currentRunner.Type() == rt.StringType {
|
|
||||||
switch currentRunner.AsString() {
|
|
||||||
case "hybrid":
|
|
||||||
_, _, err = handleLua(input)
|
|
||||||
if err == nil {
|
|
||||||
cmdFinish(0, input, priv)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
input, exitCode, cont, newline, err = handleSh(input)
|
|
||||||
case "hybridRev":
|
|
||||||
_, _, _, _, err = handleSh(input)
|
|
||||||
if err == nil {
|
|
||||||
cmdFinish(0, input, priv)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
input, exitCode, err = handleLua(input)
|
|
||||||
case "lua":
|
|
||||||
input, exitCode, err = handleLua(input)
|
|
||||||
case "sh":
|
|
||||||
input, exitCode, cont, newline, err = handleSh(input)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// can only be a string or function so
|
|
||||||
var runnerErr error
|
|
||||||
input, exitCode, cont, newline, runnerErr, err = runLuaRunner(currentRunner, input)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
cmdFinish(124, input, priv)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
// yep, we only use `err` to check for lua eval error
|
|
||||||
// our actual error should only be a runner provided error at this point
|
|
||||||
// command not found type, etc
|
|
||||||
err = runnerErr
|
|
||||||
}
|
|
||||||
|
|
||||||
if cont {
|
|
||||||
input, err = continuePrompt(input, newline)
|
|
||||||
if err == nil {
|
|
||||||
goto rerun
|
|
||||||
} else if err == io.EOF {
|
|
||||||
lr.SetPrompt(fmtPrompt(prompt))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
if exErr, ok := isExecError(err); ok {
|
|
||||||
hooks.Emit("command." + exErr.typ, exErr.cmd)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cmdFinish(exitCode, input, priv)
|
|
||||||
}
|
|
||||||
|
|
||||||
func reprompt(input string, newline bool) (string, error) {
|
|
||||||
for {
|
|
||||||
/*
|
|
||||||
if strings.HasSuffix(input, "\\") {
|
|
||||||
input = strings.TrimSuffix(input, "\\") + "\n"
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
in, err := continuePrompt(input, newline)
|
|
||||||
if err != nil {
|
|
||||||
lr.SetPrompt(fmtPrompt(prompt))
|
|
||||||
return input, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return in, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runLuaRunner(runr rt.Value, userInput string) (input string, exitCode uint8, continued bool, newline bool, runnerErr, err error) {
|
|
||||||
term := rt.NewTerminationWith(l.MainThread().CurrentCont(), 3, false)
|
|
||||||
err = rt.Call(l.MainThread(), runr, []rt.Value{rt.StringValue(userInput)}, term)
|
|
||||||
if err != nil {
|
|
||||||
return "", 124, false, false, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var runner *rt.Table
|
|
||||||
var ok bool
|
|
||||||
runnerRet := term.Get(0)
|
|
||||||
if runner, ok = runnerRet.TryTable(); !ok {
|
|
||||||
fmt.Fprintln(os.Stderr, "runner did not return a table")
|
|
||||||
exitCode = 125
|
|
||||||
input = userInput
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if code, ok := runner.Get(rt.StringValue("exitCode")).TryInt(); ok {
|
|
||||||
exitCode = uint8(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
if inp, ok := runner.Get(rt.StringValue("input")).TryString(); ok {
|
|
||||||
input = inp
|
|
||||||
}
|
|
||||||
|
|
||||||
if errStr, ok := runner.Get(rt.StringValue("err")).TryString(); ok {
|
|
||||||
runnerErr = fmt.Errorf("%s", errStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c, ok := runner.Get(rt.StringValue("continue")).TryBool(); ok {
|
|
||||||
continued = c
|
|
||||||
}
|
|
||||||
|
|
||||||
if nl, ok := runner.Get(rt.StringValue("newline")).TryBool(); ok {
|
|
||||||
newline = nl
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleLua(input string) (string, uint8, error) {
|
func handleLua(input string) (string, uint8, error) {
|
||||||
@ -239,326 +50,13 @@ func handleLua(input string) (string, uint8, error) {
|
|||||||
return cmdString, 125, err
|
return cmdString, 125, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSh(cmdString string) (input string, exitCode uint8, cont bool, newline bool, runErr error) {
|
|
||||||
shRunner := hshMod.Get(rt.StringValue("runner")).AsTable().Get(rt.StringValue("sh"))
|
|
||||||
var err error
|
|
||||||
input, exitCode, cont, newline, runErr, err = runLuaRunner(shRunner, cmdString)
|
|
||||||
if err != nil {
|
|
||||||
runErr = err
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func execSh(cmdString string) (input string, exitcode uint8, cont bool, newline bool, e error) {
|
|
||||||
_, _, err := execCommand(cmdString, nil)
|
|
||||||
if err != nil {
|
|
||||||
// If input is incomplete, start multiline prompting
|
|
||||||
if syntax.IsIncomplete(err) {
|
|
||||||
if !interactive {
|
|
||||||
return cmdString, 126, false, false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newline := false
|
|
||||||
if strings.Contains(err.Error(), "unclosed here-document") {
|
|
||||||
newline = true
|
|
||||||
}
|
|
||||||
return cmdString, 126, true, newline, err
|
|
||||||
} else {
|
|
||||||
if code, ok := interp.IsExitStatus(err); ok {
|
|
||||||
return cmdString, code, false, false, nil
|
|
||||||
} else {
|
|
||||||
return cmdString, 126, false, false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmdString, 0, false, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run command in sh interpreter
|
|
||||||
func execCommand(cmd string, strms *streams) (io.Writer, io.Writer, error) {
|
|
||||||
file, err := syntax.NewParser().Parse(strings.NewReader(cmd), "")
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if strms == nil {
|
|
||||||
strms = &streams{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if strms.stdout == nil {
|
|
||||||
strms.stdout = os.Stdout
|
|
||||||
}
|
|
||||||
|
|
||||||
if strms.stderr == nil {
|
|
||||||
strms.stderr = os.Stderr
|
|
||||||
}
|
|
||||||
|
|
||||||
if strms.stdin == nil {
|
|
||||||
strms.stdin = os.Stdin
|
|
||||||
}
|
|
||||||
|
|
||||||
interp.StdIO(strms.stdin, strms.stdout, strms.stderr)(runner)
|
|
||||||
interp.Env(nil)(runner)
|
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
printer := syntax.NewPrinter()
|
|
||||||
|
|
||||||
var bg bool
|
|
||||||
for _, stmt := range file.Stmts {
|
|
||||||
bg = false
|
|
||||||
if stmt.Background {
|
|
||||||
bg = true
|
|
||||||
printer.Print(buf, stmt.Cmd)
|
|
||||||
|
|
||||||
stmtStr := buf.String()
|
|
||||||
buf.Reset()
|
|
||||||
jobs.add(stmtStr, []string{}, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
interp.ExecHandler(execHandle(bg))(runner)
|
|
||||||
err = runner.Run(context.TODO(), stmt)
|
|
||||||
if err != nil {
|
|
||||||
return strms.stdout, strms.stderr, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return strms.stdout, strms.stderr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func execHandle(bg bool) interp.ExecHandlerFunc {
|
|
||||||
return func(ctx context.Context, args []string) error {
|
|
||||||
_, argstring := splitInput(strings.Join(args, " "))
|
|
||||||
// i dont really like this but it works
|
|
||||||
if aliases.All()[args[0]] != "" {
|
|
||||||
for i, arg := range args {
|
|
||||||
if strings.Contains(arg, " ") {
|
|
||||||
args[i] = fmt.Sprintf("\"%s\"", arg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, argstring = splitInput(strings.Join(args, " "))
|
|
||||||
|
|
||||||
// If alias was found, use command alias
|
|
||||||
argstring = aliases.Resolve(argstring)
|
|
||||||
var err error
|
|
||||||
args, err = shell.Fields(argstring, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If command is defined in Lua then run it
|
|
||||||
luacmdArgs := rt.NewTable()
|
|
||||||
for i, str := range args[1:] {
|
|
||||||
luacmdArgs.Set(rt.IntValue(int64(i + 1)), rt.StringValue(str))
|
|
||||||
}
|
|
||||||
|
|
||||||
hc := interp.HandlerCtx(ctx)
|
|
||||||
if cmd := cmds.Commands[args[0]]; cmd != nil {
|
|
||||||
stdin := newSinkInput(hc.Stdin)
|
|
||||||
stdout := newSinkOutput(hc.Stdout)
|
|
||||||
stderr := newSinkOutput(hc.Stderr)
|
|
||||||
|
|
||||||
sinks := rt.NewTable()
|
|
||||||
sinks.Set(rt.StringValue("in"), rt.UserDataValue(stdin.ud))
|
|
||||||
sinks.Set(rt.StringValue("input"), rt.UserDataValue(stdin.ud))
|
|
||||||
sinks.Set(rt.StringValue("out"), rt.UserDataValue(stdout.ud))
|
|
||||||
sinks.Set(rt.StringValue("err"), rt.UserDataValue(stderr.ud))
|
|
||||||
|
|
||||||
t := rt.NewThread(l)
|
|
||||||
sig := make(chan os.Signal)
|
|
||||||
exit := make(chan bool)
|
|
||||||
|
|
||||||
luaexitcode := rt.IntValue(63)
|
|
||||||
var err error
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
exit <- true
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
signal.Notify(sig, os.Interrupt)
|
|
||||||
select {
|
|
||||||
case <-sig:
|
|
||||||
t.KillContext()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
luaexitcode, err = rt.Call1(t, rt.FunctionValue(cmd), rt.TableValue(luacmdArgs), rt.TableValue(sinks))
|
|
||||||
exit <- true
|
|
||||||
}()
|
|
||||||
|
|
||||||
<-exit
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, "Error in command:\n" + err.Error())
|
|
||||||
return interp.NewExitStatus(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var exitcode uint8
|
|
||||||
|
|
||||||
if code, ok := luaexitcode.TryInt(); ok {
|
|
||||||
exitcode = uint8(code)
|
|
||||||
} else if luaexitcode != rt.NilValue {
|
|
||||||
// deregister commander
|
|
||||||
delete(cmds.Commands, args[0])
|
|
||||||
fmt.Fprintf(os.Stderr, "Commander did not return number for exit code. %s, you're fired.\n", args[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
return interp.NewExitStatus(exitcode)
|
|
||||||
}
|
|
||||||
|
|
||||||
path, err := lookpath(args[0])
|
|
||||||
if err == errNotExec {
|
|
||||||
return execError{
|
|
||||||
typ: "not-executable",
|
|
||||||
cmd: args[0],
|
|
||||||
code: 126,
|
|
||||||
colon: true,
|
|
||||||
err: errNotExec,
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
return execError{
|
|
||||||
typ: "not-found",
|
|
||||||
cmd: args[0],
|
|
||||||
code: 127,
|
|
||||||
err: errNotFound,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
killTimeout := 2 * time.Second
|
|
||||||
// from here is basically copy-paste of the default exec handler from
|
|
||||||
// sh/interp but with our job handling
|
|
||||||
|
|
||||||
env := hc.Env
|
|
||||||
envList := os.Environ()
|
|
||||||
env.Each(func(name string, vr expand.Variable) bool {
|
|
||||||
if vr.Exported && vr.Kind == expand.String {
|
|
||||||
envList = append(envList, name+"="+vr.String())
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
cmd := exec.Cmd{
|
|
||||||
Path: path,
|
|
||||||
Args: args,
|
|
||||||
Env: envList,
|
|
||||||
Dir: hc.Dir,
|
|
||||||
Stdin: hc.Stdin,
|
|
||||||
Stdout: hc.Stdout,
|
|
||||||
Stderr: hc.Stderr,
|
|
||||||
}
|
|
||||||
|
|
||||||
var j *job
|
|
||||||
if bg {
|
|
||||||
j = jobs.getLatest()
|
|
||||||
j.setHandle(&cmd)
|
|
||||||
err = j.start()
|
|
||||||
} else {
|
|
||||||
err = cmd.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
if done := ctx.Done(); done != nil {
|
|
||||||
go func() {
|
|
||||||
<-done
|
|
||||||
|
|
||||||
if killTimeout <= 0 || runtime.GOOS == "windows" {
|
|
||||||
cmd.Process.Signal(os.Kill)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: don't temporarily leak this goroutine
|
|
||||||
// if the program stops itself with the
|
|
||||||
// interrupt.
|
|
||||||
go func() {
|
|
||||||
time.Sleep(killTimeout)
|
|
||||||
cmd.Process.Signal(os.Kill)
|
|
||||||
}()
|
|
||||||
cmd.Process.Signal(os.Interrupt)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cmd.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
exit := handleExecErr(err)
|
|
||||||
|
|
||||||
if bg {
|
|
||||||
j.exitCode = int(exit)
|
|
||||||
j.finish()
|
|
||||||
}
|
|
||||||
return interp.NewExitStatus(exit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleExecErr(err error) (exit uint8) {
|
|
||||||
ctx := context.TODO()
|
|
||||||
|
|
||||||
switch x := err.(type) {
|
|
||||||
case *exec.ExitError:
|
|
||||||
// started, but errored - default to 1 if OS
|
|
||||||
// doesn't have exit statuses
|
|
||||||
if status, ok := x.Sys().(syscall.WaitStatus); ok {
|
|
||||||
if status.Signaled() {
|
|
||||||
if ctx.Err() != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
exit = uint8(128 + status.Signal())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
exit = uint8(status.ExitStatus())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
exit = 1
|
|
||||||
return
|
|
||||||
case *exec.Error:
|
|
||||||
// did not start
|
|
||||||
//fmt.Fprintf(hc.Stderr, "%v\n", err)
|
|
||||||
exit = 127
|
|
||||||
default: return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
func lookpath(file string) (string, error) { // custom lookpath function so we know if a command is found *and* is executable
|
|
||||||
var skip []string
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
skip = []string{"./", "../", "~/", "C:"}
|
|
||||||
} else {
|
|
||||||
skip = []string{"./", "/", "../", "~/"}
|
|
||||||
}
|
|
||||||
for _, s := range skip {
|
|
||||||
if strings.HasPrefix(file, s) {
|
|
||||||
return file, findExecutable(file, false, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, dir := range filepath.SplitList(os.Getenv("PATH")) {
|
|
||||||
path := filepath.Join(dir, file)
|
|
||||||
err := findExecutable(path, true, false)
|
|
||||||
if err == errNotExec {
|
|
||||||
return "", err
|
|
||||||
} else if err == nil {
|
|
||||||
return path, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", os.ErrNotExist
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitInput(input string) ([]string, string) {
|
func splitInput(input string) ([]string, string) {
|
||||||
// end my suffering
|
// end my suffering
|
||||||
// TODO: refactor this garbage
|
// TODO: refactor this garbage
|
||||||
quoted := false
|
quoted := false
|
||||||
startlastcmd := false
|
|
||||||
lastcmddone := false
|
|
||||||
cmdArgs := []string{}
|
cmdArgs := []string{}
|
||||||
sb := &strings.Builder{}
|
sb := &strings.Builder{}
|
||||||
cmdstr := &strings.Builder{}
|
cmdstr := &strings.Builder{}
|
||||||
lastcmd := "" //readline.GetHistory(readline.HistorySize() - 1)
|
|
||||||
|
|
||||||
for _, r := range input {
|
for _, r := range input {
|
||||||
if r == '"' {
|
if r == '"' {
|
||||||
@ -574,22 +72,6 @@ func splitInput(input string) ([]string, string) {
|
|||||||
// if not quoted and there's a space then add to cmdargs
|
// if not quoted and there's a space then add to cmdargs
|
||||||
cmdArgs = append(cmdArgs, sb.String())
|
cmdArgs = append(cmdArgs, sb.String())
|
||||||
sb.Reset()
|
sb.Reset()
|
||||||
} else if !quoted && r == '^' && startlastcmd && !lastcmddone {
|
|
||||||
// if ^ is found, isnt in quotes and is
|
|
||||||
// the second occurence of the character and is
|
|
||||||
// the first time "^^" has been used
|
|
||||||
cmdstr.WriteString(lastcmd)
|
|
||||||
sb.WriteString(lastcmd)
|
|
||||||
|
|
||||||
startlastcmd = !startlastcmd
|
|
||||||
lastcmddone = !lastcmddone
|
|
||||||
|
|
||||||
continue
|
|
||||||
} else if !quoted && r == '^' && !lastcmddone {
|
|
||||||
// if ^ is found, isnt in quotes and is the
|
|
||||||
// first time of starting "^^"
|
|
||||||
startlastcmd = !startlastcmd
|
|
||||||
continue
|
|
||||||
} else {
|
} else {
|
||||||
sb.WriteRune(r)
|
sb.WriteRune(r)
|
||||||
}
|
}
|
||||||
@ -601,11 +83,3 @@ func splitInput(input string) ([]string, string) {
|
|||||||
|
|
||||||
return cmdArgs, cmdstr.String()
|
return cmdArgs, cmdstr.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdFinish(code uint8, cmdstr string, private bool) {
|
|
||||||
util.SetField(l, hshMod, "exitCode", rt.IntValue(int64(code)))
|
|
||||||
// using AsValue (to convert to lua type) on an interface which is an int
|
|
||||||
// results in it being unknown in lua .... ????
|
|
||||||
// so we allow the hook handler to take lua runtime Values
|
|
||||||
hooks.Emit("command.exit", rt.IntValue(int64(code)), cmdstr, private)
|
|
||||||
}
|
|
||||||
|
@ -19,38 +19,25 @@ import (
|
|||||||
rt "github.com/arnodel/golua/runtime"
|
rt "github.com/arnodel/golua/runtime"
|
||||||
"github.com/arnodel/golua/lib/packagelib"
|
"github.com/arnodel/golua/lib/packagelib"
|
||||||
"github.com/arnodel/golua/lib/iolib"
|
"github.com/arnodel/golua/lib/iolib"
|
||||||
"mvdan.cc/sh/v3/interp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type fs struct{
|
var Loader = packagelib.Loader{
|
||||||
runner *interp.Runner
|
Load: loaderFunc,
|
||||||
Loader packagelib.Loader
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(runner *interp.Runner) *fs {
|
|
||||||
f := &fs{
|
|
||||||
runner: runner,
|
|
||||||
}
|
|
||||||
f.Loader = packagelib.Loader{
|
|
||||||
Load: f.loaderFunc,
|
|
||||||
Name: "fs",
|
Name: "fs",
|
||||||
}
|
|
||||||
|
|
||||||
return f
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fs) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
|
func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
|
||||||
exports := map[string]util.LuaExport{
|
exports := map[string]util.LuaExport{
|
||||||
"cd": util.LuaExport{f.fcd, 1, false},
|
"cd": util.LuaExport{fcd, 1, false},
|
||||||
"mkdir": util.LuaExport{f.fmkdir, 2, false},
|
"mkdir": util.LuaExport{fmkdir, 2, false},
|
||||||
"stat": util.LuaExport{f.fstat, 1, false},
|
"stat": util.LuaExport{fstat, 1, false},
|
||||||
"readdir": util.LuaExport{f.freaddir, 1, false},
|
"readdir": util.LuaExport{freaddir, 1, false},
|
||||||
"abs": util.LuaExport{f.fabs, 1, false},
|
"abs": util.LuaExport{fabs, 1, false},
|
||||||
"basename": util.LuaExport{f.fbasename, 1, false},
|
"basename": util.LuaExport{fbasename, 1, false},
|
||||||
"dir": util.LuaExport{f.fdir, 1, false},
|
"dir": util.LuaExport{fdir, 1, false},
|
||||||
"glob": util.LuaExport{f.fglob, 1, false},
|
"glob": util.LuaExport{fglob, 1, false},
|
||||||
"join": util.LuaExport{f.fjoin, 0, true},
|
"join": util.LuaExport{fjoin, 0, true},
|
||||||
"pipe": util.LuaExport{f.fpipe, 0, false},
|
"pipe": util.LuaExport{fpipe, 0, false},
|
||||||
}
|
}
|
||||||
mod := rt.NewTable()
|
mod := rt.NewTable()
|
||||||
util.SetExports(rtm, mod, exports)
|
util.SetExports(rtm, mod, exports)
|
||||||
@ -65,7 +52,7 @@ func (f *fs) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
|
|||||||
// This can be used to resolve short paths like `..` to `/home/user`.
|
// This can be used to resolve short paths like `..` to `/home/user`.
|
||||||
// #param path string
|
// #param path string
|
||||||
// #returns string
|
// #returns string
|
||||||
func (f *fs) fabs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func fabs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
path, err := c.StringArg(0)
|
path, err := c.StringArg(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -85,7 +72,7 @@ func (f *fs) fabs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
// `.` will be returned.
|
// `.` will be returned.
|
||||||
// #param path string Path to get the base name of.
|
// #param path string Path to get the base name of.
|
||||||
// #returns string
|
// #returns string
|
||||||
func (f *fs) fbasename(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func fbasename(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
if err := c.Check1Arg(); err != nil {
|
if err := c.Check1Arg(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -100,7 +87,7 @@ func (f *fs) fbasename(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
// cd(dir)
|
// cd(dir)
|
||||||
// Changes Hilbish's directory to `dir`.
|
// Changes Hilbish's directory to `dir`.
|
||||||
// #param dir string Path to change directory to.
|
// #param dir string Path to change directory to.
|
||||||
func (f *fs) fcd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func fcd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
if err := c.Check1Arg(); err != nil {
|
if err := c.Check1Arg(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -110,12 +97,10 @@ func (f *fs) fcd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
}
|
}
|
||||||
path = util.ExpandHome(strings.TrimSpace(path))
|
path = util.ExpandHome(strings.TrimSpace(path))
|
||||||
|
|
||||||
abspath, _ := filepath.Abs(path)
|
|
||||||
err = os.Chdir(path)
|
err = os.Chdir(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
interp.Dir(abspath)(f.runner)
|
|
||||||
|
|
||||||
return c.Next(), err
|
return c.Next(), err
|
||||||
}
|
}
|
||||||
@ -125,7 +110,7 @@ func (f *fs) fcd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
// `~/Documents/doc.txt` then this function will return `~/Documents`.
|
// `~/Documents/doc.txt` then this function will return `~/Documents`.
|
||||||
// #param path string Path to get the directory for.
|
// #param path string Path to get the directory for.
|
||||||
// #returns string
|
// #returns string
|
||||||
func (f *fs) fdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func fdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
if err := c.Check1Arg(); err != nil {
|
if err := c.Check1Arg(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -156,7 +141,7 @@ print(matches)
|
|||||||
-- -> {'init.lua', 'code.lua'}
|
-- -> {'init.lua', 'code.lua'}
|
||||||
#example
|
#example
|
||||||
*/
|
*/
|
||||||
func (f *fs) fglob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func fglob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
if err := c.Check1Arg(); err != nil {
|
if err := c.Check1Arg(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -190,7 +175,7 @@ print(fs.join(hilbish.userDir.config, 'hilbish'))
|
|||||||
-- -> '/home/user/.config/hilbish' on Linux
|
-- -> '/home/user/.config/hilbish' on Linux
|
||||||
#example
|
#example
|
||||||
*/
|
*/
|
||||||
func (f *fs) fjoin(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func fjoin(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
strs := make([]string, len(c.Etc()))
|
strs := make([]string, len(c.Etc()))
|
||||||
for i, v := range c.Etc() {
|
for i, v := range c.Etc() {
|
||||||
if v.Type() != rt.StringType {
|
if v.Type() != rt.StringType {
|
||||||
@ -217,7 +202,7 @@ func (f *fs) fjoin(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
fs.mkdir('./foo/bar', true)
|
fs.mkdir('./foo/bar', true)
|
||||||
#example
|
#example
|
||||||
*/
|
*/
|
||||||
func (f *fs) fmkdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func fmkdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
if err := c.CheckNArgs(2); err != nil {
|
if err := c.CheckNArgs(2); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -248,7 +233,7 @@ func (f *fs) fmkdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
// The type returned is a Lua file, same as returned from `io` functions.
|
// The type returned is a Lua file, same as returned from `io` functions.
|
||||||
// #returns File
|
// #returns File
|
||||||
// #returns File
|
// #returns File
|
||||||
func (f *fs) fpipe(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func fpipe(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
rf, wf, err := os.Pipe()
|
rf, wf, err := os.Pipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -263,7 +248,7 @@ func (f *fs) fpipe(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
// Returns a list of all files and directories in the provided path.
|
// Returns a list of all files and directories in the provided path.
|
||||||
// #param dir string
|
// #param dir string
|
||||||
// #returns table
|
// #returns table
|
||||||
func (f *fs) freaddir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func freaddir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
if err := c.Check1Arg(); err != nil {
|
if err := c.Check1Arg(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -311,7 +296,7 @@ Would print the following:
|
|||||||
]]--
|
]]--
|
||||||
#example
|
#example
|
||||||
*/
|
*/
|
||||||
func (f *fs) fstat(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func fstat(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
if err := c.Check1Arg(); err != nil {
|
if err := c.Check1Arg(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
221
golibs/snail/lua.go
Normal file
221
golibs/snail/lua.go
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
// shell script interpreter library
|
||||||
|
/*
|
||||||
|
The snail library houses Hilbish's Lua wrapper of its shell script interpreter.
|
||||||
|
It's not very useful other than running shell scripts, which can be done with other
|
||||||
|
Hilbish functions.
|
||||||
|
*/
|
||||||
|
package snail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"hilbish/util"
|
||||||
|
|
||||||
|
rt "github.com/arnodel/golua/runtime"
|
||||||
|
"github.com/arnodel/golua/lib/packagelib"
|
||||||
|
"github.com/arnodel/golua/lib/iolib"
|
||||||
|
"mvdan.cc/sh/v3/interp"
|
||||||
|
"mvdan.cc/sh/v3/syntax"
|
||||||
|
)
|
||||||
|
|
||||||
|
var snailMetaKey = rt.StringValue("hshsnail")
|
||||||
|
var Loader = packagelib.Loader{
|
||||||
|
Load: loaderFunc,
|
||||||
|
Name: "snail",
|
||||||
|
}
|
||||||
|
|
||||||
|
func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
|
||||||
|
snailMeta := rt.NewTable()
|
||||||
|
snailMethods := rt.NewTable()
|
||||||
|
snailFuncs := map[string]util.LuaExport{
|
||||||
|
"run": {snailrun, 3, false},
|
||||||
|
"dir": {snaildir, 2, false},
|
||||||
|
}
|
||||||
|
util.SetExports(rtm, snailMethods, snailFuncs)
|
||||||
|
|
||||||
|
snailIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
arg := c.Arg(1)
|
||||||
|
val := snailMethods.Get(arg)
|
||||||
|
|
||||||
|
return c.PushingNext1(t.Runtime, val), nil
|
||||||
|
}
|
||||||
|
snailMeta.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(snailIndex, "__index", 2, false)))
|
||||||
|
rtm.SetRegistry(snailMetaKey, rt.TableValue(snailMeta))
|
||||||
|
|
||||||
|
exports := map[string]util.LuaExport{
|
||||||
|
"new": util.LuaExport{snailnew, 0, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
mod := rt.NewTable()
|
||||||
|
util.SetExports(rtm, mod, exports)
|
||||||
|
|
||||||
|
return rt.TableValue(mod), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// new() -> @Snail
|
||||||
|
// Creates a new Snail instance.
|
||||||
|
func snailnew(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
s := New(t.Runtime)
|
||||||
|
return c.PushingNext1(t.Runtime, rt.UserDataValue(snailUserData(s))), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// #member
|
||||||
|
// run(command, streams)
|
||||||
|
// Runs a shell command. Works the same as `hilbish.run`, but only accepts a table of streams.
|
||||||
|
// #param command string
|
||||||
|
// #param streams table
|
||||||
|
func snailrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
if err := c.CheckNArgs(2); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := snailArg(c, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, err := c.StringArg(1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
streams := &util.Streams{}
|
||||||
|
thirdArg := c.Arg(2)
|
||||||
|
switch thirdArg.Type() {
|
||||||
|
case rt.TableType:
|
||||||
|
args := thirdArg.AsTable()
|
||||||
|
|
||||||
|
if luastreams, ok := args.Get(rt.StringValue("sinks")).TryTable(); ok {
|
||||||
|
handleStream(luastreams.Get(rt.StringValue("out")), streams, false, false)
|
||||||
|
handleStream(luastreams.Get(rt.StringValue("err")), streams, true, false)
|
||||||
|
handleStream(luastreams.Get(rt.StringValue("input")), streams, false, true)
|
||||||
|
}
|
||||||
|
case rt.NilType: // noop
|
||||||
|
default:
|
||||||
|
return nil, errors.New("expected 3rd arg to be a table")
|
||||||
|
}
|
||||||
|
|
||||||
|
var newline bool
|
||||||
|
var cont bool
|
||||||
|
var luaErr rt.Value = rt.NilValue
|
||||||
|
exitCode := 0
|
||||||
|
bg, _, _, err := s.Run(cmd, streams)
|
||||||
|
if err != nil {
|
||||||
|
if syntax.IsIncomplete(err) {
|
||||||
|
/*
|
||||||
|
if !interactive {
|
||||||
|
return cmdString, 126, false, false, err
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
if strings.Contains(err.Error(), "unclosed here-document") {
|
||||||
|
newline = true
|
||||||
|
}
|
||||||
|
cont = true
|
||||||
|
} else {
|
||||||
|
if code, ok := interp.IsExitStatus(err); ok {
|
||||||
|
exitCode = int(code)
|
||||||
|
} else {
|
||||||
|
if exErr, ok := util.IsExecError(err); ok {
|
||||||
|
exitCode = exErr.Code
|
||||||
|
}
|
||||||
|
luaErr = rt.StringValue(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runnerRet := rt.NewTable()
|
||||||
|
runnerRet.Set(rt.StringValue("input"), rt.StringValue(cmd))
|
||||||
|
runnerRet.Set(rt.StringValue("exitCode"), rt.IntValue(int64(exitCode)))
|
||||||
|
runnerRet.Set(rt.StringValue("continue"), rt.BoolValue(cont))
|
||||||
|
runnerRet.Set(rt.StringValue("newline"), rt.BoolValue(newline))
|
||||||
|
runnerRet.Set(rt.StringValue("err"), luaErr)
|
||||||
|
|
||||||
|
runnerRet.Set(rt.StringValue("bg"), rt.BoolValue(bg))
|
||||||
|
return c.PushingNext1(t.Runtime, rt.TableValue(runnerRet)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// #member
|
||||||
|
// dir(path)
|
||||||
|
// Changes the directory of the snail instance.
|
||||||
|
// The interpreter keeps its set directory even when the Hilbish process changes
|
||||||
|
// directory, so this should be called on the `hilbish.cd` hook.
|
||||||
|
// #param path string Has to be an absolute path.
|
||||||
|
func snaildir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
if err := c.CheckNArgs(2); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := snailArg(c, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dir, err := c.StringArg(1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
interp.Dir(dir)(s.runner)
|
||||||
|
return c.Next(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleStream(v rt.Value, strms *util.Streams, errStream, inStream bool) error {
|
||||||
|
if v == rt.NilValue {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ud, ok := v.TryUserData()
|
||||||
|
if !ok {
|
||||||
|
return errors.New("expected metatable argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
val := ud.Value()
|
||||||
|
var varstrm io.ReadWriter
|
||||||
|
if f, ok := val.(*iolib.File); ok {
|
||||||
|
varstrm = f.Handle()
|
||||||
|
}
|
||||||
|
|
||||||
|
if f, ok := val.(*util.Sink); ok {
|
||||||
|
varstrm = f.Rw
|
||||||
|
}
|
||||||
|
|
||||||
|
if varstrm == nil {
|
||||||
|
return errors.New("expected either a sink or file")
|
||||||
|
}
|
||||||
|
|
||||||
|
if errStream {
|
||||||
|
strms.Stderr = varstrm
|
||||||
|
} else if inStream {
|
||||||
|
strms.Stdin = varstrm
|
||||||
|
} else {
|
||||||
|
strms.Stdout = varstrm
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func snailArg(c *rt.GoCont, arg int) (*Snail, error) {
|
||||||
|
s, ok := valueToSnail(c.Arg(arg))
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("#%d must be a snail", arg + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func valueToSnail(val rt.Value) (*Snail, bool) {
|
||||||
|
u, ok := val.TryUserData()
|
||||||
|
if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
s, ok := u.Value().(*Snail)
|
||||||
|
return s, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func snailUserData(s *Snail) *rt.UserData {
|
||||||
|
snailMeta := s.runtime.Registry(snailMetaKey)
|
||||||
|
return rt.NewUserData(s, snailMeta.AsTable())
|
||||||
|
}
|
302
golibs/snail/snail.go
Normal file
302
golibs/snail/snail.go
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
package snail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"hilbish/util"
|
||||||
|
|
||||||
|
rt "github.com/arnodel/golua/runtime"
|
||||||
|
"mvdan.cc/sh/v3/shell"
|
||||||
|
//"github.com/yuin/gopher-lua/parse"
|
||||||
|
"mvdan.cc/sh/v3/interp"
|
||||||
|
"mvdan.cc/sh/v3/syntax"
|
||||||
|
"mvdan.cc/sh/v3/expand"
|
||||||
|
)
|
||||||
|
|
||||||
|
// #type
|
||||||
|
// A Snail is a shell script interpreter instance.
|
||||||
|
type Snail struct{
|
||||||
|
runner *interp.Runner
|
||||||
|
runtime *rt.Runtime
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(rtm *rt.Runtime) *Snail {
|
||||||
|
runner, _ := interp.New()
|
||||||
|
|
||||||
|
return &Snail{
|
||||||
|
runner: runner,
|
||||||
|
runtime: rtm,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Snail) Run(cmd string, strms *util.Streams) (bool, io.Writer, io.Writer, error){
|
||||||
|
file, err := syntax.NewParser().Parse(strings.NewReader(cmd), "")
|
||||||
|
if err != nil {
|
||||||
|
return false, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if strms == nil {
|
||||||
|
strms = &util.Streams{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if strms.Stdout == nil {
|
||||||
|
strms.Stdout = os.Stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
if strms.Stderr == nil {
|
||||||
|
strms.Stderr = os.Stderr
|
||||||
|
}
|
||||||
|
|
||||||
|
if strms.Stdin == nil {
|
||||||
|
strms.Stdin = os.Stdin
|
||||||
|
}
|
||||||
|
|
||||||
|
interp.StdIO(strms.Stdin, strms.Stdout, strms.Stderr)(s.runner)
|
||||||
|
interp.Env(nil)(s.runner)
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
//printer := syntax.NewPrinter()
|
||||||
|
|
||||||
|
var bg bool
|
||||||
|
for _, stmt := range file.Stmts {
|
||||||
|
bg = false
|
||||||
|
if stmt.Background {
|
||||||
|
bg = true
|
||||||
|
//printer.Print(buf, stmt.Cmd)
|
||||||
|
|
||||||
|
//stmtStr := buf.String()
|
||||||
|
buf.Reset()
|
||||||
|
//jobs.add(stmtStr, []string{}, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
interp.ExecHandler(func(ctx context.Context, args []string) error {
|
||||||
|
_, argstring := splitInput(strings.Join(args, " "))
|
||||||
|
// i dont really like this but it works
|
||||||
|
aliases := make(map[string]string)
|
||||||
|
aliasesLua, _ := util.DoString(s.runtime, "return hilbish.aliases.list()")
|
||||||
|
util.ForEach(aliasesLua.AsTable(), func(k, v rt.Value) {
|
||||||
|
aliases[k.AsString()] = v.AsString()
|
||||||
|
})
|
||||||
|
if aliases[args[0]] != "" {
|
||||||
|
for i, arg := range args {
|
||||||
|
if strings.Contains(arg, " ") {
|
||||||
|
args[i] = fmt.Sprintf("\"%s\"", arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, argstring = splitInput(strings.Join(args, " "))
|
||||||
|
|
||||||
|
// If alias was found, use command alias
|
||||||
|
argstring = util.MustDoString(s.runtime, fmt.Sprintf(`return hilbish.aliases.resolve("%s")`, argstring)).AsString()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
args, err = shell.Fields(argstring, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If command is defined in Lua then run it
|
||||||
|
luacmdArgs := rt.NewTable()
|
||||||
|
for i, str := range args[1:] {
|
||||||
|
luacmdArgs.Set(rt.IntValue(int64(i + 1)), rt.StringValue(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
hc := interp.HandlerCtx(ctx)
|
||||||
|
|
||||||
|
cmds := make(map[string]*rt.Closure)
|
||||||
|
luaCmds := util.MustDoString(s.runtime, "local commander = require 'commander'; return commander.registry()").AsTable()
|
||||||
|
util.ForEach(luaCmds, func(k, v rt.Value) {
|
||||||
|
cmds[k.AsString()] = v.AsTable().Get(rt.StringValue("exec")).AsClosure()
|
||||||
|
})
|
||||||
|
if cmd := cmds[args[0]]; cmd != nil {
|
||||||
|
stdin := util.NewSinkInput(s.runtime, hc.Stdin)
|
||||||
|
stdout := util.NewSinkOutput(s.runtime, hc.Stdout)
|
||||||
|
stderr := util.NewSinkOutput(s.runtime, hc.Stderr)
|
||||||
|
|
||||||
|
sinks := rt.NewTable()
|
||||||
|
sinks.Set(rt.StringValue("in"), rt.UserDataValue(stdin.UserData))
|
||||||
|
sinks.Set(rt.StringValue("input"), rt.UserDataValue(stdin.UserData))
|
||||||
|
sinks.Set(rt.StringValue("out"), rt.UserDataValue(stdout.UserData))
|
||||||
|
sinks.Set(rt.StringValue("err"), rt.UserDataValue(stderr.UserData))
|
||||||
|
|
||||||
|
t := rt.NewThread(s.runtime)
|
||||||
|
sig := make(chan os.Signal)
|
||||||
|
exit := make(chan bool)
|
||||||
|
|
||||||
|
luaexitcode := rt.IntValue(63)
|
||||||
|
var err error
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
exit <- true
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
signal.Notify(sig, os.Interrupt)
|
||||||
|
select {
|
||||||
|
case <-sig:
|
||||||
|
t.KillContext()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
luaexitcode, err = rt.Call1(t, rt.FunctionValue(cmd), rt.TableValue(luacmdArgs), rt.TableValue(sinks))
|
||||||
|
exit <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-exit
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "Error in command:\n" + err.Error())
|
||||||
|
return interp.NewExitStatus(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var exitcode uint8
|
||||||
|
|
||||||
|
if code, ok := luaexitcode.TryInt(); ok {
|
||||||
|
exitcode = uint8(code)
|
||||||
|
} else if luaexitcode != rt.NilValue {
|
||||||
|
// deregister commander
|
||||||
|
delete(cmds, args[0])
|
||||||
|
fmt.Fprintf(os.Stderr, "Commander did not return number for exit code. %s, you're fired.\n", args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
return interp.NewExitStatus(exitcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
path, err := util.LookPath(args[0])
|
||||||
|
if err == util.ErrNotExec {
|
||||||
|
return util.ExecError{
|
||||||
|
Typ: "not-executable",
|
||||||
|
Cmd: args[0],
|
||||||
|
Code: 126,
|
||||||
|
Colon: true,
|
||||||
|
Err: util.ErrNotExec,
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
return util.ExecError{
|
||||||
|
Typ: "not-found",
|
||||||
|
Cmd: args[0],
|
||||||
|
Code: 127,
|
||||||
|
Err: util.ErrNotFound,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
killTimeout := 2 * time.Second
|
||||||
|
// from here is basically copy-paste of the default exec handler from
|
||||||
|
// sh/interp but with our job handling
|
||||||
|
|
||||||
|
env := hc.Env
|
||||||
|
envList := os.Environ()
|
||||||
|
env.Each(func(name string, vr expand.Variable) bool {
|
||||||
|
if vr.Exported && vr.Kind == expand.String {
|
||||||
|
envList = append(envList, name+"="+vr.String())
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
cmd := exec.Cmd{
|
||||||
|
Path: path,
|
||||||
|
Args: args,
|
||||||
|
Env: envList,
|
||||||
|
Dir: hc.Dir,
|
||||||
|
Stdin: hc.Stdin,
|
||||||
|
Stdout: hc.Stdout,
|
||||||
|
Stderr: hc.Stderr,
|
||||||
|
}
|
||||||
|
|
||||||
|
//var j *job
|
||||||
|
if bg {
|
||||||
|
/*
|
||||||
|
j = jobs.getLatest()
|
||||||
|
j.setHandle(&cmd)
|
||||||
|
err = j.start()
|
||||||
|
*/
|
||||||
|
} else {
|
||||||
|
err = cmd.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
if done := ctx.Done(); done != nil {
|
||||||
|
go func() {
|
||||||
|
<-done
|
||||||
|
|
||||||
|
if killTimeout <= 0 || runtime.GOOS == "windows" {
|
||||||
|
cmd.Process.Signal(os.Kill)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: don't temporarily leak this goroutine
|
||||||
|
// if the program stops itself with the
|
||||||
|
// interrupt.
|
||||||
|
go func() {
|
||||||
|
time.Sleep(killTimeout)
|
||||||
|
cmd.Process.Signal(os.Kill)
|
||||||
|
}()
|
||||||
|
cmd.Process.Signal(os.Interrupt)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
exit := util.HandleExecErr(err)
|
||||||
|
|
||||||
|
if bg {
|
||||||
|
//j.exitCode = int(exit)
|
||||||
|
//j.finish()
|
||||||
|
}
|
||||||
|
return interp.NewExitStatus(exit)
|
||||||
|
})(s.runner)
|
||||||
|
err = s.runner.Run(context.TODO(), stmt)
|
||||||
|
if err != nil {
|
||||||
|
return bg, strms.Stdout, strms.Stderr, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bg, strms.Stdout, strms.Stderr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitInput(input string) ([]string, string) {
|
||||||
|
// end my suffering
|
||||||
|
// TODO: refactor this garbage
|
||||||
|
quoted := false
|
||||||
|
cmdArgs := []string{}
|
||||||
|
sb := &strings.Builder{}
|
||||||
|
cmdstr := &strings.Builder{}
|
||||||
|
|
||||||
|
for _, r := range input {
|
||||||
|
if r == '"' {
|
||||||
|
// start quoted input
|
||||||
|
// this determines if other runes are replaced
|
||||||
|
quoted = !quoted
|
||||||
|
// dont add back quotes
|
||||||
|
//sb.WriteRune(r)
|
||||||
|
} else if !quoted && r == '~' {
|
||||||
|
// if not in quotes and ~ is found then make it $HOME
|
||||||
|
sb.WriteString(os.Getenv("HOME"))
|
||||||
|
} else if !quoted && r == ' ' {
|
||||||
|
// if not quoted and there's a space then add to cmdargs
|
||||||
|
cmdArgs = append(cmdArgs, sb.String())
|
||||||
|
sb.Reset()
|
||||||
|
} else {
|
||||||
|
sb.WriteRune(r)
|
||||||
|
}
|
||||||
|
cmdstr.WriteRune(r)
|
||||||
|
}
|
||||||
|
if sb.Len() > 0 {
|
||||||
|
cmdArgs = append(cmdArgs, sb.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmdArgs, cmdstr.String()
|
||||||
|
}
|
6
job.go
6
job.go
@ -56,8 +56,8 @@ func (j *job) start() error {
|
|||||||
}
|
}
|
||||||
j.setHandle(&cmd)
|
j.setHandle(&cmd)
|
||||||
}
|
}
|
||||||
// bgProcAttr is defined in execfile_<os>.go, it holds a procattr struct
|
// bgProcAttr is defined in job_<os>.go, it holds a procattr struct
|
||||||
// in a simple explanation, it makes signals from hilbish (sigint)
|
// in a simple explanation, it makes signals from hilbish (like sigint)
|
||||||
// not go to it (child process)
|
// not go to it (child process)
|
||||||
j.handle.SysProcAttr = bgProcAttr
|
j.handle.SysProcAttr = bgProcAttr
|
||||||
// reset output buffers
|
// reset output buffers
|
||||||
@ -136,7 +136,7 @@ func luaStartJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
|
|
||||||
if !j.running {
|
if !j.running {
|
||||||
err := j.start()
|
err := j.start()
|
||||||
exit := handleExecErr(err)
|
exit := util.HandleExecErr(err)
|
||||||
j.exitCode = int(exit)
|
j.exitCode = int(exit)
|
||||||
j.finish()
|
j.finish()
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,10 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var bgProcAttr *syscall.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
Setpgid: true,
|
||||||
|
}
|
||||||
|
|
||||||
func (j *job) foreground() error {
|
func (j *job) foreground() error {
|
||||||
if jobs.foreground {
|
if jobs.foreground {
|
||||||
return errors.New("(another) job already foregrounded")
|
return errors.New("(another) job already foregrounded")
|
||||||
|
@ -4,8 +4,13 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var bgProcAttr *syscall.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
|
||||||
|
}
|
||||||
|
|
||||||
func (j *job) foreground() error {
|
func (j *job) foreground() error {
|
||||||
return errors.New("not supported on windows")
|
return errors.New("not supported on windows")
|
||||||
}
|
}
|
||||||
|
10
lua.go
10
lua.go
@ -3,11 +3,13 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"hilbish/util"
|
"hilbish/util"
|
||||||
"hilbish/golibs/bait"
|
"hilbish/golibs/bait"
|
||||||
"hilbish/golibs/commander"
|
"hilbish/golibs/commander"
|
||||||
"hilbish/golibs/fs"
|
"hilbish/golibs/fs"
|
||||||
|
"hilbish/golibs/snail"
|
||||||
"hilbish/golibs/terminal"
|
"hilbish/golibs/terminal"
|
||||||
|
|
||||||
rt "github.com/arnodel/golua/runtime"
|
rt "github.com/arnodel/golua/runtime"
|
||||||
@ -23,16 +25,14 @@ func luaInit() {
|
|||||||
MessageHandler: debuglib.Traceback,
|
MessageHandler: debuglib.Traceback,
|
||||||
})
|
})
|
||||||
lib.LoadAll(l)
|
lib.LoadAll(l)
|
||||||
setupSinkType(l)
|
|
||||||
|
|
||||||
lib.LoadLibs(l, hilbishLoader)
|
lib.LoadLibs(l, hilbishLoader)
|
||||||
// yes this is stupid, i know
|
// yes this is stupid, i know
|
||||||
util.DoString(l, "hilbish = require 'hilbish'")
|
util.DoString(l, "hilbish = require 'hilbish'")
|
||||||
|
|
||||||
// Add fs and terminal module module to Lua
|
lib.LoadLibs(l, fs.Loader)
|
||||||
f := fs.New(runner)
|
|
||||||
lib.LoadLibs(l, f.Loader)
|
|
||||||
lib.LoadLibs(l, terminal.Loader)
|
lib.LoadLibs(l, terminal.Loader)
|
||||||
|
lib.LoadLibs(l, snail.Loader)
|
||||||
|
|
||||||
cmds = commander.New(l)
|
cmds = commander.New(l)
|
||||||
lib.LoadLibs(l, cmds.Loader)
|
lib.LoadLibs(l, cmds.Loader)
|
||||||
@ -64,7 +64,7 @@ func luaInit() {
|
|||||||
|
|
||||||
err1 := util.DoFile(l, "nature/init.lua")
|
err1 := util.DoFile(l, "nature/init.lua")
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
err2 := util.DoFile(l, preloadPath)
|
err2 := util.DoFile(l, filepath.Join(dataDir, "nature", "init.lua"))
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
fmt.Fprintln(os.Stderr, "Missing nature module, some functionality and builtins will be missing.")
|
fmt.Fprintln(os.Stderr, "Missing nature module, some functionality and builtins will be missing.")
|
||||||
fmt.Fprintln(os.Stderr, "local error:", err1)
|
fmt.Fprintln(os.Stderr, "local error:", err1)
|
||||||
|
38
main.go
38
main.go
@ -21,7 +21,6 @@ import (
|
|||||||
"github.com/pborman/getopt"
|
"github.com/pborman/getopt"
|
||||||
"github.com/maxlandon/readline"
|
"github.com/maxlandon/readline"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
"mvdan.cc/sh/v3/interp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -38,16 +37,27 @@ var (
|
|||||||
cmds *commander.Commander
|
cmds *commander.Commander
|
||||||
defaultConfPath string
|
defaultConfPath string
|
||||||
defaultHistPath string
|
defaultHistPath string
|
||||||
runner *interp.Runner
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
runner, _ = interp.New()
|
if runtime.GOOS == "linux" {
|
||||||
|
// dataDir should only be empty on linux to allow XDG_DATA_DIRS searching.
|
||||||
|
// but since it might be set on some distros (nixos) we should still check if its really is empty.
|
||||||
|
if dataDir == "" {
|
||||||
|
searchableDirs := getenv("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/")
|
||||||
|
dataDir = "."
|
||||||
|
for _, path := range strings.Split(searchableDirs, ":") {
|
||||||
|
_, err := os.Stat(filepath.Join(path, "hilbish", ".hilbishrc.lua"))
|
||||||
|
if err == nil {
|
||||||
|
dataDir = filepath.Join(path, "hilbish")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
curuser, _ = user.Current()
|
curuser, _ = user.Current()
|
||||||
homedir := curuser.HomeDir
|
|
||||||
confDir, _ = os.UserConfigDir()
|
confDir, _ = os.UserConfigDir()
|
||||||
preloadPath = strings.Replace(preloadPath, "~", homedir, 1)
|
|
||||||
sampleConfPath = strings.Replace(sampleConfPath, "~", homedir, 1)
|
|
||||||
|
|
||||||
// i honestly dont know what directories to use for this
|
// i honestly dont know what directories to use for this
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
@ -141,10 +151,11 @@ func main() {
|
|||||||
confpath := ".hilbishrc.lua"
|
confpath := ".hilbishrc.lua"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If it wasnt found, go to the real sample conf
|
// If it wasnt found, go to the real sample conf
|
||||||
_, err = os.ReadFile(sampleConfPath)
|
sampleConfigPath := filepath.Join(dataDir, ".hilbishrc.lua")
|
||||||
confpath = sampleConfPath
|
_, err = os.ReadFile(sampleConfigPath)
|
||||||
|
confpath = sampleConfigPath
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("could not find .hilbishrc.lua or", sampleConfPath)
|
fmt.Println("could not find .hilbishrc.lua or", sampleConfigPath)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -313,15 +324,6 @@ func removeDupes(slice []string) []string {
|
|||||||
return newSlice
|
return newSlice
|
||||||
}
|
}
|
||||||
|
|
||||||
func contains(s []string, e string) bool {
|
|
||||||
for _, a := range s {
|
|
||||||
if strings.ToLower(a) == strings.ToLower(e) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func exit(code int) {
|
func exit(code int) {
|
||||||
jobs.stopAll()
|
jobs.stopAll()
|
||||||
|
|
||||||
|
61
nature/abbr.lua
Normal file
61
nature/abbr.lua
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
-- @module hilbish.abbr
|
||||||
|
-- command line abbreviations
|
||||||
|
-- The abbr module manages Hilbish abbreviations. These are words that can be replaced
|
||||||
|
-- with longer command line strings when entered.
|
||||||
|
-- As an example, `git push` can be abbreviated to `gp`. When the user types
|
||||||
|
-- `gp` into the command line, after hitting space or enter, it will expand to `git push`.
|
||||||
|
-- Abbreviations can be used as an alternative to aliases. They are saved entirely in the history
|
||||||
|
-- Instead of the aliased form of the same command.
|
||||||
|
local bait = require 'bait'
|
||||||
|
local hilbish = require 'hilbish'
|
||||||
|
hilbish.abbr = {
|
||||||
|
all = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
--- Adds an abbreviation. The `abbr` is the abbreviation itself,
|
||||||
|
--- while `expanded` is what the abbreviation should expand to.
|
||||||
|
--- It can be either a function or a string. If it is a function, it will expand to what
|
||||||
|
--- the function returns.
|
||||||
|
--- `opts` is a table that accepts 1 key: `anywhere`.
|
||||||
|
--- `opts.anywhere` defines whether the abbr expands anywhere in the command line or not,
|
||||||
|
--- whereas the default behavior is only at the beginning of the line
|
||||||
|
-- @param abbr string
|
||||||
|
-- @param expanded|function string
|
||||||
|
-- @param opts table
|
||||||
|
function hilbish.abbr.add(abbr, expanded, opts)
|
||||||
|
print(abbr, expanded, opts)
|
||||||
|
opts = opts or {}
|
||||||
|
opts.abbr = abbr
|
||||||
|
opts.expand = expanded
|
||||||
|
hilbish.abbr.all[abbr] = opts
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Removes the named `abbr`.
|
||||||
|
-- @param abbr string
|
||||||
|
function hilbish.abbr.remove(abbr)
|
||||||
|
hilbish.abbr.all[abbr] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
bait.catch('hilbish.rawInput', function(c)
|
||||||
|
-- 0x0d == enter
|
||||||
|
if c == ' ' or c == string.char(0x0d) then
|
||||||
|
-- check if the last "word" was a valid abbreviation
|
||||||
|
local line = hilbish.editor.getLine()
|
||||||
|
local lineSplits = string.split(line, ' ')
|
||||||
|
local thisAbbr = hilbish.abbr.all[lineSplits[#lineSplits]]
|
||||||
|
|
||||||
|
if thisAbbr and (#lineSplits == 1 or thisAbbr.anywhere == true) then
|
||||||
|
hilbish.editor.deleteByAmount(-lineSplits[#lineSplits]:len())
|
||||||
|
if type(thisAbbr.expand) == 'string' then
|
||||||
|
hilbish.editor.insert(thisAbbr.expand)
|
||||||
|
elseif type(thisAbbr.expand) == 'function' then
|
||||||
|
local expandRet = thisAbbr.expand()
|
||||||
|
if type(expandRet) ~= 'string' then
|
||||||
|
print(string.format('abbr %s has an expand function that did not return a string. instead it returned: %s', thisAbbr.abbr, expandRet))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
hilbish.editor.insert(expandRet)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
@ -3,8 +3,9 @@ local commander = require 'commander'
|
|||||||
local fs = require 'fs'
|
local fs = require 'fs'
|
||||||
local dirs = require 'nature.dirs'
|
local dirs = require 'nature.dirs'
|
||||||
|
|
||||||
dirs.old = hilbish.cwd()
|
|
||||||
commander.register('cd', function (args, sinks)
|
commander.register('cd', function (args, sinks)
|
||||||
|
local oldPath = hilbish.cwd()
|
||||||
|
|
||||||
if #args > 1 then
|
if #args > 1 then
|
||||||
sinks.out:writeln("cd: too many arguments")
|
sinks.out:writeln("cd: too many arguments")
|
||||||
return 1
|
return 1
|
||||||
@ -16,13 +17,13 @@ commander.register('cd', function (args, sinks)
|
|||||||
sinks.out:writeln(path)
|
sinks.out:writeln(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
dirs.setOld(hilbish.cwd())
|
local absPath = fs.abs(path)
|
||||||
dirs.push(path)
|
|
||||||
|
|
||||||
local ok, err = pcall(function() fs.cd(path) end)
|
local ok, err = pcall(function() fs.cd(path) end)
|
||||||
if not ok then
|
if not ok then
|
||||||
sinks.out:writeln(err)
|
sinks.out:writeln(err)
|
||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
bait.throw('cd', path)
|
|
||||||
|
bait.throw('cd', path, oldPath)
|
||||||
|
bait.throw('hilbish.cd', absPath, oldPath)
|
||||||
end)
|
end)
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
-- @module dirs
|
-- @module dirs
|
||||||
|
-- internal directory management
|
||||||
|
-- The dirs module defines a small set of functions to store and manage
|
||||||
|
-- directories.
|
||||||
|
local bait = require 'bait'
|
||||||
local fs = require 'fs'
|
local fs = require 'fs'
|
||||||
|
|
||||||
local dirs = {}
|
local dirs = {}
|
||||||
|
|
||||||
--- Last (current working) directory. Separate from recentDirs mainly for
|
--- Last (current working) directory. Separate from recentDirs mainly for easier use.
|
||||||
--- easier use.
|
|
||||||
dirs.old = ''
|
dirs.old = ''
|
||||||
--- Table of recent directories. For use, look at public functions.
|
--- Table of recent directories. For use, look at public functions.
|
||||||
dirs.recentDirs = {}
|
dirs.recentDirs = {}
|
||||||
@ -35,19 +38,21 @@ function dirRecents(num, remove)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- Look at `num` amount of recent directories, starting from the latest.
|
--- Look at `num` amount of recent directories, starting from the latest.
|
||||||
|
--- This returns a table of recent directories, up to the `num` amount.
|
||||||
-- @param num? number
|
-- @param num? number
|
||||||
function dirs.peak(num)
|
function dirs.peak(num)
|
||||||
return dirRecents(num)
|
return dirRecents(num)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Add `d` to the recent directories list.
|
--- Add `dir` to the recent directories list.
|
||||||
function dirs.push(d)
|
--- @param dir string
|
||||||
|
function dirs.push(dir)
|
||||||
dirs.recentDirs[dirs.recentSize + 1] = nil
|
dirs.recentDirs[dirs.recentSize + 1] = nil
|
||||||
if dirs.recentDirs[#dirs.recentDirs - 1] ~= d then
|
if dirs.recentDirs[#dirs.recentDirs - 1] ~= dir then
|
||||||
ok, d = pcall(fs.abs, d)
|
local ok, dir = pcall(fs.abs, dir)
|
||||||
assert(ok, 'could not turn "' .. d .. '"into an absolute path')
|
assert(ok, 'could not turn "' .. dir .. '"into an absolute path')
|
||||||
|
|
||||||
table.insert(dirs.recentDirs, 1, d)
|
table.insert(dirs.recentDirs, 1, dir)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -73,4 +78,9 @@ function dirs.setOld(d)
|
|||||||
dirs.old = d
|
dirs.old = d
|
||||||
end
|
end
|
||||||
|
|
||||||
|
bait.catch('hilbish.cd', function(path, oldPath)
|
||||||
|
dirs.setOld(oldPath)
|
||||||
|
dirs.push(path)
|
||||||
|
end)
|
||||||
|
|
||||||
return dirs
|
return dirs
|
||||||
|
@ -1,13 +1,25 @@
|
|||||||
|
-- @module doc
|
||||||
|
-- command-line doc rendering
|
||||||
|
-- The doc module contains a small set of functions
|
||||||
|
-- used by the Greenhouse pager to render parts of the documentation pages.
|
||||||
|
-- This is only documented for the sake of it. It's only intended use
|
||||||
|
-- is by the Greenhouse pager.
|
||||||
local lunacolors = require 'lunacolors'
|
local lunacolors = require 'lunacolors'
|
||||||
|
|
||||||
local M = {}
|
local doc = {}
|
||||||
|
|
||||||
function M.highlight(text)
|
--- Performs basic Lua code highlighting.
|
||||||
|
--- @param text string Code/text to do highlighting on.
|
||||||
|
function doc.highlight(text)
|
||||||
return text:gsub('\'.-\'', lunacolors.yellow)
|
return text:gsub('\'.-\'', lunacolors.yellow)
|
||||||
--:gsub('%-%- .-', lunacolors.black)
|
--:gsub('%-%- .-', lunacolors.black)
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.renderCodeBlock(text)
|
--- Assembles and renders a code block. This returns
|
||||||
|
--- the supplied text based on the number of command line columns,
|
||||||
|
--- and styles it to resemble a code block.
|
||||||
|
--- @param text string
|
||||||
|
function doc.renderCodeBlock(text)
|
||||||
local longest = 0
|
local longest = 0
|
||||||
local lines = string.split(text:gsub('\t', ' '), '\n')
|
local lines = string.split(text:gsub('\t', ' '), '\n')
|
||||||
|
|
||||||
@ -17,14 +29,18 @@ function M.renderCodeBlock(text)
|
|||||||
end
|
end
|
||||||
|
|
||||||
for i, line in ipairs(lines) do
|
for i, line in ipairs(lines) do
|
||||||
lines[i] = lunacolors.format('{greyBg}' .. ' ' .. M.highlight(line:sub(0, longest))
|
lines[i] = lunacolors.format('{greyBg}' .. ' ' .. doc.highlight(line:sub(0, longest))
|
||||||
.. string.rep(' ', longest - line:len()) .. ' ')
|
.. string.rep(' ', longest - line:len()) .. ' ')
|
||||||
end
|
end
|
||||||
|
|
||||||
return '\n' .. lunacolors.format('{greyBg}' .. table.concat(lines, '\n')) .. '\n'
|
return '\n' .. lunacolors.format('{greyBg}' .. table.concat(lines, '\n')) .. '\n'
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.renderInfoBlock(type, text)
|
--- Renders an info block. An info block is a block of text with
|
||||||
|
--- an icon and styled text block.
|
||||||
|
--- @param type string Type of info block. The only one specially styled is the `warning`.
|
||||||
|
--- @param text string
|
||||||
|
function doc.renderInfoBlock(type, text)
|
||||||
local longest = 0
|
local longest = 0
|
||||||
local lines = string.split(text:gsub('\t', ' '), '\n')
|
local lines = string.split(text:gsub('\t', ' '), '\n')
|
||||||
|
|
||||||
@ -34,7 +50,7 @@ function M.renderInfoBlock(type, text)
|
|||||||
end
|
end
|
||||||
|
|
||||||
for i, line in ipairs(lines) do
|
for i, line in ipairs(lines) do
|
||||||
lines[i] = ' ' .. M.highlight(line:sub(0, longest))
|
lines[i] = ' ' .. doc.highlight(line:sub(0, longest))
|
||||||
.. string.rep(' ', longest - line:len()) .. ' '
|
.. string.rep(' ', longest - line:len()) .. ' '
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -44,4 +60,4 @@ function M.renderInfoBlock(type, text)
|
|||||||
end
|
end
|
||||||
return '\n' .. heading .. '\n' .. lunacolors.format('{greyBg}' .. table.concat(lines, '\n')) .. '\n'
|
return '\n' .. heading .. '\n' .. lunacolors.format('{greyBg}' .. table.concat(lines, '\n')) .. '\n'
|
||||||
end
|
end
|
||||||
return M
|
return doc
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
-- Greenhouse is a simple text scrolling handler for terminal programs.
|
-- @module greenhouse
|
||||||
|
-- Greenhouse is a simple text scrolling handler (pager) for terminal programs.
|
||||||
-- The idea is that it can be set a region to do its scrolling and paging
|
-- The idea is that it can be set a region to do its scrolling and paging
|
||||||
-- job and then the user can draw whatever outside it.
|
-- job and then the user can draw whatever outside it.
|
||||||
-- This reduces code duplication for the message viewer
|
-- This reduces code duplication for the message viewer
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
-- @module greenhouse.page
|
||||||
local Object = require 'nature.object'
|
local Object = require 'nature.object'
|
||||||
|
|
||||||
local Page = Object:extend()
|
local Page = Object:extend()
|
||||||
@ -10,6 +11,7 @@ function Page:new(title, text)
|
|||||||
self.children = {}
|
self.children = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Page:setText(text)
|
function Page:setText(text)
|
||||||
self.lines = string.split(text, '\n')
|
self.lines = string.split(text, '\n')
|
||||||
end
|
end
|
||||||
|
78
nature/hilbish.lua
Normal file
78
nature/hilbish.lua
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
-- @module hilbish
|
||||||
|
local bait = require 'bait'
|
||||||
|
local snail = require 'snail'
|
||||||
|
|
||||||
|
hilbish.snail = snail.new()
|
||||||
|
bait.catch('hilbish.cd', function(path)
|
||||||
|
hilbish.snail:dir(path)
|
||||||
|
end)
|
||||||
|
--- Runs `cmd` in Hilbish's shell script interpreter.
|
||||||
|
--- The `streams` parameter specifies the output and input streams the command should use.
|
||||||
|
--- For example, to write command output to a sink.
|
||||||
|
--- As a table, the caller can directly specify the standard output, error, and input
|
||||||
|
--- streams of the command with the table keys `out`, `err`, and `input` respectively.
|
||||||
|
--- As a boolean, it specifies whether the command should use standard output or return its output streams.
|
||||||
|
--- #example
|
||||||
|
--- -- This code is the same as `ls -l | wc -l`
|
||||||
|
--- local fs = require 'fs'
|
||||||
|
--- local pr, pw = fs.pipe()
|
||||||
|
--- hilbish.run('ls -l', {
|
||||||
|
--- stdout = pw,
|
||||||
|
--- stderr = pw,
|
||||||
|
--- })
|
||||||
|
--- pw:close()
|
||||||
|
--- hilbish.run('wc -l', {
|
||||||
|
--- stdin = pr
|
||||||
|
--- })
|
||||||
|
--- #example
|
||||||
|
-- @param cmd string
|
||||||
|
-- @param streams table|boolean
|
||||||
|
-- @returns number, string, string
|
||||||
|
function hilbish.run(cmd, streams)
|
||||||
|
local sinks = {}
|
||||||
|
|
||||||
|
if type(streams) == 'boolean' then
|
||||||
|
if not streams then
|
||||||
|
sinks = {
|
||||||
|
out = hilbish.sink.new(),
|
||||||
|
err = hilbish.sink.new(),
|
||||||
|
input = io.stdin
|
||||||
|
}
|
||||||
|
end
|
||||||
|
elseif type(streams) == 'table' then
|
||||||
|
sinks = streams
|
||||||
|
end
|
||||||
|
|
||||||
|
local out = hilbish.snail:run(cmd, {sinks = sinks})
|
||||||
|
local returns = {out.exitCode}
|
||||||
|
|
||||||
|
if type(streams) == 'boolean' and not streams then
|
||||||
|
table.insert(returns, sinks.out:readAll())
|
||||||
|
table.insert(returns, sinks.err:readAll())
|
||||||
|
end
|
||||||
|
|
||||||
|
return table.unpack(returns)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Sets the execution/runner mode for interactive Hilbish.
|
||||||
|
--- **NOTE: This function is deprecated and will be removed in 3.0**
|
||||||
|
--- Use `hilbish.runner.setCurrent` instead.
|
||||||
|
--- This determines whether Hilbish wll try to run input as Lua
|
||||||
|
--- and/or sh or only do one of either.
|
||||||
|
--- Accepted values for mode are hybrid (the default), hybridRev (sh first then Lua),
|
||||||
|
--- sh, and lua. It also accepts a function, to which if it is passed one
|
||||||
|
--- will call it to execute user input instead.
|
||||||
|
--- Read [about runner mode](../features/runner-mode) for more information.
|
||||||
|
-- @param mode string|function
|
||||||
|
function hilbish.runnerMode(mode)
|
||||||
|
if type(mode) == 'string' then
|
||||||
|
hilbish.runner.setCurrent(mode)
|
||||||
|
elseif type(mode) == 'function' then
|
||||||
|
hilbish.runner.set('_', {
|
||||||
|
run = mode
|
||||||
|
})
|
||||||
|
hilbish.runner.setCurrent '_'
|
||||||
|
else
|
||||||
|
error('expected runner mode type to be either string or function, got', type(mode))
|
||||||
|
end
|
||||||
|
end
|
@ -1,3 +1,14 @@
|
|||||||
|
-- @module hilbish.messages
|
||||||
|
-- simplistic message passing
|
||||||
|
-- The messages interface defines a way for Hilbish-integrated commands,
|
||||||
|
-- user config and other tasks to send notifications to alert the user.z
|
||||||
|
-- The `hilbish.message` type is a table with the following keys:
|
||||||
|
-- `title` (string): A title for the message notification.
|
||||||
|
-- `text` (string): The contents of the message.
|
||||||
|
-- `channel` (string): States the origin of the message, `hilbish.*` is reserved for Hilbish tasks.
|
||||||
|
-- `summary` (string): A short summary of the `text`.
|
||||||
|
-- `icon` (string): Unicode (preferably standard emoji) icon for the message notification
|
||||||
|
-- `read` (boolean): Whether the full message has been read or not.
|
||||||
local bait = require 'bait'
|
local bait = require 'bait'
|
||||||
local commander = require 'commander'
|
local commander = require 'commander'
|
||||||
local lunacolors = require 'lunacolors'
|
local lunacolors = require 'lunacolors'
|
||||||
@ -44,6 +55,8 @@ function hilbish.messages.send(message)
|
|||||||
bait.throw('hilbish.notification', message)
|
bait.throw('hilbish.notification', message)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Marks a message at `idx` as read.
|
||||||
|
--- @param idx number
|
||||||
function hilbish.messages.read(idx)
|
function hilbish.messages.read(idx)
|
||||||
local msg = M._messages[idx]
|
local msg = M._messages[idx]
|
||||||
if msg then
|
if msg then
|
||||||
@ -52,16 +65,20 @@ function hilbish.messages.read(idx)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function hilbish.messages.readAll(idx)
|
--- Marks all messages as read.
|
||||||
|
function hilbish.messages.readAll()
|
||||||
for _, msg in ipairs(hilbish.messages.all()) do
|
for _, msg in ipairs(hilbish.messages.all()) do
|
||||||
hilbish.messages.read(msg.index)
|
hilbish.messages.read(msg.index)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Returns the amount of unread messages.
|
||||||
function hilbish.messages.unreadCount()
|
function hilbish.messages.unreadCount()
|
||||||
return unread
|
return unread
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Deletes the message at `idx`.
|
||||||
|
--- @param idx number
|
||||||
function hilbish.messages.delete(idx)
|
function hilbish.messages.delete(idx)
|
||||||
local msg = M._messages[idx]
|
local msg = M._messages[idx]
|
||||||
if not msg then
|
if not msg then
|
||||||
@ -71,12 +88,14 @@ function hilbish.messages.delete(idx)
|
|||||||
M._messages[idx] = nil
|
M._messages[idx] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Deletes all messages.
|
||||||
function hilbish.messages.clear()
|
function hilbish.messages.clear()
|
||||||
for _, msg in ipairs(hilbish.messages.all()) do
|
for _, msg in ipairs(hilbish.messages.all()) do
|
||||||
hilbish.messages.delete(msg.index)
|
hilbish.messages.delete(msg.index)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Returns all messages.
|
||||||
function hilbish.messages.all()
|
function hilbish.messages.all()
|
||||||
return M._messages
|
return M._messages
|
||||||
end
|
end
|
||||||
|
@ -18,12 +18,15 @@ table.insert(package.searchers, function(module)
|
|||||||
return function() return hilbish.module.load(path) end, path
|
return function() return hilbish.module.load(path) end, path
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
require 'nature.hilbish'
|
||||||
|
|
||||||
require 'nature.commands'
|
require 'nature.commands'
|
||||||
require 'nature.completions'
|
require 'nature.completions'
|
||||||
require 'nature.opts'
|
require 'nature.opts'
|
||||||
require 'nature.vim'
|
require 'nature.vim'
|
||||||
require 'nature.runner'
|
require 'nature.runner'
|
||||||
require 'nature.hummingbird'
|
require 'nature.hummingbird'
|
||||||
|
require 'nature.abbr'
|
||||||
|
|
||||||
local shlvl = tonumber(os.getenv 'SHLVL')
|
local shlvl = tonumber(os.getenv 'SHLVL')
|
||||||
if shlvl ~= nil then
|
if shlvl ~= nil then
|
||||||
|
@ -2,9 +2,7 @@ local bait = require 'bait'
|
|||||||
local lunacolors = require 'lunacolors'
|
local lunacolors = require 'lunacolors'
|
||||||
|
|
||||||
hilbish.motd = [[
|
hilbish.motd = [[
|
||||||
Wait ... {magenta}2.3{reset} is basically the same as {red}2.2?{reset}
|
{magenta}Hilbish{reset} blooms in the {blue}midnight.{reset}
|
||||||
Erm.. {blue}Ctrl-C works for Commanders,{reset} {cyan}and the sh runner has some fixes.{reset}
|
|
||||||
Just trust me bro, this is an important bug fix release. {red}- 🌺 sammyette{reset}
|
|
||||||
]]
|
]]
|
||||||
|
|
||||||
bait.catch('hilbish.init', function()
|
bait.catch('hilbish.init', function()
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
--- hilbish.runner
|
-- @module hilbish.runner
|
||||||
|
local snail = require 'snail'
|
||||||
local currentRunner = 'hybrid'
|
local currentRunner = 'hybrid'
|
||||||
local runners = {}
|
local runners = {}
|
||||||
|
|
||||||
@ -6,7 +7,7 @@ local runners = {}
|
|||||||
hilbish = hilbish
|
hilbish = hilbish
|
||||||
|
|
||||||
--- Get a runner by name.
|
--- Get a runner by name.
|
||||||
--- @param name string
|
--- @param name string Name of the runner to retrieve.
|
||||||
--- @return table
|
--- @return table
|
||||||
function hilbish.runner.get(name)
|
function hilbish.runner.get(name)
|
||||||
local r = runners[name]
|
local r = runners[name]
|
||||||
@ -18,10 +19,10 @@ function hilbish.runner.get(name)
|
|||||||
return r
|
return r
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Adds a runner to the table of available runners. If runner is a table,
|
--- Adds a runner to the table of available runners.
|
||||||
--- it must have the run function in it.
|
--- If runner is a table, it must have the run function in it.
|
||||||
--- @param name string
|
--- @param name string Name of the runner
|
||||||
--- @param runner function | table
|
--- @param runner function|table
|
||||||
function hilbish.runner.add(name, runner)
|
function hilbish.runner.add(name, runner)
|
||||||
if type(name) ~= 'string' then
|
if type(name) ~= 'string' then
|
||||||
error 'expected runner name to be a table'
|
error 'expected runner name to be a table'
|
||||||
@ -42,7 +43,9 @@ function hilbish.runner.add(name, runner)
|
|||||||
hilbish.runner.set(name, runner)
|
hilbish.runner.set(name, runner)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Sets a runner by name. The runner table must have the run function in it.
|
--- *Sets* a runner by name. The difference between this function and
|
||||||
|
--- add, is set will *not* check if the named runner exists.
|
||||||
|
--- The runner table must have the run function in it.
|
||||||
--- @param name string
|
--- @param name string
|
||||||
--- @param runner table
|
--- @param runner table
|
||||||
function hilbish.runner.set(name, runner)
|
function hilbish.runner.set(name, runner)
|
||||||
@ -53,11 +56,11 @@ function hilbish.runner.set(name, runner)
|
|||||||
runners[name] = runner
|
runners[name] = runner
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Executes cmd with a runner. If runnerName isn't passed, it uses
|
--- Executes `cmd` with a runner.
|
||||||
--- the user's current runner.
|
--- If `runnerName` is not specified, it uses the default Hilbish runner.
|
||||||
--- @param cmd string
|
--- @param cmd string
|
||||||
--- @param runnerName string?
|
--- @param runnerName string?
|
||||||
--- @return string, number, string
|
--- @return table
|
||||||
function hilbish.runner.exec(cmd, runnerName)
|
function hilbish.runner.exec(cmd, runnerName)
|
||||||
if not runnerName then runnerName = currentRunner end
|
if not runnerName then runnerName = currentRunner end
|
||||||
|
|
||||||
@ -66,13 +69,11 @@ function hilbish.runner.exec(cmd, runnerName)
|
|||||||
return r.run(cmd)
|
return r.run(cmd)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Sets the current interactive/command line runner mode.
|
--- Sets Hilbish's runner mode by name.
|
||||||
--- @param name string
|
--- @param name string
|
||||||
function hilbish.runner.setCurrent(name)
|
function hilbish.runner.setCurrent(name)
|
||||||
local r = hilbish.runner.get(name)
|
hilbish.runner.get(name) -- throws if it doesnt exist.
|
||||||
currentRunner = name
|
currentRunner = name
|
||||||
|
|
||||||
hilbish.runner.setMode(r.run)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Returns the current runner by name.
|
--- Returns the current runner by name.
|
||||||
@ -81,6 +82,81 @@ function hilbish.runner.getCurrent()
|
|||||||
return currentRunner
|
return currentRunner
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- **NOTE: This function is deprecated and will be removed in 3.0**
|
||||||
|
--- Use `hilbish.runner.setCurrent` instead.
|
||||||
|
--- This is the same as the `hilbish.runnerMode` function.
|
||||||
|
--- It takes a callback, which will be used to execute all interactive input.
|
||||||
|
--- Or a string which names the runner mode to use.
|
||||||
|
-- @param mode string|function
|
||||||
|
function hilbish.runner.setMode(mode)
|
||||||
|
hilbish.runnerMode(mode)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function finishExec(exitCode, input, priv)
|
||||||
|
hilbish.exitCode = exitCode
|
||||||
|
bait.throw('command.exit', exitCode, input, priv)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function continuePrompt(prev, newline)
|
||||||
|
local multilinePrompt = hilbish.multiprompt()
|
||||||
|
-- the return of hilbish.read is nil when error or ctrl-d
|
||||||
|
local cont = hilbish.read(multilinePrompt)
|
||||||
|
if not cont then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if newline then
|
||||||
|
cont = '\n' .. cont
|
||||||
|
end
|
||||||
|
|
||||||
|
if cont:match '\\$' then
|
||||||
|
cont = cont:gsub('\\$', '') .. '\n'
|
||||||
|
end
|
||||||
|
|
||||||
|
return prev .. cont
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Runs `input` with the currently set Hilbish runner.
|
||||||
|
--- This method is how Hilbish executes commands.
|
||||||
|
--- `priv` is an optional boolean used to state if the input should be saved to history.
|
||||||
|
-- @param input string
|
||||||
|
-- @param priv bool
|
||||||
|
function hilbish.runner.run(input, priv)
|
||||||
|
local command = hilbish.aliases.resolve(input)
|
||||||
|
bait.throw('command.preexec', input, command)
|
||||||
|
|
||||||
|
::rerun::
|
||||||
|
local runner = hilbish.runner.get(currentRunner)
|
||||||
|
local ok, out = pcall(runner.run, input)
|
||||||
|
if not ok then
|
||||||
|
io.stderr:write(out .. '\n')
|
||||||
|
finishExec(124, out.input, priv)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if out.continue then
|
||||||
|
local contInput = continuePrompt(input, out.newline)
|
||||||
|
if contInput then
|
||||||
|
input = contInput
|
||||||
|
goto rerun
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if out.err then
|
||||||
|
local fields = string.split(out.err, ': ')
|
||||||
|
if fields[2] == 'not-found' or fields[2] == 'not-executable' then
|
||||||
|
bait.throw('command.' .. fields[2], fields[1])
|
||||||
|
else
|
||||||
|
io.stderr:write(out.err .. '\n')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
finishExec(out.exitCode, out.input, priv)
|
||||||
|
end
|
||||||
|
|
||||||
|
function hilbish.runner.sh(input)
|
||||||
|
return hilbish.snail:run(input)
|
||||||
|
end
|
||||||
|
|
||||||
hilbish.runner.add('hybrid', function(input)
|
hilbish.runner.add('hybrid', function(input)
|
||||||
local cmdStr = hilbish.aliases.resolve(input)
|
local cmdStr = hilbish.aliases.resolve(input)
|
||||||
|
|
||||||
@ -107,7 +183,5 @@ hilbish.runner.add('lua', function(input)
|
|||||||
return hilbish.runner.lua(cmdStr)
|
return hilbish.runner.lua(cmdStr)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
hilbish.runner.add('sh', function(input)
|
hilbish.runner.add('sh', hilbish.runner.sh)
|
||||||
return hilbish.runner.sh(input)
|
hilbish.runner.setCurrent 'hybrid'
|
||||||
end)
|
|
||||||
|
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -56,3 +56,10 @@ func (rl *Instance) resetHintText() {
|
|||||||
//rl.hintY = 0
|
//rl.hintY = 0
|
||||||
rl.hintText = []rune{}
|
rl.hintText = []rune{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rl *Instance) insertHintText() {
|
||||||
|
if len(rl.hintText) != 0 {
|
||||||
|
// fill in hint text
|
||||||
|
rl.insert(rl.hintText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -707,6 +707,9 @@ func (rl *Instance) escapeSeq(r []rune) {
|
|||||||
rl.renderHelpers()
|
rl.renderHelpers()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rl.insertHintText()
|
||||||
|
|
||||||
if (rl.modeViMode == VimInsert && rl.pos < len(rl.line)) ||
|
if (rl.modeViMode == VimInsert && rl.pos < len(rl.line)) ||
|
||||||
(rl.modeViMode != VimInsert && rl.pos < len(rl.line)-1) {
|
(rl.modeViMode != VimInsert && rl.pos < len(rl.line)-1) {
|
||||||
rl.moveCursorByAdjust(1)
|
rl.moveCursorByAdjust(1)
|
||||||
|
@ -142,6 +142,10 @@ func (rl *Instance) viDeleteByAdjust(adjust int) {
|
|||||||
rl.updateHelpers()
|
rl.updateHelpers()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rl *Instance) DeleteByAmount(adjust int) {
|
||||||
|
rl.viDeleteByAdjust(adjust)
|
||||||
|
}
|
||||||
|
|
||||||
func (rl *Instance) vimDeleteToken(r rune) bool {
|
func (rl *Instance) vimDeleteToken(r rune) bool {
|
||||||
tokens, _, _ := tokeniseSplitSpaces(rl.line, 0)
|
tokens, _, _ := tokeniseSplitSpaces(rl.line, 0)
|
||||||
pos := int(r) - 48 // convert ASCII to integer
|
pos := int(r) - 48 // convert ASCII to integer
|
||||||
|
@ -53,9 +53,7 @@ end)
|
|||||||
*/
|
*/
|
||||||
func runnerModeLoader(rtm *rt.Runtime) *rt.Table {
|
func runnerModeLoader(rtm *rt.Runtime) *rt.Table {
|
||||||
exports := map[string]util.LuaExport{
|
exports := map[string]util.LuaExport{
|
||||||
"sh": {shRunner, 1, false},
|
|
||||||
"lua": {luaRunner, 1, false},
|
"lua": {luaRunner, 1, false},
|
||||||
"setMode": {hlrunnerMode, 1, false},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mod := rt.NewTable()
|
mod := rt.NewTable()
|
||||||
@ -64,44 +62,6 @@ func runnerModeLoader(rtm *rt.Runtime) *rt.Table {
|
|||||||
return mod
|
return mod
|
||||||
}
|
}
|
||||||
|
|
||||||
// #interface runner
|
|
||||||
// setMode(cb)
|
|
||||||
// This is the same as the `hilbish.runnerMode` function.
|
|
||||||
// It takes a callback, which will be used to execute all interactive input.
|
|
||||||
// In normal cases, neither callbacks should be overrided by the user,
|
|
||||||
// as the higher level functions listed below this will handle it.
|
|
||||||
// #param cb function
|
|
||||||
func _runnerMode() {}
|
|
||||||
|
|
||||||
// #interface runner
|
|
||||||
// sh(cmd)
|
|
||||||
// Runs a command in Hilbish's shell script interpreter.
|
|
||||||
// This is the equivalent of using `source`.
|
|
||||||
// #param cmd string
|
|
||||||
func shRunner(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
||||||
if err := c.Check1Arg(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cmd, err := c.StringArg(0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, exitCode, cont, newline, err := execSh(aliases.Resolve(cmd))
|
|
||||||
var luaErr rt.Value = rt.NilValue
|
|
||||||
if err != nil {
|
|
||||||
luaErr = rt.StringValue(err.Error())
|
|
||||||
}
|
|
||||||
runnerRet := rt.NewTable()
|
|
||||||
runnerRet.Set(rt.StringValue("input"), rt.StringValue(cmd))
|
|
||||||
runnerRet.Set(rt.StringValue("exitCode"), rt.IntValue(int64(exitCode)))
|
|
||||||
runnerRet.Set(rt.StringValue("continue"), rt.BoolValue(cont))
|
|
||||||
runnerRet.Set(rt.StringValue("newline"), rt.BoolValue(newline))
|
|
||||||
runnerRet.Set(rt.StringValue("err"), luaErr)
|
|
||||||
|
|
||||||
return c.PushingNext(t.Runtime, rt.TableValue(runnerRet)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// #interface runner
|
// #interface runner
|
||||||
// lua(cmd)
|
// lua(cmd)
|
||||||
// Evaluates `cmd` as Lua input. This is the same as using `dofile`
|
// Evaluates `cmd` as Lua input. This is the same as using `dofile`
|
||||||
|
@ -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.
@ -1,35 +1,32 @@
|
|||||||
package main
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"hilbish/util"
|
|
||||||
|
|
||||||
rt "github.com/arnodel/golua/runtime"
|
rt "github.com/arnodel/golua/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
var sinkMetaKey = rt.StringValue("hshsink")
|
var sinkMetaKey = rt.StringValue("hshsink")
|
||||||
|
|
||||||
// #type
|
// #type
|
||||||
// A sink is a structure that has input and/or output to/from
|
// A sink is a structure that has input and/or output to/from a desination.
|
||||||
// a desination.
|
type Sink struct{
|
||||||
type sink struct{
|
Rw *bufio.ReadWriter
|
||||||
writer *bufio.Writer
|
|
||||||
reader *bufio.Reader
|
|
||||||
file *os.File
|
file *os.File
|
||||||
ud *rt.UserData
|
UserData *rt.UserData
|
||||||
autoFlush bool
|
autoFlush bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupSinkType(rtm *rt.Runtime) {
|
func SinkLoader(rtm *rt.Runtime) *rt.Table {
|
||||||
sinkMeta := rt.NewTable()
|
sinkMeta := rt.NewTable()
|
||||||
|
|
||||||
sinkMethods := rt.NewTable()
|
sinkMethods := rt.NewTable()
|
||||||
sinkFuncs := map[string]util.LuaExport{
|
sinkFuncs := map[string]LuaExport{
|
||||||
"flush": {luaSinkFlush, 1, false},
|
"flush": {luaSinkFlush, 1, false},
|
||||||
"read": {luaSinkRead, 1, false},
|
"read": {luaSinkRead, 1, false},
|
||||||
"readAll": {luaSinkReadAll, 1, false},
|
"readAll": {luaSinkReadAll, 1, false},
|
||||||
@ -37,7 +34,7 @@ func setupSinkType(rtm *rt.Runtime) {
|
|||||||
"write": {luaSinkWrite, 2, false},
|
"write": {luaSinkWrite, 2, false},
|
||||||
"writeln": {luaSinkWriteln, 2, false},
|
"writeln": {luaSinkWriteln, 2, false},
|
||||||
}
|
}
|
||||||
util.SetExports(l, sinkMethods, sinkFuncs)
|
SetExports(rtm, sinkMethods, sinkFuncs)
|
||||||
|
|
||||||
sinkIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
sinkIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
s, _ := sinkArg(c, 0)
|
s, _ := sinkArg(c, 0)
|
||||||
@ -64,10 +61,25 @@ func setupSinkType(rtm *rt.Runtime) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sinkMeta.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(sinkIndex, "__index", 2, false)))
|
sinkMeta.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(sinkIndex, "__index", 2, false)))
|
||||||
l.SetRegistry(sinkMetaKey, rt.TableValue(sinkMeta))
|
rtm.SetRegistry(sinkMetaKey, rt.TableValue(sinkMeta))
|
||||||
|
|
||||||
|
exports := map[string]LuaExport{
|
||||||
|
"new": {luaSinkNew, 0, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
mod := rt.NewTable()
|
||||||
|
SetExports(rtm, mod, exports)
|
||||||
|
|
||||||
|
return mod
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func luaSinkNew(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
snk := NewSink(t.Runtime, new(bytes.Buffer))
|
||||||
|
|
||||||
|
return c.PushingNext1(t.Runtime, rt.UserDataValue(snk.UserData)), nil
|
||||||
|
}
|
||||||
|
|
||||||
// #member
|
// #member
|
||||||
// readAll() -> string
|
// readAll() -> string
|
||||||
// --- @returns string
|
// --- @returns string
|
||||||
@ -82,11 +94,17 @@ func luaSinkReadAll(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.autoFlush {
|
||||||
|
s.Rw.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
lines := []string{}
|
lines := []string{}
|
||||||
for {
|
for {
|
||||||
line, err := s.reader.ReadString('\n')
|
line, err := s.Rw.ReadString('\n')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
|
// We still want to add the data we read
|
||||||
|
lines = append(lines, line)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,7 +131,7 @@ func luaSinkRead(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
str, _ := s.reader.ReadString('\n')
|
str, _ := s.Rw.ReadString('\n')
|
||||||
|
|
||||||
return c.PushingNext1(t.Runtime, rt.StringValue(str)), nil
|
return c.PushingNext1(t.Runtime, rt.StringValue(str)), nil
|
||||||
}
|
}
|
||||||
@ -135,9 +153,9 @@ func luaSinkWrite(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.writer.Write([]byte(data))
|
s.Rw.Write([]byte(data))
|
||||||
if s.autoFlush {
|
if s.autoFlush {
|
||||||
s.writer.Flush()
|
s.Rw.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Next(), nil
|
return c.Next(), nil
|
||||||
@ -160,9 +178,9 @@ func luaSinkWriteln(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.writer.Write([]byte(data + "\n"))
|
s.Rw.Write([]byte(data + "\n"))
|
||||||
if s.autoFlush {
|
if s.autoFlush {
|
||||||
s.writer.Flush()
|
s.Rw.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Next(), nil
|
return c.Next(), nil
|
||||||
@ -181,7 +199,7 @@ func luaSinkFlush(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.writer.Flush()
|
s.Rw.Flush()
|
||||||
|
|
||||||
return c.Next(), nil
|
return c.Next(), nil
|
||||||
}
|
}
|
||||||
@ -212,11 +230,25 @@ func luaSinkAutoFlush(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
return c.Next(), nil
|
return c.Next(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSinkInput(r io.Reader) *sink {
|
func NewSink(rtm *rt.Runtime, Rw io.ReadWriter) *Sink {
|
||||||
s := &sink{
|
s := &Sink{
|
||||||
reader: bufio.NewReader(r),
|
Rw: bufio.NewReadWriter(bufio.NewReader(Rw), bufio.NewWriter(Rw)),
|
||||||
|
autoFlush: true,
|
||||||
}
|
}
|
||||||
s.ud = sinkUserData(s)
|
s.UserData = sinkUserData(rtm, s)
|
||||||
|
|
||||||
|
if f, ok := Rw.(*os.File); ok {
|
||||||
|
s.file = f
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSinkInput(rtm *rt.Runtime, r io.Reader) *Sink {
|
||||||
|
s := &Sink{
|
||||||
|
Rw: bufio.NewReadWriter(bufio.NewReader(r), nil),
|
||||||
|
}
|
||||||
|
s.UserData = sinkUserData(rtm, s)
|
||||||
|
|
||||||
if f, ok := r.(*os.File); ok {
|
if f, ok := r.(*os.File); ok {
|
||||||
s.file = f
|
s.file = f
|
||||||
@ -225,17 +257,17 @@ func newSinkInput(r io.Reader) *sink {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSinkOutput(w io.Writer) *sink {
|
func NewSinkOutput(rtm *rt.Runtime, w io.Writer) *Sink {
|
||||||
s := &sink{
|
s := &Sink{
|
||||||
writer: bufio.NewWriter(w),
|
Rw: bufio.NewReadWriter(nil, bufio.NewWriter(w)),
|
||||||
autoFlush: true,
|
autoFlush: true,
|
||||||
}
|
}
|
||||||
s.ud = sinkUserData(s)
|
s.UserData = sinkUserData(rtm, s)
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func sinkArg(c *rt.GoCont, arg int) (*sink, error) {
|
func sinkArg(c *rt.GoCont, arg int) (*Sink, error) {
|
||||||
s, ok := valueToSink(c.Arg(arg))
|
s, ok := valueToSink(c.Arg(arg))
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("#%d must be a sink", arg + 1)
|
return nil, fmt.Errorf("#%d must be a sink", arg + 1)
|
||||||
@ -244,17 +276,17 @@ func sinkArg(c *rt.GoCont, arg int) (*sink, error) {
|
|||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func valueToSink(val rt.Value) (*sink, bool) {
|
func valueToSink(val rt.Value) (*Sink, bool) {
|
||||||
u, ok := val.TryUserData()
|
u, ok := val.TryUserData()
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
s, ok := u.Value().(*sink)
|
s, ok := u.Value().(*Sink)
|
||||||
return s, ok
|
return s, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func sinkUserData(s *sink) *rt.UserData {
|
func sinkUserData(rtm *rt.Runtime, s *Sink) *rt.UserData {
|
||||||
sinkMeta := l.Registry(sinkMetaKey)
|
sinkMeta := rtm.Registry(sinkMetaKey)
|
||||||
return rt.NewUserData(s, sinkMeta.AsTable())
|
return rt.NewUserData(s, sinkMeta.AsTable())
|
||||||
}
|
}
|
11
util/streams.go
Normal file
11
util/streams.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Streams struct {
|
||||||
|
Stdout io.Writer
|
||||||
|
Stderr io.Writer
|
||||||
|
Stdin io.Reader
|
||||||
|
}
|
137
util/util.go
137
util/util.go
@ -2,14 +2,78 @@ package util
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
rt "github.com/arnodel/golua/runtime"
|
rt "github.com/arnodel/golua/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrNotExec = errors.New("not executable")
|
||||||
|
var ErrNotFound = errors.New("not found")
|
||||||
|
|
||||||
|
type ExecError struct{
|
||||||
|
Typ string
|
||||||
|
Cmd string
|
||||||
|
Code int
|
||||||
|
Colon bool
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ExecError) Error() string {
|
||||||
|
return fmt.Sprintf("%s: %s", e.Cmd, e.Typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ExecError) sprint() error {
|
||||||
|
sep := " "
|
||||||
|
if e.Colon {
|
||||||
|
sep = ": "
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("hilbish: %s%s%s", e.Cmd, sep, e.Err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsExecError(err error) (ExecError, bool) {
|
||||||
|
if exErr, ok := err.(ExecError); ok {
|
||||||
|
return exErr, true
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := strings.Split(err.Error(), ": ")
|
||||||
|
knownTypes := []string{
|
||||||
|
"not-found",
|
||||||
|
"not-executable",
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fields) > 1 && Contains(knownTypes, fields[1]) {
|
||||||
|
var colon bool
|
||||||
|
var e error
|
||||||
|
switch fields[1] {
|
||||||
|
case "not-found":
|
||||||
|
e = ErrNotFound
|
||||||
|
case "not-executable":
|
||||||
|
colon = true
|
||||||
|
e = ErrNotExec
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExecError{
|
||||||
|
Cmd: fields[0],
|
||||||
|
Typ: fields[1],
|
||||||
|
Colon: colon,
|
||||||
|
Err: e,
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExecError{}, false
|
||||||
|
}
|
||||||
|
|
||||||
// SetField sets a field in a table, adding docs for it.
|
// SetField sets a field in a table, adding docs for it.
|
||||||
// It is accessible via the __docProp metatable. It is a table of the names of the fields.
|
// It is accessible via the __docProp metatable. It is a table of the names of the fields.
|
||||||
func SetField(rtm *rt.Runtime, module *rt.Table, field string, value rt.Value) {
|
func SetField(rtm *rt.Runtime, module *rt.Table, field string, value rt.Value) {
|
||||||
@ -36,6 +100,15 @@ func DoString(rtm *rt.Runtime, code string) (rt.Value, error) {
|
|||||||
return ret, err
|
return ret, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MustDoString(rtm *rt.Runtime, code string) rt.Value {
|
||||||
|
val, err := DoString(rtm, code)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
// DoFile runs the contents of the file in the Lua runtime.
|
// DoFile runs the contents of the file in the Lua runtime.
|
||||||
func DoFile(rtm *rt.Runtime, path string) error {
|
func DoFile(rtm *rt.Runtime, path string) error {
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
@ -141,3 +214,67 @@ func AbbrevHome(path string) string {
|
|||||||
|
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func LookPath(file string) (string, error) { // custom lookpath function so we know if a command is found *and* is executable
|
||||||
|
var skip []string
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
skip = []string{"./", "../", "~/", "C:"}
|
||||||
|
} else {
|
||||||
|
skip = []string{"./", "/", "../", "~/"}
|
||||||
|
}
|
||||||
|
for _, s := range skip {
|
||||||
|
if strings.HasPrefix(file, s) {
|
||||||
|
return file, FindExecutable(file, false, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, dir := range filepath.SplitList(os.Getenv("PATH")) {
|
||||||
|
path := filepath.Join(dir, file)
|
||||||
|
err := FindExecutable(path, true, false)
|
||||||
|
if err == ErrNotExec {
|
||||||
|
return "", err
|
||||||
|
} else if err == nil {
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", os.ErrNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
func Contains(s []string, e string) bool {
|
||||||
|
for _, a := range s {
|
||||||
|
if strings.ToLower(a) == strings.ToLower(e) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleExecErr(err error) (exit uint8) {
|
||||||
|
ctx := context.TODO()
|
||||||
|
|
||||||
|
switch x := err.(type) {
|
||||||
|
case *exec.ExitError:
|
||||||
|
// started, but errored - default to 1 if OS
|
||||||
|
// doesn't have exit statuses
|
||||||
|
if status, ok := x.Sys().(syscall.WaitStatus); ok {
|
||||||
|
if status.Signaled() {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
exit = uint8(128 + status.Signal())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
exit = uint8(status.ExitStatus())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
exit = 1
|
||||||
|
return
|
||||||
|
case *exec.Error:
|
||||||
|
// did not start
|
||||||
|
//fmt.Fprintf(hc.Stderr, "%v\n", err)
|
||||||
|
exit = 127
|
||||||
|
default: return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@ -1,17 +1,12 @@
|
|||||||
//go:build unix
|
//go:build unix
|
||||||
|
|
||||||
package main
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var bgProcAttr *syscall.SysProcAttr = &syscall.SysProcAttr{
|
func FindExecutable(path string, inPath, dirs bool) error {
|
||||||
Setpgid: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
func findExecutable(path string, inPath, dirs bool) error {
|
|
||||||
f, err := os.Stat(path)
|
f, err := os.Stat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -25,5 +20,5 @@ func findExecutable(path string, inPath, dirs bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return errNotExec
|
return ErrNotExec
|
||||||
}
|
}
|
@ -1,18 +1,13 @@
|
|||||||
//go:build windows
|
//go:build windows
|
||||||
|
|
||||||
package main
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var bgProcAttr *syscall.SysProcAttr = &syscall.SysProcAttr{
|
func FindExecutable(path string, inPath, dirs bool) error {
|
||||||
CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
|
|
||||||
}
|
|
||||||
|
|
||||||
func findExecutable(path string, inPath, dirs bool) error {
|
|
||||||
nameExt := filepath.Ext(path)
|
nameExt := filepath.Ext(path)
|
||||||
pathExts := filepath.SplitList(os.Getenv("PATHEXT"))
|
pathExts := filepath.SplitList(os.Getenv("PATHEXT"))
|
||||||
if inPath {
|
if inPath {
|
||||||
@ -26,15 +21,15 @@ func findExecutable(path string, inPath, dirs bool) error {
|
|||||||
} else {
|
} else {
|
||||||
_, err := os.Stat(path)
|
_, err := os.Stat(path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if contains(pathExts, nameExt) { return nil }
|
if Contains(pathExts, nameExt) { return nil }
|
||||||
return errNotExec
|
return ErrNotExec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_, err := os.Stat(path)
|
_, err := os.Stat(path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if contains(pathExts, nameExt) { return nil }
|
if Contains(pathExts, nameExt) { return nil }
|
||||||
return errNotExec
|
return ErrNotExec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
4
vars.go
4
vars.go
@ -11,8 +11,8 @@ var (
|
|||||||
|
|
||||||
// Version info
|
// Version info
|
||||||
var (
|
var (
|
||||||
ver = "v2.3.4"
|
ver = "v2.4.0"
|
||||||
releaseName = "Alyssum"
|
releaseName = "Moonflower"
|
||||||
|
|
||||||
gitCommit string
|
gitCommit string
|
||||||
gitBranch string
|
gitBranch string
|
||||||
|
@ -15,7 +15,5 @@ var (
|
|||||||
.. hilbish.userDir.config .. '/hilbish/?/?.lua;'
|
.. hilbish.userDir.config .. '/hilbish/?/?.lua;'
|
||||||
.. hilbish.userDir.config .. '/hilbish/?.lua'`
|
.. hilbish.userDir.config .. '/hilbish/?.lua'`
|
||||||
dataDir = "/usr/local/share/hilbish"
|
dataDir = "/usr/local/share/hilbish"
|
||||||
preloadPath = dataDir + "/nature/init.lua"
|
|
||||||
sampleConfPath = dataDir + "/.hilbishrc.lua" // Path to default/sample config
|
|
||||||
defaultConfDir = getenv("XDG_CONFIG_HOME", "~/.config")
|
defaultConfDir = getenv("XDG_CONFIG_HOME", "~/.config")
|
||||||
)
|
)
|
||||||
|
@ -14,8 +14,6 @@ var (
|
|||||||
.. hilbish.userDir.config .. '/hilbish/?/init.lua;'
|
.. hilbish.userDir.config .. '/hilbish/?/init.lua;'
|
||||||
.. hilbish.userDir.config .. '/hilbish/?/?.lua;'
|
.. hilbish.userDir.config .. '/hilbish/?/?.lua;'
|
||||||
.. hilbish.userDir.config .. '/hilbish/?.lua'`
|
.. hilbish.userDir.config .. '/hilbish/?.lua'`
|
||||||
dataDir = "/usr/local/share/hilbish"
|
dataDir = ""
|
||||||
preloadPath = dataDir + "/nature/init.lua"
|
|
||||||
sampleConfPath = dataDir + "/.hilbishrc.lua" // Path to default/sample config
|
|
||||||
defaultConfDir = ""
|
defaultConfDir = ""
|
||||||
)
|
)
|
||||||
|
@ -10,8 +10,6 @@ var (
|
|||||||
.. hilbish.userDir.config .. '\\Hilbish\\libs\\?\\init.lua;'
|
.. hilbish.userDir.config .. '\\Hilbish\\libs\\?\\init.lua;'
|
||||||
.. hilbish.userDir.config .. '\\Hilbish\\libs\\?\\?.lua;'
|
.. hilbish.userDir.config .. '\\Hilbish\\libs\\?\\?.lua;'
|
||||||
.. hilbish.userDir.config .. '\\Hilbish\\libs\\?.lua;'`
|
.. hilbish.userDir.config .. '\\Hilbish\\libs\\?.lua;'`
|
||||||
dataDir = util.ExpandHome("~\\Appdata\\Roaming\\Hilbish") // ~ and \ gonna cry?
|
dataDir = util.ExpandHome("~\\Appdata\\Roaming\\Hilbish") // ~ and \, gonna cry?
|
||||||
preloadPath = dataDir + "\\nature\\init.lua"
|
|
||||||
sampleConfPath = dataDir + "\\.hilbishrc.lua" // Path to default/sample config
|
|
||||||
defaultConfDir = ""
|
defaultConfDir = ""
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user