A structured-data shell in Gleam, inspired by Nushell
8.2 kB
206 lines
1# gleshell
2
3A **structured-data shell** written in [Gleam](https://gleam.run), inspired by [Nushell](https://www.nushell.sh).
4
5Instead of piping opaque text between programs, gleshell pipelines pass typed values: strings, numbers, lists, records, and tables. Built-in commands like `ls`, `where`, and `select` work on that structure.
6
7```text
8~/code/gleshell on main
9 ls | where type == file | select name size | first 5
10╭──────┬──────╮
11│ name │ size │
12├──────┼──────┤
13│ … │ … │
14╰──────┴──────╯
15```
16
17The interactive prompt is zero-config and Starship-inspired: full path with `~`,
18optional git branch (read from `.git`, no external tools), and a Nerd Font shell
19icon (``) as the prompt character (turns red after a non-zero exit).
20Install a Nerd Font so the glyphs render (flake: `nix profile install
21.#nerd-fonts`, or put `nerd-fonts.symbols-only` in your system/fonts packages;
22the devenv shell already includes it).
23
24## Quick start
25
26Requires [Gleam](https://gleam.run/getting-started/) and Erlang (or use Nix):
27
28```bash
29# install a self-contained `gle` (Erlang shipment in the Nix store — no repo checkout)
30nix profile install .
31gle -c 'ls | first 3'
32
33# one-shot without installing
34nix run . -- -c 'ls | first 3'
35
36# dev shell (source tree; gleam, erlang, rebar3)
37# Preferred: direnv auto-loads the flake shell (see .envrc). Once:
38# direnv allow
39# then cd into the repo (nushell needs a direnv pre_prompt hook; see below).
40# Manual alternative — bare `nix develop` fails under pure flake eval:
41nix develop --no-pure-eval
42gleam run # interactive REPL
43gleam run -- -c 'ls | first 3'
44gleam test
45```
46
47### direnv
48
49This repo ships a [`.envrc`](.envrc) that runs `use flake . --no-pure-eval`
50so devenv can see the real project directory (pure flake eval cannot).
51
521. Install [direnv](https://direnv.net/) and hook it into your shell
53 ([nushell](https://github.com/direnv/direnv/wiki/Nushell), bash, zsh, …).
542. From the repo root: `direnv allow`
553. Open a new shell (or `cd .`) — you should see the devenv enter message and
56 have `gleam` on `PATH` without running `nix develop`.
57
58Bare `nix develop` still needs `--no-pure-eval` for the same reason; with
59direnv you usually do not need `nix develop` at all.
60
61### REPL editing
62
63On a TTY the interactive REPL uses a **raw-mode line editor** with
64**Nushell-style syntax highlighting** as you type (commands cyan, strings
65green, numbers purple, pipes purple, flags blue, variables purple, …).
66
67| Key | Action |
68|-----|--------|
69| ↑ / ↓ | History |
70| **Tab** | Command completion (builtins + `PATH`) at the start of a pipeline stage; filename completion for arguments (common prefix; list matches if ambiguous) |
71| **→ / Ctrl+F / End / Ctrl+E** (at end of line) | Accept greyed-out history suggestion (full) |
72| **Alt+F** (at end of line) | Accept one word of the history suggestion |
73| **Ctrl+R** | Fuzzy history search (stinkpot-style list; ↑/↓ move, Enter/Tab accept onto the line, Esc cancel) |
74| Ctrl+A / Ctrl+E | Beginning / end of line (Ctrl+E at end also accepts a history hint) |
75| Ctrl+W | Delete previous word |
76| Ctrl+U / Ctrl+K | Kill to start / end of line |
77| Ctrl+L | Clear screen |
78| **Ctrl+C** | Cancel current line at the prompt; while an external command runs, send SIGINT to that process (does not exit the shell) |
79| Ctrl+D | EOF (empty line) or delete under cursor |
80
81As you type, the **newest matching history line** is shown in grey after the
82cursor (Nushell/fish-style hints). Accept with **→**, **End**, **Ctrl+E**, or
83**Ctrl+F**; accept one word with **Alt+F**.
84
85History is persisted under the user cache as `gleshell-history/lines`.
86Non-TTY input falls back to Erlang’s `edlin`/`get_until` path.
87
88## Examples
89
90```nu
91# list files as a table, filter, project columns
92ls | where type == file | select name size
93ls | find toml md
94echo [moe larry curly] | find l
95
96# ranges and list ops
97range 10 | reverse | first 3
98range 5 | length
99
100# records and JSON
101echo {name: "gleshell", cool: true}
102echo "{\"a\": 1}" | from json | get a
103open data.json | get users | first
104range 3 | to json
105
106# variables
107let n = range 3 | length
108echo $n
109
110# process environment (Nushell-style)
111$env.HOME
112$env | get PATH
113$env.MY_VAR = hello
114echo $env.MY_VAR
115
116# external programs (stdout captured as a string)
117^uname -a
118which ls
119which -a ls
120which -f sh
121
122# processes (Nushell-style table)
123ps
124ps | sort-by mem | last 5
125ps --long | where name == beam.smp
126```
127
128## Language sketch
129
130| Feature | Syntax |
131|--------|--------|
132| Pipeline | `cmd \| cmd \| cmd` |
133| Strings | `"hello"` or bare words `hello` |
134| Numbers | `42`, `3.14` |
135| Bools | `true` / `false` |
136| Nothing | `null` / `nothing` |
137| Lists | `[1 2 3]` |
138| Records | `{name: alice, age: 30}` |
139| Variables | `let x = …` then `$x` (pipeline input is `$in`) |
140| Env | `$env`, `$env.HOME`, `$env.FOO = value` |
141| Flags | `--flag` / `--flag value` |
142| Force external | `^command args…` |
143| Comments | `# …` (word-boundary only; mid-token `#` is fine — `nixpkgs#pkg`) |
144
145## Built-ins
146
147Filesystem: `ls`, `cd`, `pwd`, `cat` (MIME/extension detect + syntax color on TTY;
148`--raw` for plain pipelines), `open`, `save`
149
150Table/list: `where`/`filter`, `find`, `select`, `get`, `first`, `last`, `take`, `skip`, `sort-by`, `reverse`, `length`, `columns`, `table`, `flatten`, `uniq`, `wrap`, `unwrap`, `keys`, `values`
151
152Data: `echo`, `range`, `lines`, `to`/`from` (subcommand `json`), `type`, `describe`, `env`, `sys`, `which`, `help`, `about`, `exit`
153
154HTTP: `http get|post|put|delete|patch|head` — fetch/send with structured JSON bodies
155and responses (`http get https://example.com`, `http post URL {a: 1}`, `--full`,
156`-H` headers, `--raw`, `--allow-errors`)
157
158Pager: `less` — builtin color-aware pager for pipeline input or files (`ls | less`,
159`less README.md`). ANSI from tables and external tools is kept; short output is
160printed without an interactive session. Interactive keys include live `/` search
161(case-insensitive, finds as you type) and `n`/`N` next/previous match. `^less`
162still runs the external binary.
163
164Unknown command names fall through to external executables on `PATH`.
165
166## Layout
167
168```text
169src/
170 gleshell.gleam # entry + REPL
171 gleshell_ffi.erl # line editor (Ctrl+R fuzzy history), cwd, process spawn
172 gleshell/
173 value.gleam # structured Value type
174 lexer.gleam / parser.gleam
175 eval.gleam # pipeline evaluator
176 builtins.gleam # Nu-inspired commands
177 pager.gleam # color-aware builtin less
178 display.gleam # table pretty-printer
179 color.gleam # Nushell-style ANSI colors
180 highlight.gleam # live input syntax highlighting
181 syntax.gleam # file language detect + cat highlighters
182 env.gleam / sys.gleam
183```
184
185Input and output are colorized on a TTY (Nu-like shapes on the command line;
186headers bold green, numbers purple, bools cyan, dirs blue, errors red, …).
187External tools (`jj`, `git`, `fastfetch`, …) keep their own colors: final-stage
188commands inherit the real TTY; captured pipeline stages (e.g. `jj log | less`,
189`git log | less`) run under a throwaway PTY when the shell wants color so tools
190that only colorize on a terminal still emit ANSI — without per-tool env hacks.
191Nested pagers are forced to `cat` so the producer cannot hang in its own
192`less`. If `script` is unavailable, capture falls back to pipes plus
193`FORCE_COLOR` / `CLICOLOR_FORCE` and a git `GIT_CONFIG_*` overlay. Pre-colored
194text is not re-painted by the shell. While the raw line editor is active, the
195TTY is switched to cooked mode for those children so LF-only output does not
196staircase. The builtin `less` keeps ANSI (like `less -R`); external pagers
197still get `LESS=FRX` when unset so they pass colors through.
198Disable with `NO_COLOR=1`; force with `FORCE_COLOR=1`.
199
200## Status
201
202Early but usable: core pipeline model, tables, filters, JSON, and external commands work. Not a full Nushell clone (no closures/plugins yet). Contributions and experiments welcome.
203
204## License
205
206Apache-2.0 (same as the Gleam project template unless you change it).