chore: update branch

fs-watch
sammyette 2024-12-22 12:46:40 -04:00
commit 398702cafa
Signed by: sammyette
GPG Key ID: 904FC49417B44DCD
53 changed files with 830 additions and 310 deletions

View File

@ -1,8 +1,12 @@
name: Build name: Build
on: on:
- push push:
- pull_request branches:
- master
pull_request:
branches:
- master
jobs: jobs:
build: build:
@ -19,18 +23,18 @@ jobs:
goos: windows goos: windows
steps: steps:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
submodules: true submodules: true
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v2 uses: actions/setup-go@v5
with: with:
go-version: '1.18.8' go-version: '1.22.2'
- name: Download Task - name: Download Task
run: 'sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d' run: 'sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d'
- name: Build - name: Build
run: GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} ./bin/task run: GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} ./bin/task
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v4
if: matrix.goos == 'windows' if: matrix.goos == 'windows'
with: with:
name: hilbish-${{ matrix.goos }}-${{ matrix.goarch }} name: hilbish-${{ matrix.goos }}-${{ matrix.goarch }}
@ -44,7 +48,7 @@ jobs:
libs libs
docs docs
emmyLuaDocs emmyLuaDocs
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v4
if: matrix.goos != 'windows' if: matrix.goos != 'windows'
with: with:
name: hilbish-${{ matrix.goos }}-${{ matrix.goarch }} name: hilbish-${{ matrix.goos }}-${{ matrix.goarch }}

View File

@ -26,7 +26,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL

View File

@ -9,8 +9,8 @@ jobs:
gen: gen:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-go@v2 - uses: actions/setup-go@v5
- name: Run docgen - name: Run docgen
run: go run cmd/docgen/docgen.go run: go run cmd/docgen/docgen.go
- name: Commit new docs - name: Commit new docs

View File

@ -9,7 +9,7 @@ jobs:
create-release: create-release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- uses: taiki-e/create-gh-release-action@v1 - uses: taiki-e/create-gh-release-action@v1
with: with:
title: Hilbish $tag title: Hilbish $tag
@ -30,7 +30,7 @@ jobs:
- goarch: arm64 - goarch: arm64
goos: windows goos: windows
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
submodules: true submodules: true
fetch-depth: 0 fetch-depth: 0

View File

@ -1,27 +1,34 @@
name: Build website name: Build website
on: on:
- push push:
- pull_request branches:
- master
tags:
- v[0-9]+.*
pull_request:
branches:
- master
workflow_dispatch:
jobs: jobs:
deploy: deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
submodules: true submodules: true
fetch-depth: 0 fetch-depth: 0
- name: Setup Hugo - name: Setup Hugo
uses: peaceiris/actions-hugo@v2 uses: peaceiris/actions-hugo@v3
with: with:
hugo-version: 'latest' hugo-version: '0.111.3'
extended: true extended: true
- name: Set branch name - name: Set branch name
id: branch id: branch
run: echo "BRANCH_NAME=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> "$GITHUB_ENV" run: echo "BRANCH_NAME=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/*/}}" >> "$GITHUB_ENV"
- name: Fix base URL - name: Fix base URL
if: env.BRANCH_NAME != 'master' && github.repository_owner == 'Rosettea' if: env.BRANCH_NAME != 'master' && github.repository_owner == 'Rosettea'
@ -32,14 +39,14 @@ jobs:
- name: Deploy - name: Deploy
if: env.BRANCH_NAME == 'master' && github.repository_owner == 'Rosettea' if: env.BRANCH_NAME == 'master' && github.repository_owner == 'Rosettea'
uses: peaceiris/actions-gh-pages@v3 uses: peaceiris/actions-gh-pages@v4
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./website/public publish_dir: ./website/public
keep_files: true keep_files: true
- name: Deploy - name: Deploy
if: env.BRANCH_NAME != 'master' && github.repository_owner == 'Rosettea' if: env.BRANCH_NAME != 'master' && github.repository_owner == 'Rosettea'
uses: peaceiris/actions-gh-pages@v3 uses: peaceiris/actions-gh-pages@v4
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./website/public publish_dir: ./website/public

View File

@ -1,6 +1,85 @@
# 🎀 Changelog # 🎀 Changelog
## Unreleased ## Unreleased
### Added
- Forward/Right arrow key will fill in hint text (#327)
### Fixed
- Skip over file and prevent panic if info cannot be retrieved during file completion (due to permission error or anything else)
## [2.3.3] - 2024-11-04
### Fixed
- Heredocs having issues
### Added
- Adding `\` at the end of input will add a newline and prompt for more input.
## [2.3.2] - 2024-07-30
### Fixed
- Command path searching due to 2.3 changes to the shell interpreter
## [2.3.1] - 2024-07-27
[hehe when you see it release](https://youtu.be/AaAF51Gwbxo?si=rhj2iYuQRkqDa693&t=64)
### Added
- `hilbish.opts.tips` was added to display random tips on start up.
Displayed tips can be modified via the `hilbish.tips` table.
### Fixed
- Fix a minor regression related to the cd command not working with relative paths
- Updated the motd for 2.3
## [2.3.0] - 2024-07-20
### Added
- `commander.registry` function to get all registered commanders.
- `fs.pipe` function to get a pair of connected files (a pipe).
- Added an alternative 2nd parameter to `hilbish.run`, which is `streams`.
`streams` is a table of input and output streams to run the command with.
It uses these 3 keys:
- `input` as standard input for the command
- `out` as standard output
- `err` as standard error
Here is a minimal example of the new usage which allows users to now pipe commands
directly via Lua functions:
```lua
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
})
```
### Changed
- The `-S` flag will be set to Hilbish's absolute path
- Hilbish now builds on any Unix (if any dependencies also work, which should.)
### Fixed
- Fix ansi attributes causing issues with text when cut off in greenhouse
- Fix greenhouse appearing on terminal resize
- Fix crashes when history goes out of bounds when using history navigation
- `exec` command should return if no arg presented
- Commanders can now be cancelled by Ctrl-C and wont hang the shell anymore.
See [issue 198](https://github.com/Rosettea/Hilbish/issues/198).
- Shell interpreter can now preserve its environment and set PWD properly.
## [2.2.3] - 2024-04-27
### Fixed
- Highligher and hinter work now, since it was regressed from the previous minor release.
- `cat` command no longer prints extra newline at end of each file
### Added
- `cat` command now reads files in chunks, allowing for reading large files
## [2.2.2] - 2024-04-16
### Fixed ### Fixed
- Line refresh fixes (less flicker) - Line refresh fixes (less flicker)
- Do more checks for a TTY - Do more checks for a TTY
@ -708,6 +787,11 @@ This input for example will prompt for more input to complete:
First "stable" release of Hilbish. First "stable" release of Hilbish.
[2.3.3]: https://github.com/Rosettea/Hilbish/compare/v2.3.2...v2.3.3
[2.3.2]: https://github.com/Rosettea/Hilbish/compare/v2.3.1...v2.3.2
[2.3.1]: https://github.com/Rosettea/Hilbish/compare/v2.3.0...v2.3.1
[2.3.0]: https://github.com/Rosettea/Hilbish/compare/v2.2.3...v2.3.0
[2.2.3]: https://github.com/Rosettea/Hilbish/compare/v2.2.2...v2.2.3
[2.2.2]: https://github.com/Rosettea/Hilbish/compare/v2.2.1...v2.2.2 [2.2.2]: https://github.com/Rosettea/Hilbish/compare/v2.2.1...v2.2.2
[2.2.1]: https://github.com/Rosettea/Hilbish/compare/v2.2.0...v2.2.1 [2.2.1]: https://github.com/Rosettea/Hilbish/compare/v2.2.0...v2.2.1
[2.2.0]: https://github.com/Rosettea/Hilbish/compare/v2.1.0...v2.2.0 [2.2.0]: https://github.com/Rosettea/Hilbish/compare/v2.1.0...v2.2.0

View File

@ -1,3 +1,6 @@
> [!TIP]
> Check out [Hilbish: Midnight Edition](https://github.com/Rosettea/Hilbish/tree/midnight-edition) if you want to use C Lua, LuaJIT or anything related!
<img src="./assets/hilbish-logo-and-text.png" width=512><br> <img src="./assets/hilbish-logo-and-text.png" width=512><br>
<blockquote> <blockquote>
🌓 The Moon-powered shell! A comfy and extensible shell for Lua fans! 🌺 ✨ 🌓 The Moon-powered shell! A comfy and extensible shell for Lua fans! 🌺 ✨
@ -10,19 +13,23 @@
<br> <br>
Hilbish is an extensible shell designed to be highly customizable. Hilbish is an extensible shell designed to be highly customizable.
It is configured in Lua and provides a good range of features.
It aims to be easy to use for anyone but powerful enough for It is configured in Lua, and provides a good range of features.
those who need it. It aims to be easy to use for anyone, and powerful enough for
those who need more.
The motivation for choosing Lua was that its simpler and better to use The motivation for choosing Lua was that its simpler and better to use
than old shell script. It's fine for basic interactive shell uses, than old shell scripts. It's fine for basic interactive shell uses,
but that's the only place Hilbish has shell script; everything else is Lua and supports [both Lua and Sh interactively](https://rosettea.github.io/Hilbish/docs/features/runner-mode/).
and aims to be infinitely configurable. If something isn't, open an issue!
That's the only place Hilbish can use traditional shell syntax though;
everything else is Lua and aims to be infinitely configurable.
If something isn't, open an issue!
# Screenshots # Screenshots
<div align="center"> <div align="center">
<img src="gallery/tab.png"> <img src="gallery/tab.png">
<img src="gallery/pillprompt.png">
</div> </div>
# Getting Hilbish # Getting Hilbish
@ -36,7 +43,7 @@ on the website for distributed binaries from GitHub or other package repositorie
Otherwise, continue reading for steps on compiling. Otherwise, continue reading for steps on compiling.
## Prerequisites ## Prerequisites
- [Go 1.17+](https://go.dev) - [Go 1.22+](https://go.dev)
- [Task](https://taskfile.dev/installation/) (**Go on the hyperlink here to see Task's install method for your OS.**) - [Task](https://taskfile.dev/installation/) (**Go on the hyperlink here to see Task's install method for your OS.**)
## Build ## Build

129
api.go
View File

@ -16,6 +16,7 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"io"
"os" "os"
"os/exec" "os/exec"
"runtime" "runtime"
@ -27,6 +28,7 @@ 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/maxlandon/readline" "github.com/maxlandon/readline"
"mvdan.cc/sh/v3/interp" "mvdan.cc/sh/v3/interp"
) )
@ -152,12 +154,64 @@ func unsetVimMode() {
util.SetField(l, hshMod, "vimMode", rt.NilValue) util.SetField(l, hshMod, "vimMode", rt.NilValue)
} }
// run(cmd, returnOut) -> exitCode (number), stdout (string), stderr (string) func handleStream(v rt.Value, strms *streams, errStream bool) error {
ud, ok := v.TryUserData()
if !ok {
return errors.New("expected metatable argument")
}
val := ud.Value()
var varstrm io.Writer
if f, ok := val.(*iolib.File); ok {
varstrm = f.Handle()
}
if f, ok := val.(*sink); ok {
varstrm = f.writer
}
if varstrm == nil {
return errors.New("expected either a sink or file")
}
if errStream {
strms.stderr = varstrm
} else {
strms.stdout = varstrm
}
return nil
}
// run(cmd, streams) -> exitCode (number), stdout (string), stderr (string)
// Runs `cmd` in Hilbish's shell script interpreter. // 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 cmd string
// #param returnOut boolean If this is true, the function will return the standard output and error of the command instead of printing it. // #param streams table|boolean
// #returns number, string, string // #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) { 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 { if err := c.Check1Arg(); err != nil {
return nil, err return nil, err
} }
@ -166,20 +220,57 @@ func hlrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return nil, err return nil, err
} }
strms := &streams{}
var terminalOut bool var terminalOut bool
if len(c.Etc()) != 0 { if len(c.Etc()) != 0 {
tout := c.Etc()[0] tout := c.Etc()[0]
termOut, ok := tout.TryBool()
terminalOut = termOut var ok bool
terminalOut, ok = tout.TryBool()
if !ok { if !ok {
return nil, errors.New("bad argument to run (expected boolean, got " + tout.TypeName() + ")") 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 { } else {
terminalOut = true if !terminalOut {
strms = &streams{
stdout: new(bytes.Buffer),
stderr: new(bytes.Buffer),
}
}
}
} }
var exitcode uint8 var exitcode uint8
stdout, stderr, err := execCommand(cmd, terminalOut) stdout, stderr, err := execCommand(cmd, strms)
if code, ok := interp.IsExitStatus(err); ok { if code, ok := interp.IsExitStatus(err); ok {
exitcode = code exitcode = code
@ -187,11 +278,12 @@ func hlrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
exitcode = 1 exitcode = 1
} }
stdoutStr := "" var stdoutStr, stderrStr string
stderrStr := "" if stdoutBuf, ok := stdout.(*bytes.Buffer); ok {
if !terminalOut { stdoutStr = stdoutBuf.String()
stdoutStr = stdout.(*bytes.Buffer).String() }
stderrStr = stderr.(*bytes.Buffer).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 return c.PushingNext(t.Runtime, rt.IntValue(int64(exitcode)), rt.StringValue(stdoutStr), rt.StringValue(stderrStr)), nil
@ -609,7 +701,7 @@ func hlwhich(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
cmd := strings.Split(alias, " ")[0] cmd := strings.Split(alias, " ")[0]
// check for commander // check for commander
if commands[cmd] != nil { if cmds.Commands[cmd] != nil {
// they dont resolve to a path, so just send the cmd // they dont resolve to a path, so just send the cmd
return c.PushingNext1(t.Runtime, rt.StringValue(cmd)), nil return c.PushingNext1(t.Runtime, rt.StringValue(cmd)), nil
} }
@ -712,5 +804,14 @@ func hlhinter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
// #example // #example
// #param line string // #param line string
func hlhighlighter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func hlhighlighter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil if err := c.Check1Arg(); err != nil {
return nil, err
}
line, err := c.StringArg(0)
if err != nil {
return nil, err
}
return c.PushingNext1(t.Runtime, rt.StringValue(line)), nil
} }

View File

@ -488,8 +488,12 @@ func main() {
} }
mdTable.SetContent(i - diff, 0, fmt.Sprintf(`<a href="#%s">%s</a>`, dps.FuncName, dps.FuncSig)) mdTable.SetContent(i - diff, 0, fmt.Sprintf(`<a href="#%s">%s</a>`, dps.FuncName, dps.FuncSig))
if len(dps.Doc) == 0 {
fmt.Printf("WARNING! Function %s on module %s has no documentation!\n", dps.FuncName, modname)
} else {
mdTable.SetContent(i - diff, 1, dps.Doc[0]) mdTable.SetContent(i - diff, 1, dps.Doc[0])
} }
}
f.WriteString(mdTable.String()) f.WriteString(mdTable.String())
f.WriteString("\n") f.WriteString("\n")
} }

View File

@ -128,7 +128,7 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) {
} }
// add lua registered commands to completions // add lua registered commands to completions
for cmdName := range commands { for cmdName := range cmds.Commands {
if strings.HasPrefix(cmdName, query) { if strings.HasPrefix(cmdName, query) {
completions = append(completions, cmdName) completions = append(completions, cmdName)
} }
@ -157,9 +157,12 @@ func matchPath(query string) ([]string, string) {
files, _ := os.ReadDir(path) files, _ := os.ReadDir(path)
for _, entry := range files { for _, entry := range files {
// should we handle errors here?
file, err := entry.Info() file, err := entry.Info()
if err == nil && file.Mode() & os.ModeSymlink != 0 { if err != nil {
continue
}
if file.Mode() & os.ModeSymlink != 0 {
path, err := filepath.EvalSymlinks(filepath.Join(path, file.Name())) path, err := filepath.EvalSymlinks(filepath.Join(path, file.Name()))
if err == nil { if err == nil {
file, err = os.Lstat(path) file, err = os.Lstat(path)

View File

@ -26,8 +26,11 @@ In this example, a command with the name of `hello` is created
that will print `Hello world!` to output. One question you may that will print `Hello world!` to output. One question you may
have is: What is the `sinks` parameter? have is: What is the `sinks` parameter?
The `sinks` parameter is a table with 3 keys: `in`, `out`, The `sinks` parameter is a table with 3 keys: `input`, `out`, and `err`.
and `err`. All of them are a <a href="/Hilbish/docs/api/hilbish/#sink" style="text-decoration: none;">Sink</a>. There is an `in` alias to `input`, but it requires using the string accessor syntax (`sinks['in']`)
as `in` is also a Lua keyword, so `input` is preferred for use.
All of them are a <a href="/Hilbish/docs/api/hilbish/#sink" style="text-decoration: none;">Sink</a>.
In the future, `sinks.in` will be removed.
- `in` is the standard input. - `in` is the standard input.
You may use the read functions on this sink to get input from the user. You may use the read functions on this sink to get input from the user.
@ -41,6 +44,7 @@ This sink is for writing errors, as the name would suggest.
|----|----| |----|----|
|<a href="#deregister">deregister(name)</a>|Removes the named command. Note that this will only remove Commander-registered commands.| |<a href="#deregister">deregister(name)</a>|Removes the named command. Note that this will only remove Commander-registered commands.|
|<a href="#register">register(name, cb)</a>|Adds a new command with the given `name`. When Hilbish has to run a command with a name,| |<a href="#register">register(name, cb)</a>|Adds a new command with the given `name`. When Hilbish has to run a command with a name,|
|<a href="#registry">registry() -> table</a>|Returns all registered commanders. Returns a list of tables with the following keys:|
<hr> <hr>
<div id='deregister'> <div id='deregister'>
@ -91,3 +95,19 @@ end)
``` ```
</div> </div>
<hr>
<div id='registry'>
<h4 class='heading'>
commander.registry() -> table
<a href="#registry" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Returns all registered commanders. Returns a list of tables with the following keys:
- `exec`: The function used to run the commander. Commanders require args and sinks to be passed.
#### Parameters
This function has no parameters.
</div>

View File

@ -23,6 +23,7 @@ library offers more functions and will work on any operating system Hilbish does
|<a href="#glob">glob(pattern) -> matches (table)</a>|Match all files based on the provided `pattern`.| |<a href="#glob">glob(pattern) -> matches (table)</a>|Match all files based on the provided `pattern`.|
|<a href="#join">join(...path) -> string</a>|Takes any list of paths and joins them based on the operating system's path separator.| |<a href="#join">join(...path) -> string</a>|Takes any list of paths and joins them based on the operating system's path separator.|
|<a href="#mkdir">mkdir(name, recursive)</a>|Creates a new directory with the provided `name`.| |<a href="#mkdir">mkdir(name, recursive)</a>|Creates a new directory with the provided `name`.|
|<a href="#pipe">fpipe() -> File, File</a>|Returns a pair of connected files, also known as a pipe.|
|<a href="#readdir">readdir(path) -> table[string]</a>|Returns a list of all files and directories in the provided path.| |<a href="#readdir">readdir(path) -> table[string]</a>|Returns a list of all files and directories in the provided path.|
|<a href="#stat">stat(path) -> {}</a>|Returns the information about a given `path`.| |<a href="#stat">stat(path) -> {}</a>|Returns the information about a given `path`.|
@ -183,6 +184,22 @@ fs.mkdir('./foo/bar', true)
``` ```
</div> </div>
<hr>
<div id='pipe'>
<h4 class='heading'>
fs.fpipe() -> File, File
<a href="#pipe" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Returns a pair of connected files, also known as a pipe.
The type returned is a Lua file, same as returned from `io` functions.
#### Parameters
This function has no parameters.
</div>
<hr> <hr>
<div id='readdir'> <div id='readdir'>
<h4 class='heading'> <h4 class='heading'>

View File

@ -28,7 +28,7 @@ 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, returnOut) -> exitCode (number), stdout (string), stderr (string)</a>|Runs `cmd` in Hilbish's shell script interpreter.| |<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="#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.|
@ -413,21 +413,44 @@ Text to print before input, can be empty.
<hr> <hr>
<div id='run'> <div id='run'>
<h4 class='heading'> <h4 class='heading'>
hilbish.run(cmd, returnOut) -> exitCode (number), stdout (string), stderr (string) hilbish.run(cmd, streams) -> exitCode (number), stdout (string), stderr (string)
<a href="#run" class='heading-link'> <a href="#run" class='heading-link'>
<i class="fas fa-paperclip"></i> <i class="fas fa-paperclip"></i>
</a> </a>
</h4> </h4>
Runs `cmd` in Hilbish's shell script interpreter. 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 #### Parameters
`string` **`cmd`** `string` **`cmd`**
`boolean` **`returnOut`** `table|boolean` **`streams`**
If this is true, the function will return the standard output and error of the command instead of printing it.
#### 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> </div>
<hr> <hr>

View File

@ -21,16 +21,18 @@ A runner is passed the input and has to return a table with these values.
All are not required, only the useful ones the runner needs to return. All are not required, only the useful ones the runner needs to return.
(So if there isn't an error, just omit `err`.) (So if there isn't an error, just omit `err`.)
- `exitCode` (number): A numerical code to indicate the exit result. - `exitCode` (number): Exit code of the command
- `input` (string): The user input. This will be used to add - `input` (string): The text input of the user. This is used by Hilbish to append extra input, in case
to the history. more is requested.
- `err` (string): A string to indicate an interal error for the runner. - `err` (string): A string that represents an error from the runner.
It can be set to a few special values for Hilbish to throw the right hooks and have a better looking message: This should only be set when, for example, there is a syntax error.
It can be set to a few special values for Hilbish to throw the right
`[command]: not-found` will throw a command.not-found hook based on what `[command]` is. hooks and have a better looking message.
- `\<command>: not-found` will throw a `command.not-found` hook
`[command]: not-executable` will throw a command.not-executable hook. based on what `\<command>` is.
- `continue` (boolean): Whether to prompt the user for more input. - `\<command>: not-executable` will throw a `command.not-executable` hook.
- `continue` (boolean): Whether Hilbish should prompt the user for no input
- `newline` (boolean): Whether a newline should be added at the end of `input`.
Here is a simple example of a fennel runner. It falls back to Here is a simple example of a fennel runner. It falls back to
shell script if fennel eval has an error. shell script if fennel eval has an error.

View File

@ -33,19 +33,6 @@ needs to run interactive input. For more detail, see the [API documentation](../
The `hilbish.runner` interface is an alternative to using `hilbish.runnerMode` The `hilbish.runner` interface is an alternative to using `hilbish.runnerMode`
and also provides the shell script and Lua runner functions that Hilbish itself uses. and also provides the shell script and Lua runner functions that Hilbish itself uses.
A runner function is expected to return a table with the following values:
- `exitCode` (number): Exit code of the command
- `input` (string): The text input of the user. This is used by Hilbish to append extra input, in case
more is requested.
- `err` (string): A string that represents an error from the runner.
This should only be set when, for example, there is a syntax error.
It can be set to a few special values for Hilbish to throw the right
hooks and have a better looking message.
- `<command>: not-found` will throw a `command.not-found` hook
based on what `<command>` is.
- `<command>: not-executable` will throw a `command.not-executable` hook.
- `continue` (boolean): Whether Hilbish should prompt the user for no input
## Functions ## Functions
These are the "low level" functions for the `hilbish.runner` interface. These are the "low level" functions for the `hilbish.runner` interface.

View File

@ -11,4 +11,8 @@ function commander.deregister(name) end
--- ---
function commander.register(name, cb) end function commander.register(name, cb) end
--- Returns all registered commanders. Returns a list of tables with the following keys:
--- - `exec`: The function used to run the commander. Commanders require args and sinks to be passed.
function commander.registry() end
return commander return commander

View File

@ -34,6 +34,10 @@ function fs.join(...path) end
--- ---
function fs.mkdir(name, recursive) end function fs.mkdir(name, recursive) end
--- Returns a pair of connected files, also known as a pipe.
--- The type returned is a Lua file, same as returned from `io` functions.
function fs.fpipe() end
--- Returns a list of all files and directories in the provided path. --- Returns a list of all files and directories in the provided path.
function fs.readdir(path) end function fs.readdir(path) end

View File

@ -132,7 +132,13 @@ function hilbish.prompt(str, typ) end
function hilbish.read(prompt) end function hilbish.read(prompt) end
--- Runs `cmd` in Hilbish's shell script interpreter. --- Runs `cmd` in Hilbish's shell script interpreter.
function hilbish.run(cmd, returnOut) end --- 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. --- Sets the execution/runner mode for interactive Hilbish.
--- This determines whether Hilbish wll try to run input as Lua --- This determines whether Hilbish wll try to run input as Lua

157
exec.go
View File

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"os/signal"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
@ -28,6 +29,12 @@ 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.StringValue("hybrid")
type streams struct {
stdout io.Writer
stderr io.Writer
stdin io.Reader
}
type execError struct{ type execError struct{
typ string typ string
cmd string cmd string
@ -91,6 +98,7 @@ func runInput(input string, priv bool) {
var exitCode uint8 var exitCode uint8
var err error var err error
var cont bool var cont bool
var newline bool
// save incase it changes while prompting (For some reason) // save incase it changes while prompting (For some reason)
currentRunner := runnerMode currentRunner := runnerMode
if currentRunner.Type() == rt.StringType { if currentRunner.Type() == rt.StringType {
@ -101,9 +109,9 @@ func runInput(input string, priv bool) {
cmdFinish(0, input, priv) cmdFinish(0, input, priv)
return return
} }
input, exitCode, cont, err = handleSh(input) input, exitCode, cont, newline, err = handleSh(input)
case "hybridRev": case "hybridRev":
_, _, _, err = handleSh(input) _, _, _, _, err = handleSh(input)
if err == nil { if err == nil {
cmdFinish(0, input, priv) cmdFinish(0, input, priv)
return return
@ -112,12 +120,12 @@ func runInput(input string, priv bool) {
case "lua": case "lua":
input, exitCode, err = handleLua(input) input, exitCode, err = handleLua(input)
case "sh": case "sh":
input, exitCode, cont, err = handleSh(input) input, exitCode, cont, newline, err = handleSh(input)
} }
} else { } else {
// can only be a string or function so // can only be a string or function so
var runnerErr error var runnerErr error
input, exitCode, cont, runnerErr, err = runLuaRunner(currentRunner, input) 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) cmdFinish(124, input, priv)
@ -130,15 +138,15 @@ func runInput(input string, priv bool) {
} }
if cont { if cont {
input, err = reprompt(input) input, err = continuePrompt(input, newline)
if err == nil { if err == nil {
goto rerun goto rerun
} else if err == io.EOF { } else if err == io.EOF {
return lr.SetPrompt(fmtPrompt(prompt))
} }
} }
if err != nil { if err != nil && err != io.EOF {
if exErr, ok := isExecError(err); ok { if exErr, ok := isExecError(err); ok {
hooks.Emit("command." + exErr.typ, exErr.cmd) hooks.Emit("command." + exErr.typ, exErr.cmd)
} else { } else {
@ -148,26 +156,28 @@ func runInput(input string, priv bool) {
cmdFinish(exitCode, input, priv) cmdFinish(exitCode, input, priv)
} }
func reprompt(input string) (string, error) { func reprompt(input string, newline bool) (string, error) {
for { for {
in, err := continuePrompt(strings.TrimSuffix(input, "\\")) /*
if strings.HasSuffix(input, "\\") {
input = strings.TrimSuffix(input, "\\") + "\n"
}
*/
in, err := continuePrompt(input, newline)
if err != nil { if err != nil {
lr.SetPrompt(fmtPrompt(prompt)) lr.SetPrompt(fmtPrompt(prompt))
return input, err return input, err
} }
if strings.HasSuffix(in, "\\") {
continue
}
return in, nil return in, nil
} }
} }
func runLuaRunner(runr rt.Value, userInput string) (input string, exitCode uint8, continued bool, runnerErr, err error) { 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) term := rt.NewTerminationWith(l.MainThread().CurrentCont(), 3, false)
err = rt.Call(l.MainThread(), runr, []rt.Value{rt.StringValue(userInput)}, term) err = rt.Call(l.MainThread(), runr, []rt.Value{rt.StringValue(userInput)}, term)
if err != nil { if err != nil {
return "", 124, false, nil, err return "", 124, false, false, nil, err
} }
var runner *rt.Table var runner *rt.Table
@ -195,6 +205,10 @@ func runLuaRunner(runr rt.Value, userInput string) (input string, exitCode uint8
if c, ok := runner.Get(rt.StringValue("continue")).TryBool(); ok { if c, ok := runner.Get(rt.StringValue("continue")).TryBool(); ok {
continued = c continued = c
} }
if nl, ok := runner.Get(rt.StringValue("newline")).TryBool(); ok {
newline = nl
}
return return
} }
@ -225,55 +239,68 @@ 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, runErr error) { 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")) shRunner := hshMod.Get(rt.StringValue("runner")).AsTable().Get(rt.StringValue("sh"))
var err error var err error
input, exitCode, cont, runErr, err = runLuaRunner(shRunner, cmdString) input, exitCode, cont, newline, runErr, err = runLuaRunner(shRunner, cmdString)
if err != nil { if err != nil {
runErr = err runErr = err
} }
return return
} }
func execSh(cmdString string) (string, uint8, bool, error) { func execSh(cmdString string) (input string, exitcode uint8, cont bool, newline bool, e error) {
_, _, err := execCommand(cmdString, true) _, _, err := execCommand(cmdString, nil)
if err != nil { if err != nil {
// If input is incomplete, start multiline prompting // If input is incomplete, start multiline prompting
if syntax.IsIncomplete(err) { if syntax.IsIncomplete(err) {
if !interactive { if !interactive {
return cmdString, 126, false, err return cmdString, 126, false, false, err
} }
return cmdString, 126, true, err
newline := false
if strings.Contains(err.Error(), "unclosed here-document") {
newline = true
}
return cmdString, 126, true, newline, err
} else { } else {
if code, ok := interp.IsExitStatus(err); ok { if code, ok := interp.IsExitStatus(err); ok {
return cmdString, code, false, nil return cmdString, code, false, false, nil
} else { } else {
return cmdString, 126, false, err return cmdString, 126, false, false, err
} }
} }
} }
return cmdString, 0, false, nil return cmdString, 0, false, false, nil
} }
// Run command in sh interpreter // Run command in sh interpreter
func execCommand(cmd string, terminalOut bool) (io.Writer, io.Writer, error) { func execCommand(cmd string, strms *streams) (io.Writer, io.Writer, error) {
file, err := syntax.NewParser().Parse(strings.NewReader(cmd), "") file, err := syntax.NewParser().Parse(strings.NewReader(cmd), "")
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
runner, _ := interp.New() if strms == nil {
strms = &streams{}
var stdout io.Writer
var stderr io.Writer
if terminalOut {
interp.StdIO(os.Stdin, os.Stdout, os.Stderr)(runner)
} else {
stdout = new(bytes.Buffer)
stderr = new(bytes.Buffer)
interp.StdIO(os.Stdin, stdout, stderr)(runner)
} }
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) buf := new(bytes.Buffer)
printer := syntax.NewPrinter() printer := syntax.NewPrinter()
@ -292,11 +319,11 @@ func execCommand(cmd string, terminalOut bool) (io.Writer, io.Writer, error) {
interp.ExecHandler(execHandle(bg))(runner) interp.ExecHandler(execHandle(bg))(runner)
err = runner.Run(context.TODO(), stmt) err = runner.Run(context.TODO(), stmt)
if err != nil { if err != nil {
return stdout, stderr, err return strms.stdout, strms.stderr, err
} }
} }
return stdout, stderr, nil return strms.stdout, strms.stderr, nil
} }
func execHandle(bg bool) interp.ExecHandlerFunc { func execHandle(bg bool) interp.ExecHandlerFunc {
@ -327,17 +354,45 @@ func execHandle(bg bool) interp.ExecHandlerFunc {
} }
hc := interp.HandlerCtx(ctx) hc := interp.HandlerCtx(ctx)
if commands[args[0]] != nil { if cmd := cmds.Commands[args[0]]; cmd != nil {
stdin := newSinkInput(hc.Stdin) stdin := newSinkInput(hc.Stdin)
stdout := newSinkOutput(hc.Stdout) stdout := newSinkOutput(hc.Stdout)
stderr := newSinkOutput(hc.Stderr) stderr := newSinkOutput(hc.Stderr)
sinks := rt.NewTable() sinks := rt.NewTable()
sinks.Set(rt.StringValue("in"), rt.UserDataValue(stdin.ud)) 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("out"), rt.UserDataValue(stdout.ud))
sinks.Set(rt.StringValue("err"), rt.UserDataValue(stderr.ud)) sinks.Set(rt.StringValue("err"), rt.UserDataValue(stderr.ud))
luaexitcode, err := rt.Call1(l.MainThread(), rt.FunctionValue(commands[args[0]]), rt.TableValue(luacmdArgs), rt.TableValue(sinks)) 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 { if err != nil {
fmt.Fprintln(os.Stderr, "Error in command:\n" + err.Error()) fmt.Fprintln(os.Stderr, "Error in command:\n" + err.Error())
return interp.NewExitStatus(1) return interp.NewExitStatus(1)
@ -349,14 +404,14 @@ func execHandle(bg bool) interp.ExecHandlerFunc {
exitcode = uint8(code) exitcode = uint8(code)
} else if luaexitcode != rt.NilValue { } else if luaexitcode != rt.NilValue {
// deregister commander // deregister commander
delete(commands, args[0]) delete(cmds.Commands, args[0])
fmt.Fprintf(os.Stderr, "Commander did not return number for exit code. %s, you're fired.\n", 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) return interp.NewExitStatus(exitcode)
} }
err := lookpath(args[0]) path, err := lookpath(args[0])
if err == errNotExec { if err == errNotExec {
return execError{ return execError{
typ: "not-executable", typ: "not-executable",
@ -377,15 +432,16 @@ func execHandle(bg bool) interp.ExecHandlerFunc {
killTimeout := 2 * time.Second killTimeout := 2 * time.Second
// from here is basically copy-paste of the default exec handler from // from here is basically copy-paste of the default exec handler from
// sh/interp but with our job handling // sh/interp but with our job handling
path, err := interp.LookPathDir(hc.Dir, hc.Env, args[0])
if err != nil {
fmt.Fprintln(hc.Stderr, err)
return interp.NewExitStatus(127)
}
env := hc.Env env := hc.Env
envList := make([]string, 0, 64) envList := make([]string, 0, 64)
env.Each(func(name string, vr expand.Variable) bool { env.Each(func(name string, vr expand.Variable) bool {
if name == "PATH" {
pathEnv := os.Getenv("PATH")
envList = append(envList, "PATH="+pathEnv)
return true
}
if !vr.IsSet() { if !vr.IsSet() {
// If a variable is set globally but unset in the // If a variable is set globally but unset in the
// runner, we need to ensure it's not part of the final // runner, we need to ensure it's not part of the final
@ -403,6 +459,7 @@ func execHandle(bg bool) interp.ExecHandlerFunc {
} }
return true return true
}) })
cmd := exec.Cmd{ cmd := exec.Cmd{
Path: path, Path: path,
Args: args, Args: args,
@ -485,7 +542,7 @@ func handleExecErr(err error) (exit uint8) {
return return
} }
func lookpath(file string) error { // custom lookpath function so we know if a command is found *and* is executable func lookpath(file string) (string, error) { // custom lookpath function so we know if a command is found *and* is executable
var skip []string var skip []string
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
skip = []string{"./", "../", "~/", "C:"} skip = []string{"./", "../", "~/", "C:"}
@ -494,20 +551,20 @@ func lookpath(file string) error { // custom lookpath function so we know if a c
} }
for _, s := range skip { for _, s := range skip {
if strings.HasPrefix(file, s) { if strings.HasPrefix(file, s) {
return findExecutable(file, false, false) return file, findExecutable(file, false, false)
} }
} }
for _, dir := range filepath.SplitList(os.Getenv("PATH")) { for _, dir := range filepath.SplitList(os.Getenv("PATH")) {
path := filepath.Join(dir, file) path := filepath.Join(dir, file)
err := findExecutable(path, true, false) err := findExecutable(path, true, false)
if err == errNotExec { if err == errNotExec {
return err return "", err
} else if err == nil { } else if err == nil {
return nil return path, nil
} }
} }
return os.ErrNotExist return "", os.ErrNotExist
} }
func splitInput(input string) ([]string, string) { func splitInput(input string) ([]string, string) {

View File

@ -1,4 +1,4 @@
// +build linux darwin //go:build unix
package main package main

View File

@ -1,4 +1,4 @@
// +build windows //go:build windows
package main package main

31
go.mod
View File

@ -1,35 +1,38 @@
module hilbish module hilbish
go 1.18 go 1.21
toolchain go1.22.2
require ( require (
github.com/arnodel/golua v0.0.0-20220221163911-dfcf252b6f86 github.com/arnodel/golua v0.0.0-20230215163904-e0b5347eaaa1
github.com/atsushinee/go-markdown-generator v0.0.0-20191121114853-83f9e1f68504 github.com/atsushinee/go-markdown-generator v0.0.0-20191121114853-83f9e1f68504
github.com/blackfireio/osinfo v1.0.3 github.com/blackfireio/osinfo v1.0.5
github.com/maxlandon/readline v0.1.0-beta.0.20211027085530-2b76cabb8036 github.com/maxlandon/readline v1.0.14
github.com/pborman/getopt v1.1.0 github.com/pborman/getopt v1.1.0
github.com/rjeczalik/notify v0.9.3 github.com/rjeczalik/notify v0.9.3
github.com/sahilm/fuzzy v0.1.0 github.com/sahilm/fuzzy v0.1.1
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a golang.org/x/sys v0.22.0
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 golang.org/x/term v0.22.0
mvdan.cc/sh/v3 v3.5.1 mvdan.cc/sh/v3 v3.0.0-00010101000000-000000000000
) )
require ( require (
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
github.com/arnodel/strftime v0.1.6 // indirect github.com/arnodel/strftime v0.1.6 // indirect
github.com/evilsocket/islazy v1.10.6 // indirect github.com/evilsocket/islazy v1.11.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 // indirect github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 // indirect golang.org/x/sync v0.7.0 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.14.0 // indirect
) )
replace mvdan.cc/sh/v3 => github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20220524215627-dfd9a4fa219b replace mvdan.cc/sh/v3 => github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20240815163633-562273e09b73
replace github.com/maxlandon/readline => ./readline replace github.com/maxlandon/readline => ./readline
replace layeh.com/gopher-luar => github.com/layeh/gopher-luar v1.0.10 replace layeh.com/gopher-luar => github.com/layeh/gopher-luar v1.0.10
replace github.com/arnodel/golua => github.com/Rosettea/golua v0.0.0-20221213193027-cbf6d4e4d345 replace github.com/arnodel/golua => github.com/Rosettea/golua v0.0.0-20241104031959-5551ea280f23

87
go.sum
View File

@ -1,72 +1,49 @@
github.com/Rosettea/golua v0.0.0-20221213193027-cbf6d4e4d345 h1:QNYjYDogUSiNUkffbhFSrSCtpZhofeiVYGFN2FI4wSs= github.com/Rosettea/golua v0.0.0-20241104031959-5551ea280f23 h1:mUZnT0gmDEmTkqXsbnDbuJ3CNil7DCOMiCQYgjbKIdI=
github.com/Rosettea/golua v0.0.0-20221213193027-cbf6d4e4d345/go.mod h1:9jzpYPiU2is0HVGCiuIOBSXdergHUW44IEjmuN1UrIE= github.com/Rosettea/golua v0.0.0-20241104031959-5551ea280f23/go.mod h1:9jzpYPiU2is0HVGCiuIOBSXdergHUW44IEjmuN1UrIE=
github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20220524215627-dfd9a4fa219b h1:s5eDMhBk6H1BgipgLub/gv9qeyBaTuiHM0k3h2/9TSE= github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20240815163633-562273e09b73 h1:zTTUJqNnrF2qf4LgygN8Oae5Uxn6ewH0hA8jyTCHfXw=
github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20220524215627-dfd9a4fa219b/go.mod h1:R09vh/04ILvP2Gj8/Z9Jd0Dh0ZIvaucowMEs6abQpWs= github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20240815163633-562273e09b73/go.mod h1:YZalN5H7WNQw3DGij6IvHsEhn5YMW7M2FCwG6gnfKy4=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
github.com/arnodel/edit v0.0.0-20220202110212-dfc8d7a13890/go.mod h1:AcpttpuZBaL9xl8/CX+Em4fBTUbwIkJ66RiAsJlNrBk=
github.com/arnodel/strftime v0.1.6 h1:0hc0pUvk8KhEMXE+htyaOUV42zNcf/csIbjzEFCJqsw= github.com/arnodel/strftime v0.1.6 h1:0hc0pUvk8KhEMXE+htyaOUV42zNcf/csIbjzEFCJqsw=
github.com/arnodel/strftime v0.1.6/go.mod h1:5NbK5XqYK8QpRZpqKNt4OlxLtIB8cotkLk4KTKzJfWs= github.com/arnodel/strftime v0.1.6/go.mod h1:5NbK5XqYK8QpRZpqKNt4OlxLtIB8cotkLk4KTKzJfWs=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/atsushinee/go-markdown-generator v0.0.0-20191121114853-83f9e1f68504 h1:R1/AOzdMbopSliUTTEHvHbyNmnZ3YxY5GvdhTkpPsSY= github.com/atsushinee/go-markdown-generator v0.0.0-20191121114853-83f9e1f68504 h1:R1/AOzdMbopSliUTTEHvHbyNmnZ3YxY5GvdhTkpPsSY=
github.com/atsushinee/go-markdown-generator v0.0.0-20191121114853-83f9e1f68504/go.mod h1:kHBCvAXJIatTX1pw6tLiOspjGc3MhUDRlog9yrCUS+k= github.com/atsushinee/go-markdown-generator v0.0.0-20191121114853-83f9e1f68504/go.mod h1:kHBCvAXJIatTX1pw6tLiOspjGc3MhUDRlog9yrCUS+k=
github.com/blackfireio/osinfo v1.0.3 h1:Yk2t2GTPjBcESv6nDSWZKO87bGMQgO+Hi9OoXPpxX8c= github.com/blackfireio/osinfo v1.0.5 h1:6hlaWzfcpb87gRmznVf7wSdhysGqLRz9V/xuSdCEXrA=
github.com/blackfireio/osinfo v1.0.3/go.mod h1:Pd987poVNmd5Wsx6PRPw4+w7kLlf9iJxoRKPtPAjOrA= github.com/blackfireio/osinfo v1.0.5/go.mod h1:Pd987poVNmd5Wsx6PRPw4+w7kLlf9iJxoRKPtPAjOrA=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
github.com/creack/pty v1.1.15 h1:cKRCLMj3Ddm54bKSpemfQ8AtYFBhAI2MPmdys22fBdc= github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.15/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/evilsocket/islazy v1.11.0 h1:B5w6uuS6ki6iDG+aH/RFeoMb8ijQh/pGabewqp2UeJ0=
github.com/evilsocket/islazy v1.10.6 h1:MFq000a1ByoumoJWlytqg0qon0KlBeUfPsDjY0hK0bo= github.com/evilsocket/islazy v1.11.0/go.mod h1:muYH4x5MB5YRdkxnrOtrXLIBX6LySj1uFIqys94LKdo=
github.com/evilsocket/islazy v1.10.6/go.mod h1:OrwQGYg3DuZvXUfmH+KIZDjwTCbrjy48T24TUpGqVVw= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/gdamore/tcell/v2 v2.4.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 h1:LiZB1h0GIcudcDci2bxbqI6DXV8bF8POAnArqvRrIyw= github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 h1:LiZB1h0GIcudcDci2bxbqI6DXV8bF8POAnArqvRrIyw=
github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0= github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0=
github.com/pborman/getopt v1.1.0 h1:eJ3aFZroQqq0bWmraivjQNt6Dmm5M0h2JcDW38/Azb0= github.com/pborman/getopt v1.1.0 h1:eJ3aFZroQqq0bWmraivjQNt6Dmm5M0h2JcDW38/Azb0=
github.com/pborman/getopt v1.1.0/go.mod h1:FxXoW1Re00sQG/+KIkuSqRL/LwQgSkv7uyac+STFsbk= github.com/pborman/getopt v1.1.0/go.mod h1:FxXoW1Re00sQG/+KIkuSqRL/LwQgSkv7uyac+STFsbk=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY= github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY=
github.com/rjeczalik/notify v0.9.3/go.mod h1:gF3zSOrafR9DQEWSE8TjfI9NkooDxbyT4UgRGKZA0lc= github.com/rjeczalik/notify v0.9.3/go.mod h1:gF3zSOrafR9DQEWSE8TjfI9NkooDxbyT4UgRGKZA0lc=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451 h1:d1PiN4RxzIFXCJTvRkvSkKqwtRAl5ZV4lATKtQI0B7I= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/sys v0.0.0-20210925032602-92d5a993a665/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210916214954-140adaaadfaf/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8=
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
mvdan.cc/editorconfig v0.2.0/go.mod h1:lvnnD3BNdBYkhq+B4uBuFFKatfp02eB6HixDvEz91C0=

View File

@ -17,8 +17,11 @@ In this example, a command with the name of `hello` is created
that will print `Hello world!` to output. One question you may that will print `Hello world!` to output. One question you may
have is: What is the `sinks` parameter? have is: What is the `sinks` parameter?
The `sinks` parameter is a table with 3 keys: `in`, `out`, The `sinks` parameter is a table with 3 keys: `input`, `out`, and `err`.
and `err`. All of them are a @Sink. There is an `in` alias to `input`, but it requires using the string accessor syntax (`sinks['in']`)
as `in` is also a Lua keyword, so `input` is preferred for use.
All of them are a @Sink.
In the future, `sinks.in` will be removed.
- `in` is the standard input. - `in` is the standard input.
You may use the read functions on this sink to get input from the user. You may use the read functions on this sink to get input from the user.
@ -40,11 +43,13 @@ import (
type Commander struct{ type Commander struct{
Events *bait.Bait Events *bait.Bait
Loader packagelib.Loader Loader packagelib.Loader
Commands map[string]*rt.Closure
} }
func New(rtm *rt.Runtime) Commander { func New(rtm *rt.Runtime) *Commander {
c := Commander{ c := &Commander{
Events: bait.New(rtm), Events: bait.New(rtm),
Commands: make(map[string]*rt.Closure),
} }
c.Loader = packagelib.Loader{ c.Loader = packagelib.Loader{
Load: c.loaderFunc, Load: c.loaderFunc,
@ -58,6 +63,7 @@ func (c *Commander) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
exports := map[string]util.LuaExport{ exports := map[string]util.LuaExport{
"register": util.LuaExport{c.cregister, 2, false}, "register": util.LuaExport{c.cregister, 2, false},
"deregister": util.LuaExport{c.cderegister, 1, false}, "deregister": util.LuaExport{c.cderegister, 1, false},
"registry": util.LuaExport{c.cregistry, 0, false},
} }
mod := rt.NewTable() mod := rt.NewTable()
util.SetExports(rtm, mod, exports) util.SetExports(rtm, mod, exports)
@ -88,7 +94,7 @@ func (c *Commander) cregister(t *rt.Thread, ct *rt.GoCont) (rt.Cont, error) {
return nil, err return nil, err
} }
c.Events.Emit("commandRegister", cmdName, cmd) c.Commands[cmdName] = cmd
return ct.Next(), err return ct.Next(), err
} }
@ -105,7 +111,23 @@ func (c *Commander) cderegister(t *rt.Thread, ct *rt.GoCont) (rt.Cont, error) {
return nil, err return nil, err
} }
c.Events.Emit("commandDeregister", cmdName) delete(c.Commands, cmdName)
return ct.Next(), err return ct.Next(), err
} }
// registry() -> table
// Returns all registered commanders. Returns a list of tables with the following keys:
// - `exec`: The function used to run the commander. Commanders require args and sinks to be passed.
// #returns table
func (c *Commander) cregistry(t *rt.Thread, ct *rt.GoCont) (rt.Cont, error) {
registryLua := rt.NewTable()
for cmdName, cmd := range c.Commands {
cmdTbl := rt.NewTable()
cmdTbl.Set(rt.StringValue("exec"), rt.FunctionValue(cmd))
registryLua.Set(rt.StringValue(cmdName), rt.TableValue(cmdTbl))
}
return ct.PushingNext1(t.Runtime, rt.TableValue(registryLua)), nil
}

View File

@ -18,17 +18,29 @@ 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"
"mvdan.cc/sh/v3/interp"
) )
var rtmm *rt.Runtime
var watcherMetaKey = rt.StringValue("hshwatcher") var watcherMetaKey = rt.StringValue("hshwatcher")
var Loader = packagelib.Loader{ type fs struct{
Load: loaderFunc, runner *interp.Runner
Loader packagelib.Loader
}
func New(runner *interp.Runner) *fs {
f := &fs{
runner: runner,
}
f.Loader = packagelib.Loader{
Load: f.loaderFunc,
Name: "fs", Name: "fs",
} }
func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) { return f
rtmm = rtm }
func (f *fs) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
watcherMethods := rt.NewTable() watcherMethods := rt.NewTable()
watcherFuncs := map[string]util.LuaExport{ watcherFuncs := map[string]util.LuaExport{
"start": {watcherStart, 1, false}, "start": {watcherStart, 1, false},
@ -54,15 +66,16 @@ func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
rtm.SetRegistry(watcherMetaKey, rt.TableValue(watcherMeta)) rtm.SetRegistry(watcherMetaKey, rt.TableValue(watcherMeta))
exports := map[string]util.LuaExport{ exports := map[string]util.LuaExport{
"cd": util.LuaExport{fcd, 1, false}, "cd": util.LuaExport{f.fcd, 1, false},
"mkdir": util.LuaExport{fmkdir, 2, false}, "mkdir": util.LuaExport{f.fmkdir, 2, false},
"stat": util.LuaExport{fstat, 1, false}, "stat": util.LuaExport{f.fstat, 1, false},
"readdir": util.LuaExport{freaddir, 1, false}, "readdir": util.LuaExport{f.freaddir, 1, false},
"abs": util.LuaExport{fabs, 1, false}, "abs": util.LuaExport{f.fabs, 1, false},
"basename": util.LuaExport{fbasename, 1, false}, "basename": util.LuaExport{f.fbasename, 1, false},
"dir": util.LuaExport{fdir, 1, false}, "dir": util.LuaExport{f.fdir, 1, false},
"glob": util.LuaExport{fglob, 1, false}, "glob": util.LuaExport{f.fglob, 1, false},
"join": util.LuaExport{fjoin, 0, true}, "join": util.LuaExport{f.fjoin, 0, true},
"pipe": util.LuaExport{f.fpipe, 0, false},
"watch": util.LuaExport{fwatch, 2, false}, "watch": util.LuaExport{fwatch, 2, false},
} }
mod := rt.NewTable() mod := rt.NewTable()
@ -78,7 +91,7 @@ func 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 fabs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func (f *fs) 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
@ -98,7 +111,7 @@ func 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 fbasename(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func (f *fs) 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
} }
@ -113,7 +126,7 @@ func 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 fcd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func (f *fs) 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
} }
@ -123,10 +136,12 @@ func 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
} }
@ -136,7 +151,7 @@ func 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 fdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func (f *fs) 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
} }
@ -167,7 +182,7 @@ print(matches)
-- -> {'init.lua', 'code.lua'} -- -> {'init.lua', 'code.lua'}
#example #example
*/ */
func fglob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func (f *fs) 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
} }
@ -201,7 +216,7 @@ print(fs.join(hilbish.userDir.config, 'hilbish'))
-- -> '/home/user/.config/hilbish' on Linux -- -> '/home/user/.config/hilbish' on Linux
#example #example
*/ */
func fjoin(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func (f *fs) 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 {
@ -228,7 +243,7 @@ func fjoin(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
fs.mkdir('./foo/bar', true) fs.mkdir('./foo/bar', true)
#example #example
*/ */
func fmkdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func (f *fs) 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
} }
@ -254,11 +269,27 @@ func fmkdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), err return c.Next(), err
} }
// fpipe() -> File, File
// Returns a pair of connected files, also known as a pipe.
// The type returned is a Lua file, same as returned from `io` functions.
// #returns File
// #returns File
func (f *fs) fpipe(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
rf, wf, err := os.Pipe()
if err != nil {
return nil, err
}
rfLua := iolib.NewFile(rf, 0)
wfLua := iolib.NewFile(wf, 0)
return c.PushingNext(t.Runtime, rfLua.Value(t.Runtime), wfLua.Value(t.Runtime)), nil
}
// readdir(path) -> table[string] // readdir(path) -> table[string]
// 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 freaddir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func (f *fs) 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
} }
@ -306,7 +337,7 @@ Would print the following:
]]-- ]]--
#example #example
*/ */
func fstat(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func (f *fs) 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
} }
@ -350,7 +381,7 @@ func fwatch(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return nil, err return nil, err
} }
dw := newWatcher(dir, watcher) dw := newWatcher(dir, watcher, t.Runtime)
return c.PushingNext1(t.Runtime, rt.UserDataValue(dw.ud)), nil return c.PushingNext1(t.Runtime, rt.UserDataValue(dw.ud)), nil
} }

View File

@ -16,6 +16,7 @@ type watcher struct{
started bool started bool
ud *rt.UserData ud *rt.UserData
notifyChan chan notify.EventInfo notifyChan chan notify.EventInfo
rtm *rt.Runtime
} }
func (w *watcher) start() { func (w *watcher) start() {
@ -32,7 +33,7 @@ func (w *watcher) start() {
ev := notif.Event().String() ev := notif.Event().String()
path := notif.Path() path := notif.Path()
_, err := rt.Call1(rtmm.MainThread(), rt.FunctionValue(w.callback), rt.StringValue(ev), rt.StringValue(path)) _, err := rt.Call1(w.rtm.MainThread(), rt.FunctionValue(w.callback), rt.StringValue(ev), rt.StringValue(path))
if err != nil { if err != nil {
// TODO: throw error // TODO: throw error
} }
@ -73,9 +74,10 @@ func watcherStop(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil return c.Next(), nil
} }
func newWatcher(path string, callback *rt.Closure) *watcher { func newWatcher(path string, callback *rt.Closure, rtm *rt.Runtime) *watcher {
pw := &watcher{ pw := &watcher{
path: path, path: path,
rtm: rtm,
callback: callback, callback: callback,
} }
pw.ud = watcherUserData(pw) pw.ud = watcherUserData(pw)
@ -104,6 +106,6 @@ func valueToWatcher(val rt.Value) (*watcher, bool) {
} }
func watcherUserData(j *watcher) *rt.UserData { func watcherUserData(j *watcher) *rt.UserData {
watcherMeta := rtmm.Registry(watcherMetaKey) watcherMeta := j.rtm.Registry(watcherMetaKey)
return rt.NewUserData(j, watcherMeta.AsTable()) return rt.NewUserData(j, watcherMeta.AsTable())
} }

View File

@ -1,4 +1,4 @@
// +build windows //go:build windows
package main package main

View File

@ -1,4 +1,4 @@
// +build darwin linux //go:build unix
package main package main

View File

@ -1,4 +1,4 @@
// +build windows //go:build windows
package main package main

17
lua.go
View File

@ -30,22 +30,11 @@ func luaInit() {
util.DoString(l, "hilbish = require 'hilbish'") util.DoString(l, "hilbish = require 'hilbish'")
// Add fs and terminal module module to Lua // 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)
cmds := commander.New(l) cmds = commander.New(l)
// When a command from Lua is added, register it for use
cmds.Events.On("commandRegister", func(args ...interface{}) {
cmdName := args[0].(string)
cmd := args[1].(*rt.Closure)
commands[cmdName] = cmd
})
cmds.Events.On("commandDeregister", func(args ...interface{}) {
cmdName := args[0].(string)
delete(commands, cmdName)
})
lib.LoadLibs(l, cmds.Loader) lib.LoadLibs(l, cmds.Loader)
hooks = bait.New(l) hooks = bait.New(l)

32
main.go
View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"os/exec"
"os/user" "os/user"
"path/filepath" "path/filepath"
"runtime" "runtime"
@ -14,18 +15,19 @@ import (
"hilbish/util" "hilbish/util"
"hilbish/golibs/bait" "hilbish/golibs/bait"
"hilbish/golibs/commander"
rt "github.com/arnodel/golua/runtime" rt "github.com/arnodel/golua/runtime"
"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 (
l *rt.Runtime l *rt.Runtime
lr *lineReader lr *lineReader
commands = map[string]*rt.Closure{}
luaCompletions = map[string]*rt.Closure{} luaCompletions = map[string]*rt.Closure{}
confDir string confDir string
@ -33,11 +35,14 @@ var (
curuser *user.User curuser *user.User
hooks *bait.Bait hooks *bait.Bait
cmds *commander.Commander
defaultConfPath string defaultConfPath string
defaultHistPath string defaultHistPath string
runner *interp.Runner
) )
func main() { func main() {
runner, _ = interp.New()
curuser, _ = user.Current() curuser, _ = user.Current()
homedir := curuser.HomeDir homedir := curuser.HomeDir
confDir, _ = os.UserConfigDir() confDir, _ = os.UserConfigDir()
@ -114,7 +119,13 @@ func main() {
// Set $SHELL if the user wants to // Set $SHELL if the user wants to
if *setshflag { if *setshflag {
os.Setenv("SHELL", os.Args[0]) os.Setenv("SHELL", "hilbish")
path, err := exec.LookPath("hilbish")
if err == nil {
os.Setenv("SHELL", path)
}
} }
lr = newLineReader("", false) lr = newLineReader("", false)
@ -212,8 +223,9 @@ input:
} }
if strings.HasSuffix(input, "\\") { if strings.HasSuffix(input, "\\") {
print("\n")
for { for {
input, err = continuePrompt(input) input, err = continuePrompt(strings.TrimSuffix(input, "\\") + "\n", false)
if err != nil { if err != nil {
running = true running = true
lr.SetPrompt(fmtPrompt(prompt)) lr.SetPrompt(fmtPrompt(prompt))
@ -237,16 +249,24 @@ input:
exit(0) exit(0)
} }
func continuePrompt(prev string) (string, error) { func continuePrompt(prev string, newline bool) (string, error) {
hooks.Emit("multiline", nil) hooks.Emit("multiline", nil)
lr.SetPrompt(multilinePrompt) lr.SetPrompt(multilinePrompt)
cont, err := lr.Read() cont, err := lr.Read()
if err != nil { if err != nil {
return "", err return "", err
} }
cont = strings.TrimSpace(cont)
return prev + strings.TrimSuffix(cont, "\n"), nil if newline {
cont = "\n" + cont
}
if strings.HasSuffix(cont, "\\") {
cont = strings.TrimSuffix(cont, "\\") + "\n"
}
return prev + cont, nil
} }
// This semi cursed function formats our prompt (obviously) // This semi cursed function formats our prompt (obviously)

View File

@ -9,6 +9,8 @@ commander.register('cat', function(args, sinks)
usage: cat [file]...]] usage: cat [file]...]]
end end
local chunkSize = 2^13 -- 8K buffer size
for _, fName in ipairs(args) do for _, fName in ipairs(args) do
local f = io.open(fName) local f = io.open(fName)
if f == nil then if f == nil then
@ -17,7 +19,11 @@ usage: cat [file]...]]
goto continue goto continue
end end
sinks.out:writeln(f:read '*a') while true do
local block = f:read(chunkSize)
if not block then break end
sinks.out:write(block)
end
::continue:: ::continue::
end end
io.flush() io.flush()

View File

@ -1,5 +1,8 @@
local commander = require 'commander' local commander = require 'commander'
commander.register('exec', function(args) commander.register('exec', function(args)
if #args == 0 then
return
end
hilbish.exec(args[1]) hilbish.exec(args[1])
end) end)

View File

@ -17,8 +17,8 @@ function M.renderCodeBlock(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] = lunacolors.format('{greyBg}' .. ' ' .. M.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'

View File

@ -61,17 +61,24 @@ function Greenhouse:updateCurrentPage(text)
page:setText(text) page:setText(text)
end end
local ansiPatters = {
'\x1b%[%d+;%d+;%d+;%d+;%d+%w',
'\x1b%[%d+;%d+;%d+;%d+%w',
'\x1b%[%d+;%d+;%d+%w',
'\x1b%[%d+;%d+%w',
'\x1b%[%d+%w'
}
function Greenhouse:sub(str, offset, limit) function Greenhouse:sub(str, offset, limit)
local overhead = 0 local overhead = 0
local function addOverhead(s) local function addOverhead(s)
overhead = overhead + string.len(s) overhead = overhead + string.len(s)
end end
local s = str:gsub('\x1b%[%d+;%d+;%d+;%d+;%d+%w', addOverhead) local s = str
:gsub('\x1b%[%d+;%d+;%d+;%d+%w', addOverhead) for _, pat in ipairs(ansiPatters) do
:gsub('\x1b%[%d+;%d+;%d+%w',addOverhead) s = s:gsub(pat, addOverhead)
:gsub('\x1b%[%d+;%d+%w', addOverhead) end
:gsub('\x1b%[%d+%w', addOverhead)
return s:sub(offset, utf8.offset(str, limit + overhead) or limit + overhead) return s:sub(offset, utf8.offset(str, limit + overhead) or limit + overhead)
--return s:sub(offset, limit + overhead) --return s:sub(offset, limit + overhead)
@ -94,14 +101,40 @@ function Greenhouse:draw()
self.sink:write(ansikit.getCSI(2, 'J')) self.sink:write(ansikit.getCSI(2, 'J'))
local writer = self.sink.writeln local writer = self.sink.writeln
self.attributes = {}
for i = offset, offset + self.region.height - 1 do for i = offset, offset + self.region.height - 1 do
local resetEnd = false
if i > #lines then break end if i > #lines then break end
if i == offset + self.region.height - 1 then writer = self.sink.write end if i == offset + self.region.height - 1 then writer = self.sink.write end
self.sink:write(ansikit.getCSI(self.start + i - offset .. ';1', 'H')) self.sink:write(ansikit.getCSI(self.start + i - offset .. ';1', 'H'))
local line = lines[i]:gsub('{separator}', function() return self.separator:rep(self.region.width - 1) end) local line = lines[i]:gsub('{separator}', function() return self.separator:rep(self.region.width - 1) end)
writer(self.sink, self:sub(line:gsub('\t', ' '), self.horizOffset, self.region.width)) for _, pat in ipairs(ansiPatters) do
line:gsub(pat, function(s)
if s == lunacolors.formatColors.reset then
self.attributes = {}
resetEnd = true
else
--resetEnd = false
--table.insert(self.attributes, s)
end
end)
end
--[[
if #self.attributes ~= 0 then
for _, attr in ipairs(self.attributes) do
--writer(self.sink, attr)
end
end
]]--
self.sink:write(lunacolors.formatColors.reset)
writer(self.sink, self:sub(line:gsub('\t', ' '), self.horizOffset, self.region.width + self.horizOffset))
if resetEnd then
self.sink:write(lunacolors.formatColors.reset)
end
end end
writer(self.sink, '\27[0m') writer(self.sink, '\27[0m')
self:render() self:render()
@ -271,6 +304,15 @@ end
function Greenhouse:input(char) function Greenhouse:input(char)
end end
local function read()
terminal.saveState()
terminal.setRaw()
local c = hilbish.editor.readChar()
terminal.restoreState()
return c
end
function Greenhouse:initUi() function Greenhouse:initUi()
local ansikit = require 'ansikit' local ansikit = require 'ansikit'
local bait = require 'bait' local bait = require 'bait'
@ -280,14 +322,17 @@ function Greenhouse:initUi()
local Page = require 'nature.greenhouse.page' local Page = require 'nature.greenhouse.page'
local done = false local done = false
bait.catch('signal.sigint', function() local function sigint()
ansikit.clear() ansikit.clear()
done = true done = true
end) end
bait.catch('signal.resize', function() local function resize()
self:update() self:update()
end) end
bait.catch('signal.sigint', sigint)
bait.catch('signal.resize', resize)
ansikit.screenAlt() ansikit.screenAlt()
ansikit.clear(true) ansikit.clear(true)
@ -311,15 +356,10 @@ function Greenhouse:initUi()
ansikit.showCursor() ansikit.showCursor()
ansikit.screenMain() ansikit.screenMain()
end
function read() self = nil
terminal.saveState() bait.release('signal.sigint', sigint)
terminal.setRaw() bait.release('signal.resize', resize)
local c = hilbish.editor.readChar()
terminal.restoreState()
return c
end end
return Greenhouse return Greenhouse

View File

@ -14,7 +14,8 @@ The nice lil shell for {blue}Lua{reset} fanatics!
motd = true, motd = true,
fuzzy = false, fuzzy = false,
notifyJobFinish = true, notifyJobFinish = true,
crimmas = true crimmas = true,
tips = true
} }
for optsName, default in pairs(defaultOpts) do for optsName, default in pairs(defaultOpts) do

View File

@ -2,8 +2,9 @@ local bait = require 'bait'
local lunacolors = require 'lunacolors' local lunacolors = require 'lunacolors'
hilbish.motd = [[ hilbish.motd = [[
Finally at {red}v2.2!{reset} So much {green}documentation improvements{reset} Wait ... {magenta}2.3{reset} is basically the same as {red}2.2?{reset}
and 1 single fix for Windows! {blue}.. and a feature they can't use.{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()

View File

@ -0,0 +1,35 @@
local bait = require 'bait'
local lunacolors = require 'lunacolors'
local postamble = [[
{yellow}These tips can be disabled with {reset}{invert} hilbish.opts.tips = false {reset}
]]
hilbish.tips = {
'Join the discord and say hi! {blue}https://discord.gg/3PDdcQz{reset}',
'{green}hilbish.alias{reset} interface manages shell aliases. See more detail by running {blue}doc api hilbish.alias.',
'{green}hilbish.appendPath(\'path\'){reset} -> Appends the provided dir to the command path ($PATH)',
'{green}hilbish.completions{reset} -> Used to control suggestions when tab completing.',
'{green}hilbish.message{reset} -> Simple notification system which can be used by other plugins and parts of the shell to notify the user of various actions.',
[[
{green}hilbish.opts{reset} -> Simple toggle or value options a user can set.
You may disable the startup greeting by {invert}hilbish.opts.greeting = false{reset}
]],
[[
{green}hilbish.runner{reset} -> The runner interface contains functions to
manage how Hilbish interprets interactive input. The default runners can run
shell script and Lua code!
]],
[[
Add Lua-written commands with the commander module!
Check the command {blue}doc api commander{reset} or the web docs:
https://rosettea.github.io/Hilbish/docs/api/commander/
]]
}
bait.catch('hilbish.init', function()
if hilbish.interactive and hilbish.opts.tips then
local idx = math.random(1, #hilbish.tips)
print(lunacolors.format('{yellow}🛈 Tip:{reset} ' .. hilbish.tips[idx] .. '\n' .. postamble))
end
end)

View File

@ -1,4 +1,4 @@
// +build pprof //go:build pprof
package main package main

View File

@ -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)
}
}

View File

@ -128,15 +128,19 @@ func (rl *Instance) walkHistory(i int) {
} }
rl.histOffset += i rl.histOffset += i
historyLen := history.Len()
if rl.histOffset == 0 { if rl.histOffset == 0 {
rl.line = []rune(rl.lineBuf) rl.line = []rune(rl.lineBuf)
rl.pos = len(rl.lineBuf) rl.pos = len(rl.lineBuf)
} else if rl.histOffset <= -1 { } else if rl.histOffset <= -1 {
rl.histOffset = 0 rl.histOffset = 0
} else if rl.histOffset > historyLen {
// TODO: should this wrap around?s
rl.histOffset = 0
} else { } else {
dedup = true dedup = true
old = string(rl.line) old = string(rl.line)
new, err = history.GetLine(history.Len() - rl.histOffset) new, err = history.GetLine(historyLen - rl.histOffset)
if err != nil { if err != nil {
rl.resetHelpers() rl.resetHelpers()
print("\r\n" + err.Error() + "\r\n") print("\r\n" + err.Error() + "\r\n")

View File

@ -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)

View File

@ -29,7 +29,7 @@ func (rl *Instance) updateTabFind(r []rune) {
rl.search = string(rl.tfLine) rl.search = string(rl.tfLine)
// We update and print // We update and print
rl.clearHelpers() //rl.clearHelpers()
rl.getTabCompletion() rl.getTabCompletion()
rl.renderHelpers() rl.renderHelpers()
} }

View File

@ -121,7 +121,7 @@ func (rl *Instance) clearHelpers() {
moveCursorForwards(rl.fullX) moveCursorForwards(rl.fullX)
// Clear everything below // Clear everything below
//print(seqClearScreenBelow) print(seqClearScreenBelow)
// Go back to current cursor position // Go back to current cursor position
moveCursorBackwards(GetTermWidth()) moveCursorBackwards(GetTermWidth())

13
rl.go
View File

@ -70,11 +70,8 @@ func newLineReader(prompt string, noHist bool) *lineReader {
hooks.Emit("hilbish.vimAction", actionStr, args) hooks.Emit("hilbish.vimAction", actionStr, args)
} }
rl.HintText = func(line []rune, pos int) []rune { rl.HintText = func(line []rune, pos int) []rune {
if hinter == nil { hinter := hshMod.Get(rt.StringValue("hinter"))
return []rune{} retVal, err := rt.Call1(l.MainThread(), hinter,
}
retVal, err := rt.Call1(l.MainThread(), rt.FunctionValue(hinter),
rt.StringValue(string(line)), rt.IntValue(int64(pos))) rt.StringValue(string(line)), rt.IntValue(int64(pos)))
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
@ -89,10 +86,8 @@ func newLineReader(prompt string, noHist bool) *lineReader {
return []rune(hintText) return []rune(hintText)
} }
rl.SyntaxHighlighter = func(line []rune) string { rl.SyntaxHighlighter = func(line []rune) string {
if highlighter == nil { highlighter := hshMod.Get(rt.StringValue("highlighter"))
return string(line) retVal, err := rt.Call1(l.MainThread(), highlighter,
}
retVal, err := rt.Call1(l.MainThread(), rt.FunctionValue(highlighter),
rt.StringValue(string(line))) rt.StringValue(string(line)))
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)

View File

@ -21,16 +21,18 @@ A runner is passed the input and has to return a table with these values.
All are not required, only the useful ones the runner needs to return. All are not required, only the useful ones the runner needs to return.
(So if there isn't an error, just omit `err`.) (So if there isn't an error, just omit `err`.)
- `exitCode` (number): A numerical code to indicate the exit result. - `exitCode` (number): Exit code of the command
- `input` (string): The user input. This will be used to add - `input` (string): The text input of the user. This is used by Hilbish to append extra input, in case
to the history. more is requested.
- `err` (string): A string to indicate an interal error for the runner. - `err` (string): A string that represents an error from the runner.
It can be set to a few special values for Hilbish to throw the right hooks and have a better looking message: This should only be set when, for example, there is a syntax error.
It can be set to a few special values for Hilbish to throw the right
`[command]: not-found` will throw a command.not-found hook based on what `[command]` is. hooks and have a better looking message.
- `<command>: not-found` will throw a `command.not-found` hook
`[command]: not-executable` will throw a command.not-executable hook. based on what `<command>` is.
- `continue` (boolean): Whether to prompt the user for more input. - `<command>: not-executable` will throw a `command.not-executable` hook.
- `continue` (boolean): Whether Hilbish should prompt the user for no input
- `newline` (boolean): Whether a newline should be added at the end of `input`.
Here is a simple example of a fennel runner. It falls back to Here is a simple example of a fennel runner. It falls back to
shell script if fennel eval has an error. shell script if fennel eval has an error.
@ -85,7 +87,7 @@ func shRunner(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return nil, err return nil, err
} }
_, exitCode, cont, err := execSh(aliases.Resolve(cmd)) _, exitCode, cont, newline, err := execSh(aliases.Resolve(cmd))
var luaErr rt.Value = rt.NilValue var luaErr rt.Value = rt.NilValue
if err != nil { if err != nil {
luaErr = rt.StringValue(err.Error()) luaErr = rt.StringValue(err.Error())
@ -94,6 +96,7 @@ func shRunner(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
runnerRet.Set(rt.StringValue("input"), rt.StringValue(cmd)) runnerRet.Set(rt.StringValue("input"), rt.StringValue(cmd))
runnerRet.Set(rt.StringValue("exitCode"), rt.IntValue(int64(exitCode))) runnerRet.Set(rt.StringValue("exitCode"), rt.IntValue(int64(exitCode)))
runnerRet.Set(rt.StringValue("continue"), rt.BoolValue(cont)) 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("err"), luaErr)
return c.PushingNext(t.Runtime, rt.TableValue(runnerRet)), nil return c.PushingNext(t.Runtime, rt.TableValue(runnerRet)), nil

View File

@ -1,4 +1,4 @@
// +build darwin linux //go:build unix
package main package main

View File

@ -1,4 +1,4 @@
// +build windows //go:build windows
package main package main

View File

@ -11,8 +11,8 @@ var (
// Version info // Version info
var ( var (
ver = "v2.2.1" ver = "v2.3.3"
releaseName = "Poppy" releaseName = "Alyssum"
gitCommit string gitCommit string
gitBranch string gitBranch string

View File

@ -1,4 +1,4 @@
// +build darwin //go:build darwin
package main package main

View File

@ -1,4 +1,4 @@
// +build linux //go:build unix && !darwin
package main package main

View File

@ -1,4 +1,4 @@
// +build windows //go:build windows
package main package main

View File

@ -0,0 +1,48 @@
---
title: "v2.3 Release"
date: 2024-07-20T10:05:17-04:00
draft: false
---
> The release with full changelogs and prebuilt binaries can be
seen at the [v2.3.0](https://github.com/Rosettea/Hilbish/releases/tag/v2.3.0)
tag.
Hilbish v2.3 has now been released! This is small feature and bug fix release
which took a while to cme ut since I took a long break from programming in general.
The next release will be great, so stay tuned for that.
# Features
## Pipes (via Lua)
Commands can now be piped to each other via the Lua API with the `hilbish.run`
function and an `fs.pipe`.
Here is a minimal example of the new usage which allows users to now pipe commands
directly via Lua functions:
```lua
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
})
```
This also means it's easier to make commands output to any stream output,
including in commanders.
# Bug Fixes
- Commanders can now be cancelled with Ctrl-C, which means if they froze for some reason
they can now be exited.
- The shell script interpreter now keeps its environment, and this also fixes the
current working directory being wrong with some commands.
- Some greenhouse bugs have been fixed, like randomly appearing when resizing the terminal
and some text attributes like color appearing where they weren't supposed to.