diff options
| author | hachem <im@hachem.wtf> | 2026-10-06 12:49:46 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-10-06 12:49:46 +0200 |
| commit | 5d4a723a5f157c0ed38a348099a600d1d21c3c2b (patch) | |
| tree | 70e5fdf560a64cd0eb3a13d88ce046ddd29fd077 /gils | |
| parent | 52dab7fc85d877e80e3032f0afabae906adebad8 (diff) | |
feat: gils core utils and other shit
Diffstat (limited to 'gils')
| -rw-r--r-- | gils/cat.rv | 10 | ||||
| -rw-r--r-- | gils/cp.rv | 15 | ||||
| -rw-r--r-- | gils/ed.rv | 190 | ||||
| -rw-r--r-- | gils/gils.rv | 50 | ||||
| -rw-r--r-- | gils/head.rv | 27 | ||||
| -rw-r--r-- | gils/ls.rv | 10 | ||||
| -rw-r--r-- | gils/mkdir.rv | 5 | ||||
| -rw-r--r-- | gils/mv.rv | 14 | ||||
| -rw-r--r-- | gils/rm.rv | 6 | ||||
| -rw-r--r-- | gils/tail.rv | 29 | ||||
| -rw-r--r-- | gils/touch.rv | 9 | ||||
| -rw-r--r-- | gils/tree.rv | 35 | ||||
| -rw-r--r-- | gils/wc.rv | 22 |
13 files changed, 422 insertions, 0 deletions
diff --git a/gils/cat.rv b/gils/cat.rv new file mode 100644 index 0000000..1cca54b --- /dev/null +++ b/gils/cat.rv @@ -0,0 +1,10 @@ +# meow + +const context = env.ctx +const parsed = arg_name(context.rest, 0) +if parsed.name:len() == 0 do puts("usage: cat <path>\n") end +else do + const path = abspath(parsed.name) + if vfs_owns(path) do vfs_cat(path) end + else do fs:cat(path) end +end diff --git a/gils/cp.rv b/gils/cp.rv new file mode 100644 index 0000000..5988e54 --- /dev/null +++ b/gils/cp.rv @@ -0,0 +1,15 @@ +# copy a file not cyberpunk +# HAAAAAANNNNKKKK + +const context = env.ctx +if context.argv:len() < 3 do puts("usage: cp <source> <destination>\n") end +else do + const source = abspath(context.argv[1]) + const destination = abspath(context.argv[2]) + const size = fs:size(source) + if size < 0 do puts("cp: #{source}: no such file\n") end + else do + const contents = if size > 0 fs:read_bytes(source, 0, size) else "" + if fs:store(destination, contents) == 1 do puts("#{source} -> #{destination}\n") end + end +end diff --git a/gils/ed.rv b/gils/ed.rv new file mode 100644 index 0000000..00ff50a --- /dev/null +++ b/gils/ed.rv @@ -0,0 +1,190 @@ +# simple ass modal ass line ass editor + +fn list_insert(items, index, value) do + let result = {} + for position in 0..index do result:push(items[position]) end + result:push(value) + for position in index..items:len() do result:push(items[position]) end + return result +end + +fn list_delete(items, low, high) do + let result = {} + for position in 0..items:len() do if (position < low or position > high) result:push(items[position]) end + return result +end + +fn ed_is_digit(character) do + const code = character:ascii() + return code >= 48 and code <= 57 +end + +# parse one address at index +fn ed_addr(state, line, index) do + if index >= line:len() return { value = -1, next = index } + if line[index] == "." return { value = state.dot, next = index + 1 } + if line[index] == "$" return { value = state.lines:len() - 1, next = index + 1 } + if ed_is_digit(line[index]) do + let number = 0 + let scan = index + while (scan < line:len() and ed_is_digit(line[scan])) do number = number * 10 + (line[scan]:ascii() - 48) scan += 1 end + return { value = number - 1, next = scan } + end + return { value = -1, next = index } +end + +# split a command line into low, high and command +fn ed_parse(state, line) do + let index = 0 + let low = -1 + let high = -1 + if (line:len() > 0 and (line[0] == "," or line[0] == "%")) do + low = 0 + high = state.lines:len() - 1 + index = 1 + end + else do + const first_addr = ed_addr(state, line, 0) + if first_addr.value >= 0 do low = first_addr.value index = first_addr.next end + if (index < line:len() and line[index] == ",") do + index += 1 + const second_addr = ed_addr(state, line, index) + if second_addr.value >= 0 do high = second_addr.value index = second_addr.next end + else do high = state.lines:len() - 1 end + end + end + if low < 0 do low = state.dot end + if high < 0 do high = low end + const command = if index < line:len() line[index..] else "" + return { low = low, high = high, command = command } +end + +fn ed_print(state, low, high, numbered) do + let index = low + while index <= high do + if (index >= 0 and index < state.lines:len()) do + if numbered do puts("#{index + 1}\t#{state.lines[index]}\n") end + else do puts("#{state.lines[index]}\n") end + end + index += 1 + end + if (high >= 0 and high < state.lines:len()) do state.dot = high end +end + +fn ed_goto(state, low) do + if state.lines:len() == 0 do puts("?\n") return :nil end + let target = low + if target < 0 do target = 0 end + if target >= state.lines:len() do target = state.lines:len() - 1 end + state.dot = target + puts("#{state.lines[target]}\n") +end + +fn ed_begin_input(state, position) do + let clamped = position + if clamped < 0 do clamped = 0 end + if clamped > state.lines:len() do clamped = state.lines:len() end + state.input = :true + state.insert_at = clamped +end + +fn ed_delete(state, low, high) do + state.lines = list_delete(state.lines, low, high) + state.dirty = :true + let new_dot = low + if new_dot >= state.lines:len() do new_dot = state.lines:len() - 1 end + if new_dot < 0 do new_dot = 0 end + state.dot = new_dot +end + +fn ed_change(state, low, high) do + ed_delete(state, low, high) + ed_begin_input(state, low) +end + +fn ed_join(items) do + let result = "" + for index in 0..items:len() do result = result ~ items[index] ~ "\n" end + return result +end + +fn ed_write(state, command) do + let path = state.path + if command:len() > 1 do + const filename = command[1..]:trim() + if filename:len() > 0 do path = abspath(filename) end + end + if path:len() == 0 do puts("?\n") return :nil end + const contents = ed_join(state.lines) + fs:store(path, contents) + state.path = path + state.dirty = :false + puts("#{contents:len()}\n") +end + +fn ed_quit(state) do + state.active = :false + puts("% ") +end + +global ed_state = { active = :false, input = :false, path = "", lines = {}, dot = 0, insert_at = 0, dirty = :false } + +fn gils_ed(context) do + ed_state.active = :true + ed_state.input = :false + ed_state.dirty = :false + ed_state.lines = {} + ed_state.path = if context.argv:len() > 1 abspath(context.argv[1]) else "" + if ed_state.path:len() > 0 do + const size = fs:size(ed_state.path) + if size > 0 do + const contents = fs:read_bytes(ed_state.path, 0, size) + const raw_lines = contents:split("\n") + for index in 0..raw_lines:len() do + if (index < raw_lines:len() - 1 or raw_lines[index]:len() > 0) do ed_state.lines:push(raw_lines[index]) end + end + puts("#{size}\n") + end + else do puts("#{ed_state.path}: new file\n") end + end + ed_state.dot = if ed_state.lines:len() > 0 ed_state.lines:len() - 1 else 0 + return :hold +end + +fn gils_ed_feed(line) do + const state = ed_state + if state.input do + if line == "." do state.input = :false end + else do + state.lines = list_insert(state.lines, state.insert_at, line) + state.insert_at += 1 + state.dot = state.insert_at - 1 + state.dirty = :true + end + return :nil + end + if line:len() == 0 do + if state.lines:len() == 0 do puts("?\n") return :nil end + if state.dot < state.lines:len() - 1 do state.dot += 1 end + puts("#{state.lines[state.dot]}\n") + return :nil + end + const parsed = ed_parse(state, line) + const command_char = if parsed.command:len() > 0 parsed.command[0] else "" + match command_char + | "p" => ed_print(state, parsed.low, parsed.high, :false) + | "n" => ed_print(state, parsed.low, parsed.high, :true) + | "a" => ed_begin_input(state, parsed.low + 1) + | "i" => ed_begin_input(state, parsed.low) + | "c" => ed_change(state, parsed.low, parsed.high) + | "d" => ed_delete(state, parsed.low, parsed.high) + | "w" => ed_write(state, parsed.command) + | "q" => ed_quit(state) + | "=" => puts("#{state.dot + 1}\n") + | "" => ed_goto(state, parsed.low) + | _ => puts("?\n") +end + +fn gils_ed_active() ed_state.active + +register("ed [path]", "gils", "the standard editor (a i d p w q)", gils_ed) diff --git a/gils/gils.rv b/gils/gils.rv new file mode 100644 index 0000000..1a61cc5 --- /dev/null +++ b/gils/gils.rv @@ -0,0 +1,50 @@ +# gils: better than Gnu utILS +global commands = {} + +fn register(usage, group, help, handler) do + const name = usage:split(" ")[0] + commands:push({ name = name, usage = usage, group = group, help = help, handler = handler }) +end + +fn lookup(name) do + for index in 0..commands:len() do + if commands[index].name == name return commands[index] + end + return :nil +end + +fn make_ctx(line, argv) do + const name = argv[0] + let rest = "" + const cut = name:len() + 1 + if line:len() > cut do rest = line[cut..] end + return { line = line, argv = argv, rest = rest } +end + +# scripts read their args off env.ctx, stashed here before eval +global env = { ctx = :nil } + +# working dir + path resolver, shared by every gils tool and the shell. lives +# here so its declared before anything that uses it gets concatenated +global wd = { path = "/" } + +global abspath = fn(arg) do + if arg:len() == 0 return wd.path + let path = arg + if arg[0] != "/" do + if wd.path == "/" do path = "/#{arg}" end + else do path = "#{wd.path}/#{arg}" end + end + while (path:len() > 1 and path[path:len() - 1] == "/") do path = path[0..(path:len() - 1)] end + return path +end + +fn run_script(name, ctx) do + const path = "/bin/#{name}.rv" + const size = fs:size(path) + if size < 0 return :false + env.ctx = ctx + const error_msg = eval_script(fs:read_bytes(path, 0, size)) + if error_msg != :nil do puts("#{name}: #{error_msg}\n") end + return :true +end diff --git a/gils/head.rv b/gils/head.rv new file mode 100644 index 0000000..edfd096 --- /dev/null +++ b/gils/head.rv @@ -0,0 +1,27 @@ +# gimme some head lung + +fn file_lines(path) do + const size = fs:size(path) + if size < 0 return :nil + const contents = if size > 0 fs:read_bytes(path, 0, size) else "" + const raw_lines = contents:split("\n") + let result = {} + for index in 0..raw_lines:len() do + if (index < raw_lines:len() - 1 or raw_lines[index]:len() > 0) result:push(raw_lines[index]) + end + return result +end + +const context = env.ctx +if context.argv:len() < 2 do puts("usage: head [n] <path>\n") end +else do + let limit = 10 + let path_arg = context.argv[1] + if (context.argv:len() >= 3 and parse_pid(context.argv[1]) >= 0) do limit = parse_pid(context.argv[1]) path_arg = context.argv[2] end + const lines = file_lines(abspath(path_arg)) + if lines == :nil do puts("head: no such file\n") end + else do + let index = 0 + while (index < lines:len() and index < limit) do puts("#{lines[index]}\n") index += 1 end + end +end diff --git a/gils/ls.rv b/gils/ls.rv new file mode 100644 index 0000000..f7b9cf7 --- /dev/null +++ b/gils/ls.rv @@ -0,0 +1,10 @@ +# list a directory + +const context = env.ctx +const target = if context.argv:len() > 1 abspath(context.argv[1]) else wd.path +if vfs_owns(target) do vfs_ls(target) end +else do + const count = fs:ls(target) + if target == "/" do puts(" proc/\n") end + else if count == 0 do puts(" (empty)\n") end +end diff --git a/gils/mkdir.rv b/gils/mkdir.rv new file mode 100644 index 0000000..cb630d1 --- /dev/null +++ b/gils/mkdir.rv @@ -0,0 +1,5 @@ +# make a directory + +const context = env.ctx +if context.argv:len() < 2 do puts("usage: mkdir <path>\n") end +else do fs:mkdir(abspath(context.argv[1])) end diff --git a/gils/mv.rv b/gils/mv.rv new file mode 100644 index 0000000..95d9a40 --- /dev/null +++ b/gils/mv.rv @@ -0,0 +1,14 @@ +# move or rename a file + +const context = env.ctx +if context.argv:len() < 3 do puts("usage: mv <source> <destination>\n") end +else do + const source = abspath(context.argv[1]) + const destination = abspath(context.argv[2]) + const size = fs:size(source) + if size < 0 do puts("mv: #{source}: no such file\n") end + else do + const contents = if size > 0 fs:read_bytes(source, 0, size) else "" + if fs:store(destination, contents) == 1 do fs:unlink(source) puts("#{source} -> #{destination}\n") end + end +end diff --git a/gils/rm.rv b/gils/rm.rv new file mode 100644 index 0000000..92e21c7 --- /dev/null +++ b/gils/rm.rv @@ -0,0 +1,6 @@ +# delete a file or an empty directory + +const context = env.ctx +const parsed = arg_name(context.rest, 0) +if parsed.name:len() == 0 do puts("usage: rm <path>\n") end +else do fs:rm(abspath(parsed.name)) end diff --git a/gils/tail.rv b/gils/tail.rv new file mode 100644 index 0000000..9cd5647 --- /dev/null +++ b/gils/tail.rv @@ -0,0 +1,29 @@ +# my tail is 16 inches long +# and its between my legs + +fn file_lines(path) do + const size = fs:size(path) + if size < 0 return :nil + const contents = if size > 0 fs:read_bytes(path, 0, size) else "" + const raw_lines = contents:split("\n") + let result = {} + for index in 0..raw_lines:len() do + if (index < raw_lines:len() - 1 or raw_lines[index]:len() > 0) result:push(raw_lines[index]) + end + return result +end + +const context = env.ctx +if context.argv:len() < 2 do puts("usage: tail [n] <path>\n") end +else do + let limit = 10 + let path_arg = context.argv[1] + if (context.argv:len() >= 3 and parse_pid(context.argv[1]) >= 0) do limit = parse_pid(context.argv[1]) path_arg = context.argv[2] end + const lines = file_lines(abspath(path_arg)) + if lines == :nil do puts("tail: no such file\n") end + else do + let start = lines:len() - limit + if start < 0 do start = 0 end + for index in start..lines:len() do puts("#{lines[index]}\n") end + end +end diff --git a/gils/touch.rv b/gils/touch.rv new file mode 100644 index 0000000..2dd0d8c --- /dev/null +++ b/gils/touch.rv @@ -0,0 +1,9 @@ +# dont touch my nono square + +const context = env.ctx +if context.argv:len() < 2 do puts("usage: touch <path>\n") end +else do + const path = abspath(context.argv[1]) + if fs:is_dir(path) do puts("touch: #{path}: is a directory\n") end + else if fs:size(path) < 0 do fs:store(path, "") end +end diff --git a/gils/tree.rv b/gils/tree.rv new file mode 100644 index 0000000..b052adb --- /dev/null +++ b/gils/tree.rv @@ -0,0 +1,35 @@ +# recursive directory listing. + +fn tree_walk(path, prefix) do + const items = fs:entries(path) + if items == :nil return :nil + for index in 0..items:len() do + const entry = items[index] + const last = index == items:len() - 1 + let branch = "|-- " + if last do branch = "`-- " end # font doesnt support fancy ascii symbols + const slash = if entry.type == 2 "/" else "" + puts("#{prefix}#{branch}#{entry.name}#{slash}\n") + if entry.type == 2 do + const child = if path == "/" "/#{entry.name}" else "#{path}/#{entry.name}" + let indent = "| " + if last do indent = " " end + tree_walk(child, prefix ~ indent) + end + end +end + +const context = env.ctx +const target = if context.argv:len() > 1 abspath(context.argv[1]) else wd.path +if (vfs_owns(target) and target != "/") do puts("tree: #{target}: virtual dir\n") end +else if (target != "/" and fs:is_dir(target) == :false) do puts("tree: #{target}: not a directory\n") end +else do + puts("#{target}\n") + tree_walk(target, "") + # /proc is the one thing not on disk so tack it on at root + if target == "/" do + puts("`-- proc/\n") + puts(" |-- uptime\n") + puts(" `-- meminfo\n") + end +end diff --git a/gils/wc.rv b/gils/wc.rv new file mode 100644 index 0000000..2f8be3d --- /dev/null +++ b/gils/wc.rv @@ -0,0 +1,22 @@ +# word count + +const context = env.ctx +if context.argv:len() < 2 do puts("usage: wc <path>\n") end +else do + const path = abspath(context.argv[1]) + const size = fs:size(path) + if size < 0 do puts("wc: #{path}: no such file\n") end + else do + const contents = if size > 0 fs:read_bytes(path, 0, size) else "" + let line_count = 0 + let word_count = 0 + let in_word = :false + for index in 0..contents:len() do + const character = contents[index] + if character == "\n" do line_count += 1 end + if (character == " " or character == "\n" or character == "\t") do in_word = :false end + else if in_word == :false do word_count += 1 in_word = :true end + end + puts(" #{line_count} #{word_count} #{size} #{context.argv[1]}\n") + end +end |
