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.pathimport std.process
-- | the current git commit of a repositorycommitOf : {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 exceptionimport std.process
-- | what a command wrote, or why it did not runsaid : {e : HFunctor} -> {{sys : process <: e}} -> {{ex : exception ProcessError <: e}} -> Invocation -> Co e Stringsaid 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 ProcessErrorAs 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 ProcessErrorHow 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 | InheritedWhat 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 | InheritedWhat 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 StringHow 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 | ClosedWhat 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 hasTimedOut(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 -> BoolIt ended by itself, with the status 0.
>>> succeeded { ended = Exited 0, out = "", err = "" }True>>> succeeded { ended = Signalled 9, out = "", err = "" }FalseStarted
data StartedA 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 : HFunctorFor 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 PathThe path of the program’s own file.
workingDir
workingDir : {e : HFunctor} -> {{ev : process <: e}} -> Co e PathThe 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 Booldir (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 StringThe platform the program runs on, as Node.js names it: linux,
darwin, win32, …
processors
processors : {e : HFunctor} -> {{ev : process <: e}} -> Co e IntHow 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 UnitEnd 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 UnitEnd 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 RanRun 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 aThe 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 RanThe 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 UnitThe 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 RanThe 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 BoolThe 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 UnitThe 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 ReceivedOf 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 ReceivedreadOutput, of its error stream.