Register luaopen_lush() in stdlibs so the lush table is available as a global. Users can now extend the shell by adding functions to lush.builtins. The OP_LUSH VM access via integer keys is preserved.
97 lines
3.9 KiB
Markdown
97 lines
3.9 KiB
Markdown
# 26 — Shell abbreviations and user-extensible builtins
|
|
|
|
**Status:** done
|
|
|
|
## Problem
|
|
|
|
Users cannot define shell abbreviations or custom commands. For example, there's no way to alias `gs` to `git status` or add a custom `mkcd` that creates a directory and cd's into it.
|
|
|
|
## Current architecture
|
|
|
|
Commit `f88b1795` moved all shell internals (`__command`, `__interactive`, `__getenv`, `__setenv`) out of `_G` and into a hidden registry table at `LUA_RIDX_LUSH`. Builtins (`cd`, `exec`, `umask`) live at `LUA_RIDX_LUSH.builtins`. The dispatch path in `try_builtin()` (`lcmd.c:875`) already does a table lookup:
|
|
|
|
```c
|
|
lua_getfield(L, -1, "builtins") /* get builtins table */
|
|
lua_getfield(L, -1, pa->argv[0]) /* look up command name */
|
|
```
|
|
|
|
This means builtins are **already data-driven** — if a function appears in the table, it gets called. But the table is invisible to user code, so there's no way to add entries.
|
|
|
|
## Proposal: expose `lush.builtins` to user code
|
|
|
|
Expose the `LUA_RIDX_LUSH` table (or a curated view of it) as a global like `lush` or `__lush`. Users extend the shell by adding functions to `lush.builtins`:
|
|
|
|
```lua
|
|
-- abbreviation
|
|
lush.builtins.gs = function(cmd)
|
|
!git status
|
|
end
|
|
|
|
-- custom builtin
|
|
lush.builtins.mkcd = function(cmd, dir)
|
|
!mkdir -p ${dir}
|
|
!cd ${dir}
|
|
end
|
|
|
|
-- override (with access to original)
|
|
local orig_cd = lush.builtins.cd
|
|
lush.builtins.cd = function(cmd, dir)
|
|
orig_cd(cmd, dir)
|
|
print("now in: " .. $PWD)
|
|
end
|
|
```
|
|
|
|
This requires no new dispatch mechanism — `try_builtin()` already does the right thing. The only change is making the table accessible.
|
|
|
|
## Design considerations
|
|
|
|
### What to expose
|
|
|
|
Option A: Expose the entire `LUA_RIDX_LUSH` table as `lush`:
|
|
```lua
|
|
lush.builtins -- cd, exec, umask, user additions
|
|
lush.command -- __command (backtick execution)
|
|
lush.interactive -- __interactive (! execution)
|
|
lush.getenv -- __getenv ($VAR read)
|
|
lush.setenv -- __setenv ($VAR write)
|
|
```
|
|
|
|
This gives power users access to the shell primitives directly, which is useful for building abstractions. But it also means users could break internals.
|
|
|
|
Option B: Expose only `lush.builtins` — safer, sufficient for the abbreviation use case.
|
|
|
|
Consider the clarity of these endpoints. Would an alternative naming scheme be clearer? i.e.
|
|
```lua
|
|
lush.commands -- cd, exec, umask, user additions
|
|
lush.run -- __command (backtick execution)
|
|
lush.interactive -- __interactive (! execution) -- is there an idiotmatic name for this?
|
|
lush.getenv -- __getenv ($VAR read)
|
|
lush.setenv -- __setenv ($VAR write)
|
|
```
|
|
|
|
### Relationship to f88b1795
|
|
|
|
That commit deliberately hid these from `_G` to avoid polluting the namespace. Exposing a single `lush` global is a middle ground: one clean entry point instead of scattered `__double_underscore` globals, and users can only extend via a structured API rather than accidentally shadowing internals.
|
|
|
|
### Abbreviations vs builtins
|
|
|
|
Shell abbreviations (fish-style text expansion) and builtins (function dispatch) are different features in traditional shells. But in lush, a builtin that calls `!git status` achieves the same effect as an abbreviation — the `!` prefix runs the command interactively with terminal inheritance. So a single mechanism covers both use cases.
|
|
|
|
### Builtin protocol
|
|
|
|
Current builtins receive `(cmd_name, arg1, arg2, ...)` and return a result table `{code, stdout, stderr}`. User builtins should follow the same protocol. Document this as the contract.
|
|
|
|
## Files likely affected
|
|
|
|
| File | Changes |
|
|
|------|---------|
|
|
| `linit.c` | Expose `LUA_RIDX_LUSH` table as `lush` global |
|
|
| `lcmd.c` | No changes — `try_builtin()` already works via table lookup |
|
|
| `lbuiltin.c` | No changes — existing builtins stay as-is |
|
|
|
|
## Open questions
|
|
|
|
- Name: `lush`, `shell`, `__shell`? `lush` is clean and matches the project name.
|
|
- Should `lush.command` / `lush.interactive` be exposed or kept hidden?
|
|
- Should there be a `builtin` command to bypass user overrides (like bash's `builtin cd`)?
|