2025-03-17 16:29:28 -07:00
|
|
|
# Functions for running system commands
|
|
|
|
|
|
|
|
use ./commands.c
|
2025-03-30 12:41:37 -07:00
|
|
|
use -lunistring
|
2025-03-17 16:29:28 -07:00
|
|
|
|
2025-04-06 11:20:18 -07:00
|
|
|
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?)?)
|
2025-03-17 16:29:28 -07:00
|
|
|
|
2025-04-06 13:07:23 -07:00
|
|
|
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
|
2025-03-24 12:20:19 -07:00
|
|
|
|
2025-04-06 13:07:23 -07:00
|
|
|
func or_fail(e:ExitType, message:Text?=none)
|
|
|
|
if not e.succeeded()
|
2025-03-24 12:23:29 -07:00
|
|
|
fail(message or "Program failed: $e")
|
2025-03-17 16:29:28 -07:00
|
|
|
|
2025-04-06 13:07:23 -07:00
|
|
|
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
|
2025-03-17 16:29:28 -07:00
|
|
|
return r
|
2025-04-06 13:07:23 -07:00
|
|
|
else fail(message or "Program failed: $r")
|
2025-03-24 12:23:29 -07:00
|
|
|
fail(message or "Program failed: $r")
|
2025-03-17 16:29:28 -07:00
|
|
|
|
2025-04-06 13:07:23 -07:00
|
|
|
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
|
2025-04-06 19:26:12 -07:00
|
|
|
text = text.without_suffix("\n")
|
2025-03-17 16:29:28 -07:00
|
|
|
return text
|
2025-04-06 13:07:23 -07:00
|
|
|
else return none
|
2025-03-17 16:29:28 -07:00
|
|
|
return none
|
|
|
|
|
2025-04-06 13:07:23 -07:00
|
|
|
func error_text(r:ProgramResult -> Text?)
|
|
|
|
when r.exit_type is Exited(status)
|
|
|
|
if status == 0
|
2025-03-17 16:29:28 -07:00
|
|
|
return Text.from_bytes(r.stderr)
|
2025-04-06 13:07:23 -07:00
|
|
|
else return none
|
2025-03-17 16:29:28 -07:00
|
|
|
return none
|
|
|
|
|
2025-04-06 13:07:23 -07:00
|
|
|
func succeeded(r:ProgramResult -> Bool)
|
|
|
|
when r.exit_type is Exited(status)
|
2025-03-21 15:07:17 -07:00
|
|
|
return (status == 0)
|
2025-04-06 13:07:23 -07:00
|
|
|
else
|
2025-03-21 15:07:17 -07:00
|
|
|
return no
|
|
|
|
|
2025-04-06 13:07:23 -07:00
|
|
|
struct Command(command:Text, args:[Text]=[], env:{Text=Text}={})
|
|
|
|
func from_path(path:Path, args:[Text]=[], env:{Text=Text}={} -> Command)
|
2025-03-17 18:22:10 -07:00
|
|
|
return Command(Text(path), args, env)
|
|
|
|
|
2025-04-06 13:07:23 -07:00
|
|
|
func result(command:Command, input="", input_bytes:[Byte]=[] -> ProgramResult)
|
|
|
|
if input.length > 0
|
2025-04-06 11:20:18 -07:00
|
|
|
(&input_bytes).insert_all(input.bytes())
|
2025-03-17 16:29:28 -07:00
|
|
|
|
2025-04-06 16:20:07 -07:00
|
|
|
stdout : [Byte]
|
|
|
|
stderr : [Byte]
|
2025-03-17 16:29:28 -07:00
|
|
|
status := run_command(command.command, command.args, command.env, input_bytes, &stdout, &stderr)
|
|
|
|
|
2025-04-06 18:43:19 -07:00
|
|
|
if C_code:Bool(WIFEXITED(_$status))
|
|
|
|
return ProgramResult(stdout, stderr, ExitType.Exited(C_code:Int32(WEXITSTATUS(_$status))))
|
2025-03-17 16:29:28 -07:00
|
|
|
|
2025-04-06 18:43:19 -07:00
|
|
|
if C_code:Bool(WIFSIGNALED(_$status))
|
|
|
|
return ProgramResult(stdout, stderr, ExitType.Signaled(C_code:Int32(WTERMSIG(_$status))))
|
2025-03-17 16:29:28 -07:00
|
|
|
|
|
|
|
return ProgramResult(stdout, stderr, ExitType.Failed)
|
|
|
|
|
2025-04-06 13:07:23 -07:00
|
|
|
func run(command:Command, -> ExitType)
|
2025-03-24 12:16:56 -07:00
|
|
|
status := run_command(command.command, command.args, command.env, none, none, none)
|
|
|
|
|
2025-04-06 18:43:19 -07:00
|
|
|
if C_code:Bool(WIFEXITED(_$status))
|
|
|
|
return ExitType.Exited(C_code:Int32(WEXITSTATUS(_$status)))
|
2025-03-24 12:16:56 -07:00
|
|
|
|
2025-04-06 18:43:19 -07:00
|
|
|
if C_code:Bool(WIFSIGNALED(_$status))
|
|
|
|
return ExitType.Signaled(C_code:Int32(WTERMSIG(_$status)))
|
2025-03-24 12:16:56 -07:00
|
|
|
|
|
|
|
return ExitType.Failed
|
|
|
|
|
2025-04-06 13:07:23 -07:00
|
|
|
func get_output(command:Command, input="", trim_newline=yes -> Text?)
|
2025-04-06 11:20:18 -07:00
|
|
|
return command.result(input=input).output_text(trim_newline=trim_newline)
|
2025-03-17 16:29:28 -07:00
|
|
|
|
2025-04-06 13:07:23 -07:00
|
|
|
func get_output_bytes(command:Command, input="", input_bytes:[Byte]=[] -> [Byte]?)
|
2025-04-06 11:20:18 -07:00
|
|
|
result := command.result(input=input, input_bytes=input_bytes)
|
2025-04-06 13:07:23 -07:00
|
|
|
when result.exit_type is Exited(status)
|
|
|
|
if status == 0 return result.stdout
|
2025-03-17 16:29:28 -07:00
|
|
|
return none
|
2025-04-06 13:07:23 -07:00
|
|
|
else return none
|
2025-03-17 16:29:28 -07:00
|
|
|
|
2025-04-06 13:07:23 -07:00
|
|
|
func by_line(command:Command -> func(->Text?)?)
|
2025-03-19 13:17:44 -07:00
|
|
|
return command_by_line(command.command, command.args, command.env)
|