Add language guide to README covering all lush features
This commit is contained in:
243
README.md
243
README.md
@@ -1,5 +1,246 @@
|
|||||||
# Lush
|
# Lush
|
||||||
|
|
||||||
Lush is a shell built on [Lua 5.5.0](https://www.lua.org/). It extends Lua with shell-oriented features like command execution, pipelines, and built-in shell commands while maintaining full compatibility with the Lua 5.5 ecosystem.
|
Lush is a shell built on [Lua 5.5.0](https://www.lua.org/). It extends Lua with shell-oriented features — backtick commands, pipes, globbing, interpolation, environment variables — while remaining a fully compatible Lua 5.5 interpreter.
|
||||||
|
|
||||||
Based on [Lua](https://www.lua.org/) by R. Ierusalimschy, L. H. de Figueiredo, W. Celes (PUC-Rio).
|
Based on [Lua](https://www.lua.org/) by R. Ierusalimschy, L. H. de Figueiredo, W. Celes (PUC-Rio).
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
Build and run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make
|
||||||
|
./lush
|
||||||
|
```
|
||||||
|
|
||||||
|
## Language Guide
|
||||||
|
|
||||||
|
Lush is Lua with shell extensions. Every valid Lua program runs unchanged. The additions are described below.
|
||||||
|
|
||||||
|
### Backtick Commands
|
||||||
|
|
||||||
|
Execute shell commands with backticks. Each command runs in its own child process. Stdout and stderr are captured and returned as a table with `code`, `stdout`, and `stderr` fields.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local r = `echo hello`
|
||||||
|
print(r.stdout) -- "hello\n"
|
||||||
|
print(r.code) -- 0
|
||||||
|
|
||||||
|
local r = `ls nonexistent`
|
||||||
|
print(r.code) -- non-zero
|
||||||
|
print(r.stderr) -- error message
|
||||||
|
```
|
||||||
|
|
||||||
|
Empty backticks return `{code=0, stdout="", stderr=""}`. A command killed by a signal returns exit code 128 + signal number (e.g. 137 for SIGKILL).
|
||||||
|
|
||||||
|
### Interactive Commands
|
||||||
|
|
||||||
|
Prefix a line with `!` to run a command in a child process with stdout and stderr inherited from the terminal. The result is stored in `_`.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
!ls -la
|
||||||
|
print(_.code) -- 0
|
||||||
|
|
||||||
|
!sh -c "exit 42"
|
||||||
|
print(_.code) -- 42
|
||||||
|
```
|
||||||
|
|
||||||
|
Works anywhere a statement is valid — inside functions, loops, if-blocks.
|
||||||
|
|
||||||
|
### Pipes
|
||||||
|
|
||||||
|
Use `|` to connect commands in a pipeline. Each stage runs in its own process with stdout piped to the next stage's stdin.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local r = `echo hello | tr a-z A-Z`
|
||||||
|
print(r.stdout) -- "HELLO\n"
|
||||||
|
|
||||||
|
!ps aux | grep lush | head -5
|
||||||
|
```
|
||||||
|
|
||||||
|
The exit code comes from the last stage. Stderr from the last stage is captured (in backtick mode); stderr from middle stages goes to the terminal. Quoting (`"a|b"`, `'a|b'`, `a\|b`) treats `|` as literal.
|
||||||
|
|
||||||
|
### String Interpolation
|
||||||
|
|
||||||
|
Three forms of interpolation inside backtick and interactive commands:
|
||||||
|
|
||||||
|
`**${expr}` — Lua expression:**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local name = "world"
|
||||||
|
`echo ${name}` -- hello world
|
||||||
|
`echo ${40 + 2}` -- 42
|
||||||
|
`echo ${name:upper()}` -- WORLD
|
||||||
|
```
|
||||||
|
|
||||||
|
`**$NAME` — Environment variable:**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
`echo $HOME`
|
||||||
|
`echo $USER`
|
||||||
|
```
|
||||||
|
|
||||||
|
`**$(cmd)` — Subcommand substitution:**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
`echo today is $(date +%Y-%m-%d)`
|
||||||
|
`echo $(echo $(echo nested))` -- nested substitution
|
||||||
|
```
|
||||||
|
|
||||||
|
Escape with backslash: `\$HOME` produces the literal string `$HOME`.
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
Read and write environment variables directly from Lua using `$NAME` syntax:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- Read
|
||||||
|
local home = $HOME
|
||||||
|
local path = $PATH
|
||||||
|
|
||||||
|
-- Write (visible to child processes)
|
||||||
|
$MY_VAR = "hello"
|
||||||
|
`echo $MY_VAR` -- hello
|
||||||
|
|
||||||
|
-- Unset
|
||||||
|
$MY_VAR = nil
|
||||||
|
|
||||||
|
-- Numbers are coerced to strings
|
||||||
|
$PORT = 8080
|
||||||
|
```
|
||||||
|
|
||||||
|
### Globbing
|
||||||
|
|
||||||
|
Shell glob patterns are expanded inside backtick commands:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
`echo *.lua` -- expands to matching files
|
||||||
|
`echo src/**/*.c`
|
||||||
|
`echo file?.txt` -- single-character wildcard
|
||||||
|
`echo config.[ch]` -- bracket patterns
|
||||||
|
```
|
||||||
|
|
||||||
|
Brace expansion:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
`echo {a,b,c}` -- a b c
|
||||||
|
`echo file.{lua,c,h}` -- file.lua file.c file.h
|
||||||
|
```
|
||||||
|
|
||||||
|
Tilde expansion:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
`echo ~` -- /home/user
|
||||||
|
`echo ~/projects` -- /home/user/projects
|
||||||
|
```
|
||||||
|
|
||||||
|
Quoting suppresses expansion: `"*.lua"`, `'*.lua'`, and `\*.lua` are all literal.
|
||||||
|
|
||||||
|
### Quoting
|
||||||
|
|
||||||
|
Single quotes are literal (no escapes):
|
||||||
|
|
||||||
|
```lua
|
||||||
|
`echo 'hello world'`
|
||||||
|
`echo 'no $expansion here'`
|
||||||
|
```
|
||||||
|
|
||||||
|
Double quotes allow escapes and interpolation:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
`echo "hello world"`
|
||||||
|
`echo "tab:\there"`
|
||||||
|
`echo "path is $HOME"`
|
||||||
|
`echo "sum is ${1 + 2}"`
|
||||||
|
```
|
||||||
|
|
||||||
|
Backslash outside quotes escapes the next character:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
`echo hello\ world` -- single argument: hello world
|
||||||
|
```
|
||||||
|
|
||||||
|
### Builtins
|
||||||
|
|
||||||
|
Built-in commands that affect the shell process:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
`cd /tmp` -- change directory
|
||||||
|
`cd -` -- previous directory
|
||||||
|
`cd` -- home directory
|
||||||
|
|
||||||
|
`umask` -- query current umask
|
||||||
|
`umask 0077` -- set umask
|
||||||
|
```
|
||||||
|
|
||||||
|
### The `lush` Library
|
||||||
|
|
||||||
|
Programmatic API for shell operations:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
lush.capture("echo hello") -- like backticks, returns {code, stdout, stderr}
|
||||||
|
lush.run("echo hello") -- returns stdout with trailing newline stripped
|
||||||
|
lush.interactive("ls") -- like !, returns result and sets _
|
||||||
|
|
||||||
|
lush.envget("HOME") -- read env var
|
||||||
|
lush.envset("MY_VAR", "value") -- write env var
|
||||||
|
```
|
||||||
|
|
||||||
|
### Extensibility
|
||||||
|
|
||||||
|
**Custom commands** run in a child process (like any external command), so their output is captured and they support piping:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
lush.commands.greet = function(name, ...)
|
||||||
|
print("hello " .. (({...})[1] or "world"))
|
||||||
|
end
|
||||||
|
|
||||||
|
`greet lush` -- hello lush
|
||||||
|
`greet lush | tr a-z A-Z` -- HELLO LUSH
|
||||||
|
```
|
||||||
|
|
||||||
|
**Custom builtins** run in the current Lua process, so they can modify shell state (like `cd` does). They receive the raw command table and return a result table:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
lush.builtins.hello = function(cmd, name)
|
||||||
|
return {code = 0, stdout = "hi " .. (name or "") .. "\n", stderr = ""}
|
||||||
|
end
|
||||||
|
|
||||||
|
`hello world` -- hi world
|
||||||
|
```
|
||||||
|
|
||||||
|
**Aliases:**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
lush.aliases.ll = "ls -la"
|
||||||
|
`ll` -- runs: ls -la
|
||||||
|
|
||||||
|
lush.aliases.ll = nil -- remove alias
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
Lush loads config files on interactive startup (suppressed with `-E`):
|
||||||
|
|
||||||
|
- `$XDG_CONFIG_HOME/lush/config.lua` — main config
|
||||||
|
- `$XDG_CONFIG_HOME/lush/config.d/*.lua` — additional configs, loaded alphabetically
|
||||||
|
|
||||||
|
Use config files to set up aliases, custom commands, prompts, and environment.
|
||||||
|
|
||||||
|
### Prompt
|
||||||
|
|
||||||
|
Customize the interactive prompt:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- In config.lua:
|
||||||
|
_PROMPT = "lush> "
|
||||||
|
_PROMPT2 = "...> "
|
||||||
|
|
||||||
|
-- Dynamic prompt:
|
||||||
|
_PROMPT = setmetatable({}, {
|
||||||
|
__tostring = function()
|
||||||
|
return os.date("%H:%M") .. " > "
|
||||||
|
end
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user