hachem.wtf
aboutsummaryrefslogtreecommitdiff
path: root/gils
diff options
context:
space:
mode:
Diffstat (limited to 'gils')
-rw-r--r--gils/cat.rv10
-rw-r--r--gils/cp.rv15
-rw-r--r--gils/ed.rv190
-rw-r--r--gils/gils.rv50
-rw-r--r--gils/head.rv27
-rw-r--r--gils/ls.rv10
-rw-r--r--gils/mkdir.rv5
-rw-r--r--gils/mv.rv14
-rw-r--r--gils/rm.rv6
-rw-r--r--gils/tail.rv29
-rw-r--r--gils/touch.rv9
-rw-r--r--gils/tree.rv35
-rw-r--r--gils/wc.rv22
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