A structured-data shell in Gleam, inspired by Nushell
1//// gleshell — a structured-data shell in Gleam, inspired by Nushell.
2
3import argv
4import gleam/io
5import gleam/string
6import gleshell/display
7import gleshell/env
8import gleshell/eval
9import gleshell/sys
10import gleshell/value.{Nothing}
11
12pub fn main() -> Nil {
13 case argv.load().arguments {
14 ["-h"] | ["--help"] | ["help"] -> {
15 print_usage()
16 Nil
17 }
18 ["-c", code] -> run_once(code)
19 ["-c"] -> {
20 io.println_error("gleshell: -c requires a command string")
21 halt(2)
22 }
23 [] -> repl(env.new())
24 args -> run_once(string.join(args, " "))
25 }
26}
27
28fn print_usage() -> Nil {
29 io.println(string.join(
30 [
31 "gleshell — Gleam shell inspired by Nushell",
32 "",
33 "Usage:",
34 " gleshell Interactive REPL",
35 " gleshell -c <code> Evaluate a one-liner",
36 " gleshell <code>… Evaluate remaining args as code",
37 "",
38 "Examples:",
39 " gleshell -c 'ls | where type == file | first 5'",
40 " gleshell -c 'range 10 | reverse | first 3'",
41 " gleshell -c 'echo {name: \"gleshell\", cool: true}'",
42 "",
43 "In the REPL, type `help` for built-in commands.",
44 ],
45 "\n",
46 ))
47}
48
49fn run_once(code: String) -> Nil {
50 let env = env.new()
51 case eval.eval_source(env, code) {
52 eval.Quit(code) -> halt(code)
53 eval.Continue(_env, value) -> {
54 print_value(value)
55 case value {
56 value.Fail(_) -> halt(1)
57 _ -> Nil
58 }
59 }
60 }
61}
62
63fn repl(env: env.Env) -> Nil {
64 io.println(
65 "gleshell 0.1 — structured data shell (type `help`, `exit` to quit)",
66 )
67 repl_loop(env)
68}
69
70fn repl_loop(env: env.Env) -> Nil {
71 let prompt = prompt_for(env)
72 case sys.get_line(prompt) {
73 Error("eof") -> {
74 io.println("")
75 Nil
76 }
77 Error(e) -> {
78 io.println_error("read error: " <> e)
79 Nil
80 }
81 Ok(line) -> {
82 let line = string.trim(line)
83 case line {
84 "" -> repl_loop(env)
85 _ ->
86 case eval.eval_source(env, line) {
87 eval.Quit(code) -> {
88 case code {
89 0 -> Nil
90 _ -> halt(code)
91 }
92 }
93 eval.Continue(env2, value) -> {
94 print_value(value)
95 repl_loop(env2)
96 }
97 }
98 }
99 }
100 }
101}
102
103fn prompt_for(env: env.Env) -> String {
104 let base = basename(env.cwd)
105 "gleshell:" <> base <> "> "
106}
107
108fn basename(path: String) -> String {
109 case string.split(path, "/") {
110 [] -> path
111 parts ->
112 case list_last(parts) {
113 "" ->
114 // path ended with /
115 case parts {
116 [only] -> only
117 _ -> {
118 // take second last non-empty if possible
119 path
120 }
121 }
122 name -> name
123 }
124 }
125}
126
127fn list_last(items: List(String)) -> String {
128 case items {
129 [] -> ""
130 [x] -> x
131 [_, ..rest] -> list_last(rest)
132 }
133}
134
135fn print_value(value: value.Value) -> Nil {
136 case value {
137 Nothing -> Nil
138 _ -> {
139 let text = display.render(value)
140 case text {
141 "" -> Nil
142 t -> io.println(t)
143 }
144 }
145 }
146}
147
148@external(erlang, "erlang", "halt")
149fn halt(status: Int) -> Nil