tomo/examples/commands/commands.tm
2025-03-30 15:41:37 -04:00

92 lines
3.5 KiB
Tcl

# 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=none:Text):
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=none:Text -> 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:trim($/{1 nl}/, trim_left=no, trim_right=yes)
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 inline C : Bool { WIFEXITED(_$status) }:
return ProgramResult(stdout, stderr, ExitType.Exited(inline C : Int32 { WEXITSTATUS(_$status) }))
if inline C : Bool { WIFSIGNALED(_$status) }:
return ProgramResult(stdout, stderr, ExitType.Signaled(inline C : 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 inline C : Bool { WIFEXITED(_$status) }:
return ExitType.Exited(inline C : Int32 { WEXITSTATUS(_$status) })
if inline C : Bool { WIFSIGNALED(_$status) }:
return ExitType.Signaled(inline C : 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)