- #25: $VAR expansion in commands - #26: Shell abbreviations and user-extensible builtins - #27: $(cmd) subcommand syntax - #22: Updated with implementation details (in progress) - #7: Standardize status label
This commit is contained in:
87
issues/26-shell-abbreviations-and-user-builtins.md
Normal file
87
issues/26-shell-abbreviations-and-user-builtins.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# 26 — Shell abbreviations and user-extensible builtins
|
||||
|
||||
**Status:** open
|
||||
|
||||
## 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.
|
||||
|
||||
### 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`)?
|
||||
Reference in New Issue
Block a user