Compare commits

..

9 Commits

11 changed files with 162 additions and 34 deletions

View File

@ -1,5 +1,33 @@
# 🎀 Changelog # 🎀 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 ## [2.2.3] - 2024-04-27
### Fixed ### Fixed
- Highligher and hinter work now, since it was regressed from the previous minor release. - Highligher and hinter work now, since it was regressed from the previous minor release.
@ -716,6 +744,7 @@ This input for example will prompt for more input to complete:
First "stable" release of Hilbish. 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.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

94
api.go
View File

@ -16,6 +16,7 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"io"
"os" "os"
"os/exec" "os/exec"
"runtime" "runtime"
@ -153,11 +154,62 @@ func unsetVimMode() {
util.SetField(l, hshMod, "vimMode", rt.NilValue) util.SetField(l, hshMod, "vimMode", rt.NilValue)
} }
func handleStream(v rt.Value, strms *streams, errStream bool) error {
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) // 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. // TODO: ON BREAKING RELEASE, DO NOT ACCEPT `streams` AS A BOOLEAN.
if err := c.Check1Arg(); err != nil { if err := c.Check1Arg(); err != nil {
@ -168,7 +220,7 @@ func hlrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return nil, err return nil, err
} }
var strms *streams 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]
@ -181,35 +233,31 @@ func hlrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return nil, errors.New("bad argument to run (expected boolean or table, got " + tout.TypeName() + ")") return nil, errors.New("bad argument to run (expected boolean or table, got " + tout.TypeName() + ")")
} }
var stdoutStream, stderrStream *iolib.File handleStream(luastreams.Get(rt.StringValue("out")), strms, false)
handleStream(luastreams.Get(rt.StringValue("err")), strms, true)
stdoutstrm := luastreams.Get(rt.StringValue("stdout")) stdinstrm := luastreams.Get(rt.StringValue("input"))
if !stdoutstrm.IsNil() { if !stdinstrm.IsNil() {
f, ok := iolib.ValueToFile(stdoutstrm) ud, ok := stdinstrm.TryUserData()
if !ok { if !ok {
return nil, errors.New("bad argument to run streams table (expected file, got " + stdoutstrm.TypeName() + ")") return nil, errors.New("bad type as run stdin stream (expected userdata as either sink or file, got " + stdinstrm.TypeName() + ")")
} }
stdoutStream = f val := ud.Value()
} var varstrm io.Reader
if f, ok := val.(*iolib.File); ok {
stderrstrm := luastreams.Get(rt.StringValue("stderr")) varstrm = f.Handle()
if !stderrstrm.IsNil() {
f, ok := iolib.ValueToFile(stderrstrm)
if !ok {
return nil, errors.New("bad argument to run streams table (expected file, got " + stderrstrm.TypeName() + ")")
} }
stderrStream = f if f, ok := val.(*sink); ok {
} varstrm = f.reader
}
strms = &streams{} if varstrm == nil {
if stdoutStream != nil { return nil, errors.New("bad type as run stdin stream (expected userdata as either sink or file)")
strms.stdout = stdoutStream.Handle() }
}
if stderrStream != nil { strms.stdin = varstrm
strms.stderr = stderrStream.Handle()
} }
} else { } else {
if !terminalOut { if !terminalOut {

View File

@ -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, 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(mdTable.String())
f.WriteString("\n") f.WriteString("\n")

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.

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,20 +413,25 @@ 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.
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.
</div> </div>

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,12 @@ 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 --- 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

View File

@ -283,7 +283,11 @@ func execCommand(cmd string, strms *streams) (io.Writer, io.Writer, error) {
strms.stderr = os.Stderr strms.stderr = os.Stderr
} }
interp.StdIO(os.Stdin, strms.stdout, strms.stderr)(runner) if strms.stdin == nil {
strms.stdin = os.Stdin
}
interp.StdIO(strms.stdin, strms.stdout, strms.stderr)(runner)
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
printer := syntax.NewPrinter() printer := syntax.NewPrinter()
@ -345,6 +349,7 @@ func execHandle(bg bool) interp.ExecHandlerFunc {
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))

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.

View File

@ -228,6 +228,11 @@ 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 fpipe(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func fpipe(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
rf, wf, err := os.Pipe() rf, wf, err := os.Pipe()
if err != nil { if err != nil {