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 / test / gleshell_test.gleam
9.7 kB 346 lines
1import gleam/string 2import gleeunit 3import gleshell/color 4import gleshell/display 5import gleshell/env 6import gleshell/eval 7import gleshell/highlight 8import gleshell/lexer 9import gleshell/parser 10import gleshell/sys 11import gleshell/value.{Bool, Int, List, Nothing, Record, String, Table} 12 13pub fn main() -> Nil { 14 gleeunit.main() 15} 16 17// --- lexer --- 18 19pub fn lexer_pipeline_test() { 20 let assert Ok(tokens) = lexer.tokenize("ls | where type == file") 21 let assert [lexer.Ident("ls"), lexer.Pipe, lexer.Ident("where"), ..] = tokens 22 Nil 23} 24 25pub fn lexer_string_and_number_test() { 26 let assert Ok(tokens) = lexer.tokenize("echo \"hi\" 42 3.14 true") 27 let assert [ 28 lexer.Ident("echo"), 29 lexer.StringLit("hi"), 30 lexer.IntLit(42), 31 lexer.FloatLit(3.14), 32 lexer.BoolLit(True), 33 lexer.Eof, 34 ] = tokens 35 Nil 36} 37 38// --- parser --- 39 40pub fn parse_pipeline_test() { 41 let assert Ok(parser.Expr(parser.Pipeline(cmds))) = 42 parser.parse("ls | first 3") 43 let assert [ 44 parser.Command("ls", [], False), 45 parser.Command("first", args, False), 46 ] = cmds 47 let assert [parser.ValueArg(parser.Lit(Int(3)))] = args 48 Nil 49} 50 51pub fn parse_let_test() { 52 let assert Ok(parser.Let( 53 "x", 54 parser.Pipeline([parser.Command("echo", args, False)]), 55 )) = parser.parse("let x = echo 1") 56 let assert [parser.ValueArg(parser.Lit(Int(1)))] = args 57 Nil 58} 59 60pub fn parse_where_ops_test() { 61 let assert Ok(parser.Expr(parser.Pipeline([ 62 parser.Command("where", args, False), 63 ]))) = parser.parse("where type == file") 64 let assert [ 65 parser.ValueArg(parser.Lit(String("type"))), 66 parser.ValueArg(parser.Lit(String("=="))), 67 parser.ValueArg(parser.Lit(String("file"))), 68 ] = args 69 Nil 70} 71 72pub fn parse_list_and_record_test() { 73 let assert Ok(parser.Expr(parser.Pipeline([ 74 parser.Command("echo", args, False), 75 ]))) = parser.parse("echo [1 2] {a: true}") 76 let assert [ 77 parser.ValueArg(parser.ListExpr([parser.Lit(Int(1)), parser.Lit(Int(2))])), 78 parser.ValueArg(parser.RecordExpr([#("a", parser.Lit(Bool(True)))])), 79 ] = args 80 Nil 81} 82 83// --- value helpers --- 84 85pub fn table_from_records_test() { 86 let rows = [ 87 Record([#("name", String("a")), #("n", Int(1))]), 88 Record([#("name", String("b")), #("n", Int(2))]), 89 ] 90 let assert Table( 91 ["name", "n"], 92 [[String("a"), Int(1)], [String("b"), Int(2)]], 93 ) = value.table_from_records(rows) 94 Nil 95} 96 97// --- eval --- 98 99pub fn eval_echo_and_range_test() { 100 let env = env.new() 101 let assert eval.Continue(_, List([Int(0), Int(1), Int(2)])) = 102 eval.eval_source(env, "range 3") 103 let assert eval.Continue(_, String("hello")) = 104 eval.eval_source(env, "echo hello") 105 Nil 106} 107 108pub fn eval_pipeline_reverse_first_test() { 109 let env = env.new() 110 let assert eval.Continue(_, Int(2)) = 111 eval.eval_source(env, "range 3 | reverse | first") 112 Nil 113} 114 115pub fn eval_let_and_var_test() { 116 let env = env.new() 117 let assert eval.Continue(env2, Int(7)) = 118 eval.eval_source(env, "let n = echo 7") 119 let assert eval.Continue(_, Int(7)) = eval.eval_source(env2, "echo $n") 120 Nil 121} 122 123pub fn eval_env_var_get_test() { 124 // `$env.HOME` and bare `$env.HOME` as pipeline source 125 let env = env.new() 126 let assert Ok(home) = sys.getenv("HOME") 127 let assert eval.Continue(_, String(got)) = 128 eval.eval_source(env, "echo $env.HOME") 129 let assert True = got == home 130 let assert eval.Continue(_, String(got2)) = eval.eval_source(env, "$env.HOME") 131 let assert True = got2 == home 132 Nil 133} 134 135pub fn eval_env_record_test() { 136 let env = env.new() 137 let assert eval.Continue(_, Record(fields)) = eval.eval_source(env, "$env") 138 let assert True = list_has_string_field(fields, "HOME") 139 let assert True = list_has_string_field(fields, "PATH") 140 // `$env | get HOME` 141 let assert eval.Continue(_, String(home)) = 142 eval.eval_source(env, "$env | get HOME") 143 let assert True = string.length(home) > 0 144 Nil 145} 146 147pub fn eval_env_assign_test() { 148 let env = env.new() 149 let assert eval.Continue(env2, String("gleshell-test-val")) = 150 eval.eval_source(env, "$env.GLESHELL_TEST_VAR = gleshell-test-val") 151 let assert eval.Continue(_, String("gleshell-test-val")) = 152 eval.eval_source(env2, "$env.GLESHELL_TEST_VAR") 153 let assert Ok("gleshell-test-val") = sys.getenv("GLESHELL_TEST_VAR") 154 Nil 155} 156 157pub fn parse_env_assign_test() { 158 let assert Ok(parser.EnvAssign("FOO", parser.Pipeline([cmd]))) = 159 parser.parse("$env.FOO = hello") 160 let assert parser.Command( 161 "__value__", 162 [parser.ValueArg(parser.Lit(String("hello")))], 163 False, 164 ) = cmd 165 Nil 166} 167 168fn list_has_string_field( 169 fields: List(#(String, value.Value)), 170 key: String, 171) -> Bool { 172 case fields { 173 [] -> False 174 [#(k, String(_)), ..] if k == key -> True 175 [_, ..rest] -> list_has_string_field(rest, key) 176 } 177} 178 179pub fn eval_where_select_test() { 180 let env = env.new() 181 // build table via records in a list, convert with table 182 let assert eval.Continue(_, result) = 183 eval.eval_source( 184 env, 185 "echo [{name: a, n: 1} {name: b, n: 2} {name: c, n: 3}] | table | where n > 1 | select name", 186 ) 187 let assert Table(["name"], rows) = result 188 let assert [[String("b")], [String("c")]] = rows 189 Nil 190} 191 192pub fn eval_from_json_test() { 193 let env = env.new() 194 let assert eval.Continue(_, Record(fields)) = 195 eval.eval_source(env, "echo \"{\\\"x\\\": 1}\" | from json") 196 let assert True = list_has_field(fields, "x", Int(1)) 197 Nil 198} 199 200pub fn eval_to_json_pretty_test() { 201 let env = env.new() 202 // Nushell-style multi-word `to json` — pretty by default 203 let assert eval.Continue(_, String(pretty)) = 204 eval.eval_source(env, "echo [1 2 3] | to json") 205 let assert True = string.contains(pretty, "\n") 206 let assert True = string.contains(pretty, "1") 207 // `--raw` matches Nu: compact, no trailing newline 208 let assert eval.Continue(_, String(raw)) = 209 eval.eval_source(env, "echo [1 2 3] | to json --raw") 210 let assert "[1,2,3]" = raw 211 Nil 212} 213 214pub fn eval_to_json_record_test() { 215 let env = env.new() 216 let assert eval.Continue(_, String(raw)) = 217 eval.eval_source(env, "echo {a: 1, b: true} | to json -r") 218 let assert True = string.contains(raw, "\"a\":1") 219 let assert True = string.contains(raw, "\"b\":true") 220 Nil 221} 222 223fn list_has_field( 224 fields: List(#(String, value.Value)), 225 key: String, 226 expected: value.Value, 227) -> Bool { 228 case fields { 229 [] -> False 230 [#(k, v), ..rest] -> 231 case k == key { 232 True -> value.equals(v, expected) 233 False -> list_has_field(rest, key, expected) 234 } 235 } 236} 237 238pub fn eval_length_test() { 239 let env = env.new() 240 let assert eval.Continue(_, Int(4)) = 241 eval.eval_source(env, "range 4 | length") 242 Nil 243} 244 245pub fn value_nothing_falsey_test() { 246 let assert False = value.is_truthy(Nothing) 247 let assert True = value.is_truthy(Int(1)) 248 Nil 249} 250 251// --- display / color --- 252 253pub fn display_plain_no_ansi_test() { 254 let text = display.render_with(False, Bool(True)) 255 let assert "true" = text 256 let assert False = string_contains(text, "\u{001b}") 257 Nil 258} 259 260pub fn display_colored_has_ansi_test() { 261 let text = display.render_with(True, Bool(True)) 262 let assert True = string_contains(text, "\u{001b}") 263 let assert True = string_contains(text, "true") 264 Nil 265} 266 267pub fn display_table_headers_colored_test() { 268 let text = 269 display.render_with( 270 True, 271 Table(["name", "type"], [[String("src"), String("dir")]]), 272 ) 273 // bold green header + bright-blue dir name 274 let assert True = string_contains(text, "\u{001b}[1;32m") 275 let assert True = string_contains(text, "\u{001b}[94m") 276 let assert True = string_contains(text, "src") 277 Nil 278} 279 280pub fn color_visible_length_strips_ansi_test() { 281 let painted = color.paint(True, "\u{001b}[32m", "hi") 282 let assert 2 = color.visible_length(painted) 283 let assert 2 = color.visible_length("hi") 284 Nil 285} 286 287// --- input syntax highlighting --- 288 289pub fn highlight_plain_when_off_test() { 290 let src = "ls | where type == file" 291 let assert True = highlight.highlight(False, src) == src 292 Nil 293} 294 295pub fn highlight_pipeline_has_shapes_test() { 296 let text = highlight.highlight(True, "ls | first 3") 297 // bold cyan internalcall, bold purple pipe, bold purple int 298 let assert True = string_contains(text, "\u{001b}[1;36m") 299 let assert True = string_contains(text, "\u{001b}[1;35m") 300 let assert True = string_contains(text, "ls") 301 let assert True = string_contains(text, "first") 302 let assert True = string_contains(text, "3") 303 // visible text unchanged 304 let assert 12 = color.visible_length(text) 305 Nil 306} 307 308pub fn highlight_string_and_flag_test() { 309 let text = highlight.highlight(True, "echo \"hi\" --raw") 310 let assert True = string_contains(text, "\u{001b}[32m") 311 let assert True = string_contains(text, "\u{001b}[1;34m") 312 let assert True = string_contains(text, "hi") 313 let assert True = string_contains(text, "--raw") 314 Nil 315} 316 317pub fn highlight_variable_and_let_test() { 318 let text = highlight.highlight(True, "let x = $in") 319 let assert True = string_contains(text, "\u{001b}[1;36m") 320 let assert True = string_contains(text, "\u{001b}[35m") 321 let assert True = string_contains(text, "let") 322 let assert True = string_contains(text, "$in") 323 Nil 324} 325 326pub fn highlight_incomplete_string_test() { 327 // Unterminated string while typing should not crash 328 let text = highlight.highlight(True, "echo \"hel") 329 let assert True = string_contains(text, "hel") 330 let assert True = string_contains(text, "\u{001b}[32m") 331 Nil 332} 333 334pub fn highlight_to_json_multiword_test() { 335 let text = highlight.highlight(True, "range 3 | to json") 336 let assert True = string_contains(text, "to json") 337 let assert True = string_contains(text, "\u{001b}[1;36m") 338 Nil 339} 340 341fn string_contains(haystack: String, needle: String) -> Bool { 342 case string.split(haystack, needle) { 343 [_] -> False 344 _ -> True 345 } 346}