A structured-data shell in Gleam, inspired by Nushell
0

Configure Feed

Select the types of activity you want to include in your feed.

gleshell / README.md
8.9 kB 227 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 104echo "{\"user\": {\"name\": \"ada\"}}" | from json | get user.name 105range 3 | to json 106 107# JWT (parse only — does not verify signature) 108echo $token | from jwt | get payload 109echo $token | from jwt | get header.alg 110from jwt eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.… 111 112# paste multi-line text into a pipeline (finish with Ctrl+D) 113input | from json 114input "Paste notes:" | lines | find TODO 115# or pipe data into a one-shot: printf '{"a":1}' | gle -c 'input | from json' 116 117# variables 118let n = range 3 | length 119echo $n 120 121# process environment (Nushell-style) 122$env.HOME 123$env | get PATH 124$env.MY_VAR = hello 125echo $env.MY_VAR 126 127# external programs (stdout captured as a string) 128^uname -a 129which ls 130which -a ls 131which -f sh 132 133# processes (Nushell-style table) 134ps 135ps | sort-by mem | last 5 136ps --long | where name == beam.smp 137 138# who is bound to a port (listeners by default; --all ≈ lsof -i) 139whyport 22 140whyport 4004 141whyport --all 4004 142 143# current time (Unix epoch seconds; prints like ls modified) 144now 145``` 146 147## Language sketch 148 149| Feature | Syntax | 150|--------|--------| 151| Pipeline | `cmd \| cmd \| cmd` | 152| Strings | `"hello"` or bare words `hello` | 153| Numbers | `42`, `3.14` | 154| Bools | `true` / `false` | 155| Nothing | `null` / `nothing` | 156| Lists | `[1 2 3]` | 157| Records | `{name: alice, age: 30}` | 158| Variables | `let x = …` then `$x` (pipeline input is `$in`) | 159| Env | `$env`, `$env.HOME`, `$env.FOO = value` | 160| Flags | `--flag` / `--flag value` | 161| Force external | `^command args…` | 162| Comments | `# …` (word-boundary only; mid-token `#` is fine — `nixpkgs#pkg`) | 163 164## Built-ins 165 166Filesystem: `ls`, `cd`, `pwd`, `cat` (MIME/extension detect + syntax color on TTY; 167`--raw` for plain pipelines), `open`, `save` 168 169Table/list: `where`/`filter`, `find`, `select`, `get`, `first`, `last`, `take`, `skip`, `sort-by`, `reverse`, `length`, `columns`, `table`, `flatten`, `uniq`, `wrap`, `unwrap`, `keys`, `values` 170 171Data: `echo`, `range`, `lines`, `input` (multi-line paste / stdin until Ctrl+D), 172`to`/`from` (subcommands `json`, `jwt`), `type`, `describe`, `env`, `sys`, `ps`, 173`whyport`, `now`, `which`, `help`, `about`, `exit` 174 175HTTP: `http get|post|put|delete|patch|head` — fetch/send with structured JSON bodies 176and responses (`http get https://example.com`, `http post URL {a: 1}`, `--full`, 177`-H` headers, `--raw`, `--allow-errors`) 178 179Pager: `less` — builtin color-aware pager for pipeline input or files (`ls | less`, 180`less README.md`). ANSI from tables and external tools is kept; short output is 181printed without an interactive session. Interactive keys include live `/` search 182(case-insensitive, finds as you type) and `n`/`N` next/previous match. `^less` 183still runs the external binary. 184 185Unknown command names fall through to external executables on `PATH`. 186 187## Layout 188 189```text 190src/ 191 gleshell.gleam # entry + REPL 192 gleshell_ffi.erl # line editor (Ctrl+R fuzzy history), cwd, process spawn 193 gleshell/ 194 value.gleam # structured Value type 195 lexer.gleam / parser.gleam 196 eval.gleam # pipeline evaluator 197 builtins.gleam # Nu-inspired commands 198 pager.gleam # color-aware builtin less 199 display.gleam # table pretty-printer 200 color.gleam # Nushell-style ANSI colors 201 highlight.gleam # live input syntax highlighting 202 syntax.gleam # file language detect + cat highlighters 203 env.gleam / sys.gleam 204``` 205 206Input and output are colorized on a TTY (Nu-like shapes on the command line; 207headers bold green, numbers purple, bools cyan, dirs blue, errors red, …). 208External tools (`jj`, `git`, `fastfetch`, …) keep their own colors: final-stage 209commands inherit the real TTY; captured pipeline stages (e.g. `jj log | less`, 210`git log | less`) run under a throwaway PTY when the shell wants color so tools 211that only colorize on a terminal still emit ANSI — without per-tool env hacks. 212Nested pagers are forced to `cat` so the producer cannot hang in its own 213`less`. If `script` is unavailable, capture falls back to pipes plus 214`FORCE_COLOR` / `CLICOLOR_FORCE` and a git `GIT_CONFIG_*` overlay. Pre-colored 215text is not re-painted by the shell. While the raw line editor is active, the 216TTY is switched to cooked mode for those children so LF-only output does not 217staircase. The builtin `less` keeps ANSI (like `less -R`); external pagers 218still get `LESS=FRX` when unset so they pass colors through. 219Disable with `NO_COLOR=1`; force with `FORCE_COLOR=1`. 220 221## Status 222 223Early 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. 224 225## License 226 227Apache-2.0 (same as the Gleam project template unless you change it).