# shapi
`shapi`, or **sh**ell **API**, is a Lua module which implements some kind of
eDSL[^1] to work with the shell/CLI[^2] environment. It depends on
[luaposix](https://github.com/luaposix/luaposix).
[^1]: Embedded Domain Specific Language
[^2]: Command-line interface
The incentive is to have an alternative to a shell language like **bash**, as an
API.
E.g. for scripting purposes, if one already uses Lua at the heart of its
methodology, or to be embedded into an existing application.
## Overview
```lua
local sh = require "shapi"
print("HEAD: "..sh:git("rev-parse", "HEAD"))
print(sh:__in("foo/bar.txt"):md5sum())
sh:__p("my-command", "--foo", "bar"):__out("foo/bar.txt")()
sh:sleep(2):__return()
sh:sleep(2)()
```
## Install
See `src/`, `rockspecs/` or
[luarocks](https://luarocks.org/modules/imagicthecat-0a6b669a3a/shapi).
## API
A command object is created and then chain methods are applied to it. This
chaining pattern is similar to **bash** pipes, making `:` behave like `|`. For
most methods, it means linking **stdout** from the previous step to **stdin** of
the next one while keeping **stderr** of the parent process (unless `__err` is
used).
Methods will directly spawn sub-processes as they are called, there is no build
phase of the command.
Special methods start with `__` and other strings will be interpreted as shell
process names.
Calling on the command object is an alias to `__return(...)`. String conversion
and concatenation will call `__return()`.
**Note:** The chain starts from the module, the first method is called on it,
but the `self` parameter will be replaced with the command object.
### __str_in(data)
Input raw string data into the chain (create a new process).
### __in(file, [mode])
Input a file into the chain.
If `file` is a string, `file, [mode]` is the path and mode for `io.open()`. The
`mode` defaults to `rb`.
If `file` is a number, it indicates a file descriptor[^fd].
[^fd]: A file descriptor of the current process, the one constructing the
command.
### __out(file, [mode])
Output the chain to a file (create a new process).
If `file` is a string, `file, [mode]` is the path and mode for `io.open()`. The
`mode` defaults to `wb`.
If `file` is a number, it indicates a file descriptor[^fd].
### __err(file, [mode])
Setup **stderr** for subsequent processes of the chain.
If `file` is a string, `file, [mode]` is the path and mode for `io.open()`. The
`mode` defaults to `wb`.
If `file` is a number, it indicates a file descriptor[^fd].
Example: Discard **stderr**
```lua
local cmd = sh:__err("/dev/null"):cat("foo/missing.txt")
local ok, out = pcall(cmd)
```
### __p(name, ...)
Chain a shell process.
Can be used to chain a shell process with a name which cannot be represented
with a Lua name/identifier.
- name: shell process name/path (see luaposix **execp**)
- ...: process arguments
### \<shell-process>(...)
Alias for `__p(<shell-process>, ...)`.
### __lua(fproc, ...)
Chain a Lua function (create a new process).
- fproc: Lua function
- ...: function arguments
Example: Implementation of `__str_in`
```lua
-- __str_in is equivalent to:
sh:__lua(function() assert(io.stdout:write(data)) end)
```
### __wait()
Wait/end the command.
It waits on the command processes and returns the command internal state.
state (table):
- output: unprocessed final output (stdout), string
- children: list of children `{}` (follows the chain order)
- pid
- kind: `"exited"`, `"killed"` or `"stopped"`
- status: exit status, or signal number responsible for `"killed"` or
`"stopped"`
### __return([mode])
Return/end the command.
It waits on the command processes, propagates exit errors or returns the final
output (stdout) as a string.
By default, trailing new lines are removed, but this can be disabled using the
mode parameter.
- mode: string, `"binary"` to prevent processing of the output
### __do()
Do/end the command (outputs to stdout).
It waits on the command processes and propagates exit errors.
### __(f, ...)
Chain custom method.
- f(self, ...): method
- ...: method arguments
Example: Abstraction of multiple steps
```lua
local function my_md5sum(self, file)
return self:md5sum(file):cut("-d", " ", "-f", 1)
end
print(sh:__in("foo/bar.txt"):__(my_md5sum))
print(sh:__(my_md5sum, "foo/bar.txt"))
```