On this page
  1. tecs.io.Process
  2. Module contents
    1. Constructors
    2. Types
    3. Functions
    4. Values
  3. Constructors
    1. new
  4. Types
    1. CommunicateOptions
    2. ErrorMode
    3. Exit
    4. InputMode
    5. Options
    6. OutputMode
    7. Reader
    8. Result
    9. Writer
  5. Functions
    1. Process:communicate
    2. Process:isRunning
    3. Process:kill
    4. Process:wait
  6. Values
    1. pid
    2. stderr
    3. stdin
    4. stdout

tecs.io.Process

Streaming child processes.

new starts the child immediately and returns its live standard-stream endpoints. Piped stdin is a Writer; piped stdout and stderr are Reader values. The process owns all three endpoints. Closing one endpoint closes only that pipe; closing the process closes every endpoint, terminates a live child, and reaps it. Buffers and byte views passed to an endpoint remain caller-owned.

Process:communicate is the complete-exchange form for tools and build steps. It feeds stdin while draining stdout and stderr concurrently, so neither output pipe can fill and deadlock the child:

tecs.scoped(
    "read child output",
    function(scope: tecs.Scope)
        local child <const> = scope:own(tecs.io.Process.new({
            args = {"git", "status", "--porcelain"},
            timeoutMs = 2000,
        }))

        local result, communicateReason = child:communicate()
        if result == nil then
            error(communicateReason)
        end

        if result:succeeded() then
            print(result.output)
        else
            io.stderr:write(result.errorOutput)
        end
    end
)

Interactive children use the same reader and writer vocabulary as files, buffers, and transforms. Closing stdin sends EOF:

tecs.scoped(
    "communicate with child",
    function(scope: tecs.Scope)
        local child <const> = scope:own(tecs.io.Process.new({
            args = {"/bin/cat"}
        }))

        local written, writeReason = child.stdin:write("one request\n")
        if written == nil then
            error(writeReason)
        end
        child.stdin:close()

        local reply, readReason = child.stdout:read(4096)
        if reply == nil then
            error(readReason)
        end
        print(reply)
    end
)

Process:wait, pipe reads and writes, and communicate all use contextual waiting. They suspend a normal system without blocking SDL and block when called outside an update. Buffer and byte-view variants avoid constructing Lua strings.

A pipe read waits at most the reader's timeout, thirty seconds by default and Reader:setTimeout to change it, and then returns nil and a reason. Reading one pipe at a time deadlocks against a child that writes to both: the pipe the caller ignores fills, the child stops, and the pipe the caller reads never produces another byte. communicate drains both and is the form to reach for whenever a child writes standard error at all.

Use "inherit" for a CLI that should share the terminal, "null" to discard a stream, and stderr = "stdout" for one ordered transcript. A nonzero child exit still returns an Exit; succeeded performs the separate exit-code check. Spawn failures return nil and a reason. Deadlines and explicit kills set the exit's killed fields.

Module contents

Constructors

Constructor Description
new Starts a caller-owned child process with streaming standard I/O.

Types

Type Kind Description
CommunicateOptions record CommunicateOptions controls a complete duplex exchange.
ErrorMode enum ErrorMode selects where a child writes standard error.
Exit record Exit describes how a child stopped.
InputMode enum InputMode selects where a child reads standard input.
Options record Options configures a child and its standard streams.
OutputMode enum OutputMode selects where a child writes standard output.
Reader interface Reader reads one live child-process output pipe.
Result record Result contains a completed duplex exchange.
Writer interface Writer writes one live child-process input pipe.

Functions

Function Kind Description
communicate Instance Feeds stdin and captures stdout and stderr without pipe deadlock.
isRunning Instance Returns whether the child has not stopped yet.
kill Instance Requests that the child terminate.
wait Instance Waits for the child to stop and returns its exit description.

Values

Value Type Description
pid integer Read-only. Reports the operating-system process identifier.
stderr Reader Read-only. Provides standard error when its mode is "pipe", or nil when error output is inherited, discarded, or...
stdin Writer Read-only. Provides standard input when its mode is "pipe", or nil when input is inherited or discarded.
stdout Reader Read-only. Provides standard output when its mode is "pipe", or nil when output is inherited or discarded.

Constructors

tecs.io.Process.new Static

Starts a caller-owned child process with streaming standard I/O.

Piped endpoints use contextual waiting through the ordinary Reader and Writer methods. The process owns them, while buffers and byte views passed to endpoint methods remain caller-owned.

function tecs.io.Process.new(options: Options): Process, string

Arguments

Name Type Description
options Options The caller supplies the program, arguments, environment, working directory, standard-stream modes, and optional deadline.

Returns

Type Description
Process Returns a caller-owned process when the platform starts it.
string Returns the platform reason when the first return is nil.

Examples

tecs.scoped(
    "inspect repository",
    function(scope: tecs.Scope)
        local child <const> = scope:own(tecs.io.Process.new({
            args = {"git", "status", "--porcelain"},
            cwd = tecs.io.Path.new("workspace"),
        }))

        local result, communicateReason = child:communicate()

        assert(result, communicateReason)
    end
)
tecs.scoped(
    "read revision",
    function(scope: tecs.Scope)
        local child <const> = scope:own(tecs.io.Process.new({
            args = {"git", "rev-parse", "HEAD"},
            timeoutMs = 2000,
        }))

        local result, communicateReason = child:communicate()

        assert(result, communicateReason)
        assert(result:succeeded())
        print(result.output)
    end
)

Types

tecs.io.Process.CommunicateOptions record

CommunicateOptions controls a complete duplex exchange.

record tecs.io.Process.CommunicateOptions
    input: string | ByteView
    maxOutputBytes: integer
end

tecs.io.Process.CommunicateOptions.input field

Caller-writable. Supplies complete standard-input bytes as a string, open Buffer, or open ByteView. Omitted input sends EOF immediately.

tecs.io.Process.CommunicateOptions.input: string | ByteView

tecs.io.Process.CommunicateOptions.maxOutputBytes field

Caller-writable. Limits stdout and stderr together. Defaults to 268,435,456 bytes. Exceeding it forcibly terminates the child and returns a failure.

tecs.io.Process.CommunicateOptions.maxOutputBytes: integer

tecs.io.Process.ErrorMode enum

ErrorMode selects where a child writes standard error.

enum tecs.io.Process.ErrorMode
    "inherit"
    "null"
    "pipe"
    "stdout"
end

tecs.io.Process.Exit record

Exit describes how a child stopped.

record tecs.io.Process.Exit
    exitCode: integer
    killed: boolean
    timedOut: boolean

    succeeded: function(self): boolean
end

tecs.io.Process.Exit.exitCode field

Read-only. Reports the platform exit code, or the platform's best answer after termination.

tecs.io.Process.Exit.exitCode: integer

tecs.io.Process.Exit.killed field

Read-only. Reports whether Tecs requested termination.

tecs.io.Process.Exit.killed: boolean

tecs.io.Process.Exit.timedOut field

Read-only. Reports whether the configured deadline requested that termination.

tecs.io.Process.Exit.timedOut: boolean

tecs.io.Process.Exit:succeeded Instance

Returns whether the child exited normally with code zero.

function tecs.io.Process.Exit.succeeded(self): boolean
Arguments
Name Type Description
self ProcessExit The completed exit to inspect.
Returns
Type Description
boolean Returns true only for a normal zero exit.

tecs.io.Process.InputMode enum

InputMode selects where a child reads standard input.

enum tecs.io.Process.InputMode
    "inherit"
    "null"
    "pipe"
end

tecs.io.Process.Options record

Options configures a child and its standard streams.

record tecs.io.Process.Options
    args: {string}
    cwd: string | Path
    env: {string: string}
    clearEnv: boolean
    stdin: ProcessInputMode
    stdout: ProcessOutputMode
    stderr: ProcessErrorMode
    timeoutMs: integer
end

tecs.io.Process.Options.args field

Caller-writable. Supplies the program in args[1] followed by its arguments. A program without a separator resolves through PATH.

tecs.io.Process.Options.args: {string}

tecs.io.Process.Options.cwd field

Caller-writable. Selects the child's working directory as a string or Path, or omits it to inherit the current directory.

tecs.io.Process.Options.cwd: string | Path

tecs.io.Process.Options.env field

Caller-writable. Overlays environment variables on the inherited environment, or supplies the complete environment with clearEnv.

tecs.io.Process.Options.env: {string: string}

tecs.io.Process.Options.clearEnv field

Caller-writable. Starts with an empty environment when true. Defaults to false.

tecs.io.Process.Options.clearEnv: boolean

tecs.io.Process.Options.stdin field

Caller-writable. Selects "pipe", "inherit", or "null" for standard input. Defaults to "pipe".

tecs.io.Process.Options.stdin: ProcessInputMode

tecs.io.Process.Options.stdout field

Caller-writable. Selects "pipe", "inherit", or "null" for standard output. Defaults to "pipe".

tecs.io.Process.Options.stderr field

Caller-writable. Selects "pipe", "inherit", "null", or "stdout" for standard error. Defaults to "pipe".

tecs.io.Process.Options.timeoutMs field

Caller-writable. Forcibly terminates the child after this many milliseconds, or omits the deadline. The deadline begins when the process is created.

tecs.io.Process.Options.timeoutMs: integer

tecs.io.Process.OutputMode enum

OutputMode selects where a child writes standard output.

enum tecs.io.Process.OutputMode
    "inherit"
    "null"
    "pipe"
end

tecs.io.Process.Reader interface

Reader reads one live child-process output pipe.

interface tecs.io.Process.Reader is Reader
    isClosed: function(self): boolean
    isEOF: function(self): boolean
    setTimeout: function(self, timeoutMs: integer)
end

Interfaces

Interface
Reader

tecs.io.Process.Reader:isClosed Instance

Returns whether this endpoint has been closed.

function tecs.io.Process.Reader.isClosed(self): boolean
Arguments
Name Type Description
self ProcessReader The process reader to inspect.
Returns
Type Description
boolean Returns true after close.

tecs.io.Process.Reader:isEOF Instance

Returns whether the child closed its end and every byte was consumed.

function tecs.io.Process.Reader.isEOF(self): boolean
Arguments
Name Type Description
self ProcessReader The process reader to inspect.
Returns
Type Description
boolean Returns true after end of file.

tecs.io.Process.Reader:setTimeout Instance

Bounds how long read and readInto wait for the child to send bytes.

The bound covers waiting only, so a read that already has bytes returns them however long the reader has been idle. It starts when a call first finds the pipe empty and ends that call with nil and a reason. The default is 30,000 milliseconds, and zero returns as soon as one attempt finds no bytes. The new bound applies to the next call rather than to one already waiting.

function tecs.io.Process.Reader.setTimeout(self, timeoutMs: integer)
Arguments
Name Type Description
self ProcessReader The process reader to configure.
timeoutMs integer The caller supplies 0 through 2147483647 milliseconds. Any other value raises.
Returns

None.

tecs.io.Process.Result record

Result contains a completed duplex exchange.

record tecs.io.Process.Result
    exit: ProcessExit
    output: string
    errorOutput: string

    succeeded: function(self): boolean
end

tecs.io.Process.Result.exit field

Read-only. Contains the child's exit description.

tecs.io.Process.Result.exit: ProcessExit

tecs.io.Process.Result.output field

Read-only. Contains every captured standard-output byte. It is empty when stdout was inherited or discarded.

tecs.io.Process.Result.output: string

tecs.io.Process.Result.errorOutput field

Read-only. Contains every captured standard-error byte. It is empty when stderr was inherited, discarded, or merged into stdout.

tecs.io.Process.Result.errorOutput: string

tecs.io.Process.Result:succeeded Instance

Returns whether the child exited normally with code zero.

function tecs.io.Process.Result.succeeded(self): boolean
Arguments
Name Type Description
self ProcessResult The completed exchange to inspect.
Returns
Type Description
boolean Returns true only for a normal zero exit.

tecs.io.Process.Writer interface

Writer writes one live child-process input pipe.

interface tecs.io.Process.Writer is Writer
    isClosed: function(self): boolean
    setTimeout: function(self, timeoutMs: integer)
end

Interfaces

Interface
Writer

tecs.io.Process.Writer:isClosed Instance

Returns whether this endpoint has sent EOF or been closed.

function tecs.io.Process.Writer.isClosed(self): boolean
Arguments
Name Type Description
self ProcessWriter The process writer to inspect.
Returns
Type Description
boolean Returns true after close.

tecs.io.Process.Writer:setTimeout Instance

Bounds how long write, writeFrom, and writeView wait for the child to take bytes.

The bound covers waiting only, so a write the pipe has room for returns however long the writer has been idle. It starts when a call first finds the pipe full and ends that call with its failure and a reason, and the child taking further bytes starts it again, so it bounds one stall rather than a whole long write. The default is 30,000 milliseconds, and zero returns as soon as one attempt finds no room. The new bound applies to the next call rather than to one already waiting.

function tecs.io.Process.Writer.setTimeout(self, timeoutMs: integer)
Arguments
Name Type Description
self ProcessWriter The process writer to configure.
timeoutMs integer The caller supplies 0 through 2147483647 milliseconds. Any other value raises.
Returns

None.

Functions

tecs.io.Process:communicate Instance

Feeds stdin and captures stdout and stderr without pipe deadlock.

The call drains both output pipes while feeding input, closes stdin after the final byte, and returns after the child exits and both pipes reach EOF. It suspends a normal system or blocks an ordinary caller.

function tecs.io.Process.communicate(
    self, options: CommunicateOptions
): Result, string

Arguments

Name Type Description
self Process The open process whose piped endpoints are consumed.
options CommunicateOptions The caller supplies complete input and an output limit, or omits both.

Returns

Type Description
Result Returns the complete exchange result.
string Returns a reason when communication fails or exceeds its output limit.

Examples

tecs.scoped(
    "communicate with child",
    function(scope: tecs.Scope)
        local child <const> = scope:own(tecs.io.Process.new({
            args = {"/bin/cat"}
        }))

        local result, communicateReason = child:communicate({
            input = "request bytes\n",
            maxOutputBytes = 1024 * 1024,
        })

        assert(result, communicateReason)
        assert(result.output == "request bytes\n")
    end
)

tecs.io.Process:isRunning Instance

Returns whether the child has not stopped yet.

function tecs.io.Process.isRunning(self): boolean

Arguments

Name Type Description
self Process The process to inspect.

Returns

Type Description
boolean Returns true while the child remains live.

tecs.io.Process:kill Instance

Requests that the child terminate.

function tecs.io.Process.kill(self, force: boolean): boolean, string

Arguments

Name Type Description
self Process The process to terminate.
force boolean The caller requests an unhandleable termination when true or a graceful request when false or omitted.

Returns

Type Description
boolean Returns whether the request was delivered or the child had already stopped.
string Returns the platform reason when the first return is false.

tecs.io.Process:wait Instance

Waits for the child to stop and returns its exit description.

Inside a system, the call suspends the logical world update without blocking the host thread. Outside a world update, it blocks.

function tecs.io.Process.wait(self): Exit

Arguments

Name Type Description
self Process The process to wait for.

Returns

Type Description
Exit Returns how the child stopped.

Values

tecs.io.Process.pid variable

Read-only. Reports the operating-system process identifier.

tecs.io.Process.pid: integer

tecs.io.Process.stderr variable

Read-only. Provides standard error when its mode is "pipe", or nil when error output is inherited, discarded, or merged into stdout. The process owns it.

tecs.io.Process.stderr: Reader

tecs.io.Process.stdin variable

Read-only. Provides standard input when its mode is "pipe", or nil when input is inherited or discarded. The process owns it.

tecs.io.Process.stdin: Writer

tecs.io.Process.stdout variable

Read-only. Provides standard output when its mode is "pipe", or nil when output is inherited or discarded. The process owns it.

tecs.io.Process.stdout: Reader