Skip to content

Commit

Permalink
feat: allow hilbish.run to take a table of files to use for output (#291
Browse files Browse the repository at this point in the history
)
  • Loading branch information
TorchedSammy authored Apr 28, 2024
1 parent fb9d305 commit a20123f
Show file tree
Hide file tree
Showing 13 changed files with 261 additions and 80 deletions.
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
# 🎀 Changelog

## Unreleased
### Added
- `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
})
```

## [2.2.3] - 2024-04-27
### Fixed
- Highligher and hinter work now, since it was regressed from the previous minor release.
Expand Down Expand Up @@ -716,6 +744,7 @@ This input for example will prompt for more input to complete:
First "stable" release of Hilbish.
[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.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
Expand Down
118 changes: 105 additions & 13 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"bytes"
"errors"
"fmt"
"io"
"os"
"os/exec"
"runtime"
Expand All @@ -27,6 +28,7 @@ import (

rt "github.com/arnodel/golua/runtime"
"github.com/arnodel/golua/lib/packagelib"
"github.com/arnodel/golua/lib/iolib"
"github.com/maxlandon/readline"
"mvdan.cc/sh/v3/interp"
)
Expand Down Expand Up @@ -152,12 +154,64 @@ func unsetVimMode() {
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.
// 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 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
// #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
}
Expand All @@ -166,32 +220,70 @@ func hlrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return nil, err
}

strms := &streams{}
var terminalOut bool
if len(c.Etc()) != 0 {
tout := c.Etc()[0]
termOut, ok := tout.TryBool()
terminalOut = termOut

var ok bool
terminalOut, ok = tout.TryBool()
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 {
if !terminalOut {
strms = &streams{
stdout: new(bytes.Buffer),
stderr: new(bytes.Buffer),
}
}
}
} else {
terminalOut = true
}

var exitcode uint8
stdout, stderr, err := execCommand(cmd, terminalOut)
stdout, stderr, err := execCommand(cmd, strms)

if code, ok := interp.IsExitStatus(err); ok {
exitcode = code
} else if err != nil {
exitcode = 1
}

stdoutStr := ""
stderrStr := ""
if !terminalOut {
stdoutStr = stdout.(*bytes.Buffer).String()
stderrStr = stderr.(*bytes.Buffer).String()
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
Expand Down
6 changes: 5 additions & 1 deletion cmd/docgen/docgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,11 @@ func main() {
}

mdTable.SetContent(i - diff, 0, fmt.Sprintf(`<a href="#%s">%s</a>`, dps.FuncName, dps.FuncSig))
mdTable.SetContent(i - diff, 1, dps.Doc[0])
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])
}
}
f.WriteString(mdTable.String())
f.WriteString("\n")
Expand Down
7 changes: 5 additions & 2 deletions docs/api/commander.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
have is: What is the `sinks` parameter?

The `sinks` parameter is a table with 3 keys: `in`, `out`,
and `err`. All of them are a <a href="/Hilbish/docs/api/hilbish/#sink" style="text-decoration: none;">Sink</a>.
The `sinks` parameter is a table with 3 keys: `input`, `out`, and `err`.
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.
You may use the read functions on this sink to get input from the user.
Expand Down
17 changes: 17 additions & 0 deletions docs/api/fs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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="#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="#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="#stat">stat(path) -> {}</a>|Returns the information about a given `path`.|

Expand Down Expand Up @@ -183,6 +184,22 @@ fs.mkdir('./foo/bar', true)
```
</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>
<div id='readdir'>
<h4 class='heading'>
Expand Down
13 changes: 9 additions & 4 deletions docs/api/hilbish/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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="#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="#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="#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.|
Expand Down Expand Up @@ -413,20 +413,25 @@ Text to print before input, can be empty.
<hr>
<div id='run'>
<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'>
<i class="fas fa-paperclip"></i>
</a>
</h4>

Runs `cmd` in Hilbish's shell script interpreter.
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`**


`boolean` **`returnOut`**
If this is true, the function will return the standard output and error of the command instead of printing it.
`table|boolean` **`streams`**


</div>

Expand Down
4 changes: 4 additions & 0 deletions emmyLuaDocs/fs.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ function fs.join(...path) 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.
function fs.readdir(path) end

Expand Down
7 changes: 6 additions & 1 deletion emmyLuaDocs/hilbish.lua
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,12 @@ function hilbish.prompt(str, typ) end
function hilbish.read(prompt) end

--- Runs `cmd` in Hilbish's shell script interpreter.
function hilbish.run(cmd, returnOut) end
--- 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
Expand Down
Loading

0 comments on commit a20123f

Please sign in to comment.