Skip to content
heff

std.process

For the process: its arguments, its environment, where its program is and where it runs, the commands it runs — to their end, or started beside it, held by a with, spoken to and waited for — and how it ends. programPath and workingDir give a Path, read in the system’s style, and setWorkingDir takes one; arguments and variables are strings.

import std.path
import std.process
-- | the current git commit of a repository
commitOf : {e : HFunctor} -> {{sys : process <: e}} -> Path -> Co e (Maybe String)
commitOf repo = do
r <- run { command = "git", args = ["rev-parse", "HEAD"], dir = repo }
pure (if succeeded r then Just (trim r.out) else Nothing)

A command started beside the program is the host’s own thing, a Started, had only by a with (withProcess, the one use of its Start and Release, which the module keeps to itself): the body is given a With s Started in a region of its own, the operations on a started command take that, and when the body ends the command is ended, if it has not — asked to (SIGTERM), then made to (SIGKILL) two seconds after — and waited for. So a body that is to see its command’s end waits for it (wait); one that leaves is rid of it. A command that cannot start is thrown, a ProcessError:

import exception
import std.process
-- | what a command wrote, or why it did not run
said : {e : HFunctor}
-> {{sys : process <: e}}
-> {{ex : exception ProcessError <: e}}
-> Invocation
-> Co e String
said c = do
try
r <- withProcess c (\p -> wait p)
pure r.out
catch (err : ProcessError) ->
pure ("not run: " ^ show err)

The with of several commands at once — a pool — is a thread (cml’s) for each, each its own with, what came of them sent on a channel: wait and the reads and writes of a started command’s pipes are outside operations of std.reactor’s, pending while the other threads go on.

ProcessError

type ProcessError =
{ code : Int, name : String, message : String, path : String }

Why a command was not started: it could not be run — not found, not a program, its directory not there — the system’s code, its name (ENOENT), what it says of it, and the path it was said of: the command’s name as the invocation gives it, or its dir, written as the system writes it, where that is what was not there.

instanceShowProcessError

instance instanceShowProcessError : Show ProcessError

As a system’s tools say it: the program or the directory, the errno’s name, and what it is.

>>> show ({ code = 2, name = "ENOENT", message = "No such file or directory", path = "heff-none" } : ProcessError)
"heff-none: ENOENT: No such file or directory"

Ended

data Ended = Exited Int | Signalled Int | TimedOut | NotRun ProcessError

How a command run ended: by itself, with its exit status (0: it went well); by a signal, its number (never on Windows); killed at its time limit; or not at all, as it could not be run, and why (not found, not a program, a NUL in its name or an argument, its directory not there).

Input

data Input = NoInput | Given String | Piped | Inherited

What a command’s standard input is: none, the null device (not the program’s, nor a closed descriptor, which its next open would take); a string, written to it and then closed; a pipe the program writes to as it goes, writeInput, until closeInput; or the program’s own.

Output

data Output = Collected | Piped | Inherited

What a command’s output, or its error stream, is: read as it comes and kept for its Ran; a pipe the program reads as it goes, readOutput, readErrors, what it has not read its Ran’s; or the program’s own, written there as it comes, its Ran given none of it.

Portion

data Portion = Available | Exactly Int | Through String

How much a read takes of what a command has written and the program has not yet read: whatever there is, waiting for some; so many bytes; or all up to and with the first of a string ("\n": a line; "\r\n\r\n": HTTP’s and LSP’s headers).

Received

data Received = Got Bytes | Timeout | Closed

What a read was given: bytes; nothing within its time limit, the bytes there are left for the next read; or nothing, as the stream has ended (or is not Piped). At the end, a read takes what is left, fewer bytes than asked or no string, and the next is Closed.

Invocation

type Invocation =
{ command : String
, args : List String = []
, dir : Path = current
, env : List (String, String) = []
, input : Input = NoInput
, output : Output = Collected
, errors : Output = Collected
, timeout : Int = 0
, keep : Int = 0
, group : Bool = False
}

A command to run: its name (found on the PATH) or a path, written as the system writes one (render p), its arguments, and how it is run. Each field but the name has a default, so a record gives only what it changes: { command = "make", args = ["all"], dir = d, timeout = 60000 }.

  • dir: the directory it runs in, from the working directory where it is relative (current, std.path’s: the working directory itself);
  • env: environment variables set for it, each a name and a value, over the program’s own, which it has too;
  • input: its standard input (NoInput: none);
  • output, errors: its output and its error stream (Collected);
  • timeout: how many milliseconds it is given, after which it is killed (SIGKILL) and has TimedOut (0: as long as it takes);
  • keep: how many bytes of each of its output and its error stream are kept, the first, the rest read and let go (0: all of them);
  • group: started in a process group of its own, so that killing it (its time limit, kill, terminate, the program’s end) kills what it started too (False: only it; on Windows, only it either way).

Ran

type Ran = { ended : Ended, out : String, err : String }

a command run to its end: how it ended, and what it wrote to its output and its error stream (those Collected)

succeeded

succeeded : Ran -> Bool

It ended by itself, with the status 0.

>>> succeeded { ended = Exited 0, out = "", err = "" }
True
>>> succeeded { ended = Signalled 9, out = "", err = "" }
False

Started

data Started

A command started beside the program, the host’s own, running until it is waited for or its with ends: held by withProcess, as a With s Started, and had by nothing else.

process

process : HFunctor

For the program’s own process and the commands it runs: its arguments, environment, working directory, platform and exit; a command run to its end, or started and written to, read from, waited for and killed.

args

args : {e : HFunctor} -> {{ev : process <: e}} -> Co e (List String)

The program’s arguments (not Node’s, nor the program’s own path): in any effect the process is in, by the evidence.

getEnv

getEnv : {e : HFunctor}
-> {{ev : process <: e}}
-> String
-> Co e (Maybe String)

An environment variable, if it is set.

programPath

programPath : {e : HFunctor} -> {{ev : process <: e}} -> Co e Path

The path of the program’s own file.

workingDir

workingDir : {e : HFunctor} -> {{ev : process <: e}} -> Co e Path

The working directory: every relative path the program gives the system is under it. It is each thread’s own (a thread starts with its starter’s; the main thread, with the process’s), never the process’s.

setWorkingDir

setWorkingDir : {e : HFunctor} -> {{ev : process <: e}} -> Path -> Co e Bool

dir (from the working directory, where it is relative) made this thread’s working directory, as the system names it, its links resolved: True; or False, the directory as it was, where there is no directory at dir. The process’s own is never moved, nor any other thread’s.

platform

platform : {e : HFunctor} -> {{ev : process <: e}} -> Co e String

The platform the program runs on, as Node.js names it: linux, darwin, win32, …

processors

processors : {e : HFunctor} -> {{ev : process <: e}} -> Co e Int

How many threads the machine runs at once: its cores, as the system says (1 where it does not), as std.thread’s cores — here for a program that runs commands, and not threads, to size its work by.

exit

exit : {e : HFunctor} -> {{ev : process <: e}} -> Int -> Co e Unit

End the program, with a status (0: it went well); nothing after it runs.

exec

exec : {e : HFunctor}
-> {{ev : process <: e}}
-> String
-> List String
-> Co e Unit

End the program, and run the command with the arguments in its place: its input is the program’s, and its status is the program’s status.

run

run : {e : HFunctor} -> {{ev : process <: e}} -> Invocation -> Co e Ran

Run the command, to its end, and go on: how it ended, and what it wrote — run { command = "ls", args = ["-l"], dir = "/tmp" }.

withProcess

withProcess : {e : HFunctor}
-> {a : Type}
-> {{ev : process <: e}}
-> {{cu : Custodian.custodian <: e}}
-> {{ex : exception ProcessError <: e}}
-> Invocation
-> ({s : Type} -> With s Started -> Co e a)
-> Co e a

The command started, as run would run it, and held by body in a region of its own — it is not kept past it — the program going on beside it; when body ends, the with lets go of it, and where nothing else holds it — no thread it was given to — it is ended there if it has not — asked to (SIGTERM), then made to (SIGKILL) where it has not within two seconds — and waited for; else it is ended so once the last holder lets go of it: what body came to. A command that could not be run (where run says NotRun) is thrown, a ProcessError, the body not run. What it writes is read, its input written, and its time limit kept, while the program waits on it (wait, tryWait, a read, a write); a command writing more than a pipe holds (64 KiB, most often) waits until then. It runs in the program’s process group (Ctrl-C ends both); at the program’s end, and where a signal ends it (one not waited for by std.signal’s wait, which is the program’s to handle), a command started and not ended is terminated. Should body be left otherwise — a throw heard outside it, a handler between that drops the continuation — the command is ended where the program drops its last reference to it.

wait

wait : {e : HFunctor}
-> {s : Type}
-> {{ev : process <: e}}
-> With s Started
-> Co e Ran

The command waited for, to its end — its input closed first, where it is Piped — its Ran; asked again, its Ran again. Started in a group of std.reactor’s — a thread’s, under cml’s scheduledWithReactor — it is pending while the other threads go on; else it waits where it is, the program with it.

tryWait

tryWait : {e : HFunctor}
-> {s : Type}
-> {{ev : process <: e}}
-> With s Started
-> Co e (Maybe Ran)

The command’s Ran if it has ended, not waiting; else Nothing, and it runs still.

kill

kill : {e : HFunctor}
-> {s : Type}
-> {{ev : process <: e}}
-> With s Started
-> Co e Unit

The command killed (SIGKILL; on Windows, TerminateProcess), if it has not ended, not waited for: its wait says Signalled 9.

terminate

terminate : {e : HFunctor}
-> {s : Type}
-> {{ev : process <: e}}
-> With s Started
-> Co e Ran

The command ended, if it has not: asked to (SIGTERM), then made to (SIGKILL) where it has not within two seconds; waited for, its Ran. (Its with ending does the same.)

writeInput

writeInput : {e : HFunctor}
-> {s : Type}
-> {{ev : process <: e}}
-> With s Started
-> Bytes
-> Co e Bool

The bytes written to the command’s input, Piped, all of them, waiting while its pipe is full (what it writes read meanwhile, so that it never waits on the program): False where they could not be, the input not Piped, closed, or no more read.

closeInput

closeInput : {e : HFunctor}
-> {s : Type}
-> {{ev : process <: e}}
-> With s Started
-> Co e Unit

The command’s input, Piped, closed: it reads its end.

readOutput

readOutput : {e : HFunctor}
-> {s : Type}
-> {{ev : process <: e}}
-> With s Started
-> Portion
-> Int
-> Co e Received

Of what the command has written to its output, Piped, and the program not yet read, the portion, waited for up to ms milliseconds (0: as long as it takes).

readErrors

readErrors : {e : HFunctor}
-> {s : Type}
-> {{ev : process <: e}}
-> With s Started
-> Portion
-> Int
-> Co e Received

readOutput, of its error stream.