Download
96 lines 3.4 KiBTomoModified 2025-11-29 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
# Functions for running system commands

use ./commands.c

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(output:[Byte], errors:[Byte], exit_type:ExitType)
    func or_fail(r:ProgramResult, message:Text?=none -> ProgramResult)
        when r.exit_type is Exited(status)
            if status != 0
                fail(message or "Program failed: $r")
        else fail(message or "Program failed: $r")
        return r

    func output_text(r:ProgramResult, trim_newline=yes -> Text?)
        when r.exit_type is Exited(status)
            if status == 0
                if text := Text.from_utf8(r.output)
                    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_utf8(r.errors)
        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.utf8())

        output : [Byte]
        errors : [Byte]
        status := C_code:Int32`
            run_command(@(command.command), @(command.args), @(command.env), @input_bytes, &@output, &@errors)
        `

        if C_code:Bool`WIFEXITED(@status)`
            return ProgramResult(output, errors, ExitType.Exited(C_code:Int32`WEXITSTATUS(@status)`))

        if C_code:Bool`WIFSIGNALED(@status)`
            return ProgramResult(output, errors, ExitType.Signaled(C_code:Int32`WTERMSIG(@status)`))

        return ProgramResult(output, errors, ExitType.Failed)

    func run(command:Command, -> ExitType)
        status := C_code:Int32`
            run_command(@(command.command), @(command.args), @(command.env), NONE_LIST, NULL, NULL)
        `

        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.output
            return none
        else return none

    func by_line(command:Command -> func(->Text?)?)
        return C_code:func(->Text?)?`
            command_by_line(@(command.command), @(command.args), @(command.env))
        `

func main(cmd:Command)
    >> cmd.get_output()