aboutsummaryrefslogtreecommitdiff
path: root/lib/commands/commands.tm
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2025-04-07 18:14:20 -0400
committerBruce Hill <bruce@bruce-hill.com>2025-04-07 18:14:20 -0400
commit3efd7d9cfbd330ebb45f39648ee96a3e429a06f9 (patch)
tree29acb9e2b2370bd155fed24fba79d01b553a24f3 /lib/commands/commands.tm
parent15fabfb9be3e3620e4b96983a49017116cea40e2 (diff)
Move core libraries into their own folder
Diffstat (limited to 'lib/commands/commands.tm')
-rw-r--r--lib/commands/commands.tm91
1 files changed, 91 insertions, 0 deletions
diff --git a/lib/commands/commands.tm b/lib/commands/commands.tm
new file mode 100644
index 00000000..d72398b9
--- /dev/null
+++ b/lib/commands/commands.tm
@@ -0,0 +1,91 @@
+# Functions for running system commands
+
+use ./commands.c
+use -lunistring
+
+extern run_command : func(exe:Text, args:[Text], env:{Text=Text}, input:[Byte]?, output:&[Byte]?, error:&[Byte]? -> Int32)
+extern command_by_line : func(exe:Text, args:[Text], env:{Text=Text} -> func(->Text?)?)
+
+enum ExitType(Exited(status:Int32), Signaled(signal:Int32), Failed)
+ func succeeded(e:ExitType -> Bool)
+ when e is Exited(status) return (status == 0)
+ else return no
+
+ func or_fail(e:ExitType, message:Text?=none)
+ if not e.succeeded()
+ fail(message or "Program failed: $e")
+
+struct ProgramResult(stdout:[Byte], stderr:[Byte], exit_type:ExitType)
+ func or_fail(r:ProgramResult, message:Text?=none -> ProgramResult)
+ when r.exit_type is Exited(status)
+ if status == 0
+ return r
+ else fail(message or "Program failed: $r")
+ fail(message or "Program failed: $r")
+
+ func output_text(r:ProgramResult, trim_newline=yes -> Text?)
+ when r.exit_type is Exited(status)
+ if status == 0
+ if text := Text.from_bytes(r.stdout)
+ if trim_newline
+ text = text.without_suffix("\n")
+ return text
+ else return none
+ return none
+
+ func error_text(r:ProgramResult -> Text?)
+ when r.exit_type is Exited(status)
+ if status == 0
+ return Text.from_bytes(r.stderr)
+ else return none
+ return none
+
+ func succeeded(r:ProgramResult -> Bool)
+ when r.exit_type is Exited(status)
+ return (status == 0)
+ else
+ return no
+
+struct Command(command:Text, args:[Text]=[], env:{Text=Text}={})
+ func from_path(path:Path, args:[Text]=[], env:{Text=Text}={} -> Command)
+ return Command(Text(path), args, env)
+
+ func result(command:Command, input="", input_bytes:[Byte]=[] -> ProgramResult)
+ if input.length > 0
+ (&input_bytes).insert_all(input.bytes())
+
+ stdout : [Byte]
+ stderr : [Byte]
+ status := run_command(command.command, command.args, command.env, input_bytes, &stdout, &stderr)
+
+ if C_code:Bool(WIFEXITED(_$status))
+ return ProgramResult(stdout, stderr, ExitType.Exited(C_code:Int32(WEXITSTATUS(_$status))))
+
+ if C_code:Bool(WIFSIGNALED(_$status))
+ return ProgramResult(stdout, stderr, ExitType.Signaled(C_code:Int32(WTERMSIG(_$status))))
+
+ return ProgramResult(stdout, stderr, ExitType.Failed)
+
+ func run(command:Command, -> ExitType)
+ status := run_command(command.command, command.args, command.env, none, none, none)
+
+ if C_code:Bool(WIFEXITED(_$status))
+ return ExitType.Exited(C_code:Int32(WEXITSTATUS(_$status)))
+
+ if C_code:Bool(WIFSIGNALED(_$status))
+ return ExitType.Signaled(C_code:Int32(WTERMSIG(_$status)))
+
+ return ExitType.Failed
+
+ func get_output(command:Command, input="", trim_newline=yes -> Text?)
+ return command.result(input=input).output_text(trim_newline=trim_newline)
+
+ func get_output_bytes(command:Command, input="", input_bytes:[Byte]=[] -> [Byte]?)
+ result := command.result(input=input, input_bytes=input_bytes)
+ when result.exit_type is Exited(status)
+ if status == 0 return result.stdout
+ return none
+ else return none
+
+ func by_line(command:Command -> func(->Text?)?)
+ return command_by_line(command.command, command.args, command.env)