Skip to content

Commit 990af1f

Browse files
committed
Implement suggestions from #2
- Just one effect for child processes, rather than separate Stdout, Stdin, Stderr. I named it CHILD_PROCESS because I plan to add `exec` and `execFile` in addition to `spawn`, and because the effect represents not only spawning child processes, but also interacting with them. - Change the child process record into an opaque newtype (without an exported constructor). This prevents people from trying to update its fields, which would probably not be a good idea. - Change types for some child process attributes to be a bit safer. For example, reading the "connected" state of a child process now requires Eff, as does "killing" one (ie, sending a signal). - Use StrMap for the `env` option - Make the `env` option nullable so that you can inherit the parent environment if you want to.
1 parent 0be6cf7 commit 990af1f

File tree

6 files changed

+142
-100
lines changed

6 files changed

+142
-100
lines changed

bower.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"purescript-functions": "~0.1.0",
88
"purescript-node-streams": "~0.2.0",
99
"purescript-foreign": "~0.7.2",
10-
"purescript-maps": "~0.5.3"
10+
"purescript-maps": "~0.5.3",
11+
"purescript-nullable": "~0.2.1"
1112
},
1213
"devDependencies": {
1314
"purescript-console": "~0.1.1"

src/Node/ChildProcess.purs

Lines changed: 78 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,112 @@
11
module Node.ChildProcess
22
( Handle()
3-
, Spawn()
4-
, Stdin()
5-
, Stdout()
6-
, Stderr()
73
, ChildProcess()
8-
, SpawnOptions()
4+
, CHILD_PROCESS()
5+
, stderr
6+
, stdout
7+
, stdin
8+
, pid
9+
, connected
10+
, kill
11+
, send
12+
, disconnect
913
, ChildProcessError()
1014
, onExit
1115
, onClose
1216
, onDisconnect
1317
, onMessage
1418
, onError
1519
, spawn
20+
, SpawnOptions()
1621
, defaultSpawnOptions
1722
) where
1823

1924
import Prelude
2025

2126
import Control.Monad.Eff (Eff())
2227

23-
import Data.Function (Fn0(), Fn1(), Fn2())
28+
import Data.StrMap (StrMap())
29+
import Data.Function (Fn2(), runFn2)
30+
import Data.Nullable (Nullable(), toNullable)
2431
import Data.Maybe (Maybe(..))
2532
import Data.Foreign (Foreign())
2633

34+
import Node.Buffer (Buffer())
2735
import Node.Stream (Readable(), Writable())
2836
import Node.ChildProcess.Signal (Signal(..))
2937

38+
-- | A handle for inter-process communication (IPC).
3039
foreign import data Handle :: *
31-
foreign import data Spawn :: !
32-
foreign import data Stdin :: !
33-
foreign import data Stdout :: !
34-
foreign import data Stderr :: !
35-
36-
type ChildProcess =
37-
{ stderr :: forall eff. Readable () (stderr :: Stderr | eff) String
38-
, stdin :: forall eff. Writable () (stdin :: Stdin | eff) String
39-
, stdout :: forall eff. Readable () (stdout :: Stdout | eff) String
40-
, pid :: Number
40+
41+
-- | The effect for creating and interacting with child processes.
42+
foreign import data CHILD_PROCESS :: !
43+
44+
newtype ChildProcess = ChildProcess ChildProcessRec
45+
46+
runChildProcess :: ChildProcess -> ChildProcessRec
47+
runChildProcess (ChildProcess r) = r
48+
49+
-- | Note: some of these types are lies, and so it is unsafe to access some of
50+
-- | these record fields directly.
51+
type ChildProcessRec =
52+
{ stderr :: forall eff. Readable () (cp :: CHILD_PROCESS | eff) Buffer
53+
, stdin :: forall eff. Writable () (cp :: CHILD_PROCESS | eff) Buffer
54+
, stdout :: forall eff. Readable () (cp :: CHILD_PROCESS | eff) Buffer
55+
, pid :: Int
4156
, connected :: Boolean
42-
, kill :: forall eff. Fn1 Signal Boolean
43-
, send :: forall eff r. Fn2 { | r} Handle (Eff eff Unit)
44-
, disconnect :: forall eff. Fn0 (Eff eff Unit)
57+
, kill :: Signal -> Boolean
58+
, send :: forall r. Fn2 { | r} Handle Boolean
59+
, disconnect :: forall eff. Eff eff Unit
4560
}
4661

62+
-- | The standard error stream of a child process. Note that this is only
63+
-- | available if the process was spawned with the stderr option set to "pipe".
64+
stderr :: forall eff. ChildProcess -> Readable () (cp :: CHILD_PROCESS | eff) Buffer
65+
stderr = _.stderr <<< runChildProcess
66+
67+
-- | The standard output stream of a child process. Note that this is only
68+
-- | available if the process was spawned with the stdout option set to "pipe".
69+
stdout :: forall eff. ChildProcess -> Readable () (cp :: CHILD_PROCESS | eff) Buffer
70+
stdout = _.stdout <<< runChildProcess
71+
72+
-- | The standard input stream of a child process. Note that this is only
73+
-- | available if the process was spawned with the stdin option set to "pipe".
74+
stdin :: forall eff. ChildProcess -> Writable () (cp :: CHILD_PROCESS | eff) Buffer
75+
stdin = _.stdin <<< runChildProcess
76+
77+
-- | The process ID of a child process. Note that if the process has already
78+
-- | exited, another process may have taken the same ID, so be careful!
79+
pid :: ChildProcess -> Int
80+
pid = _.pid <<< runChildProcess
81+
82+
connected :: forall eff. ChildProcess -> Eff (cp :: CHILD_PROCESS | eff) Boolean
83+
connected = pure <<< _.connected <<< runChildProcess
84+
85+
send :: forall eff props. { | props } -> Handle -> ChildProcess -> Eff (cp :: CHILD_PROCESS | eff) Boolean
86+
send msg handle (ChildProcess cp) = pure (runFn2 cp.send msg handle)
87+
88+
disconnect :: forall eff. ChildProcess -> Eff (cp :: CHILD_PROCESS | eff) Unit
89+
disconnect = _.disconnect <<< runChildProcess
90+
91+
-- | Send a signal to a child process. It's an unfortunate historical decision
92+
-- | that this function is called "kill", as sending a signal to a child
93+
-- | process won't necessarily kill it.
94+
kill :: forall eff. Signal -> ChildProcess -> Eff (cp :: CHILD_PROCESS | eff) Boolean
95+
kill sig (ChildProcess cp) = pure (cp.kill sig)
96+
4797
type SpawnOptions =
4898
{ cwd :: String
4999
, stdio :: Array String
50-
, env :: forall r. { | r}
100+
, env :: Nullable (StrMap String)
51101
, detached :: Boolean
52-
, uid :: Number
53-
, gid :: Number
102+
, uid :: Int
103+
, gid :: Int
54104
}
55105

56-
onExit :: forall eff. ChildProcess -> (Maybe Number -> Maybe Signal -> Eff eff Unit) -> Eff eff Unit
106+
onExit :: forall eff. ChildProcess -> (Maybe Int -> Maybe Signal -> Eff eff Unit) -> Eff eff Unit
57107
onExit = mkOnExit Nothing Just Signal
58108

59-
onClose :: forall eff. ChildProcess -> (Maybe Number -> Maybe Signal -> Eff eff Unit) -> Eff eff Unit
109+
onClose :: forall eff. ChildProcess -> (Maybe Int -> Maybe Signal -> Eff eff Unit) -> Eff eff Unit
60110
onClose = mkOnClose Nothing Just Signal
61111

62112
onMessage :: forall eff. ChildProcess -> (Foreign -> Maybe Handle -> Eff eff Unit) -> Eff eff Unit
@@ -65,13 +115,13 @@ onMessage = mkOnMessage Nothing Just
65115
foreign import onDisconnect :: forall eff. ChildProcess -> Eff eff Unit -> Eff eff Unit
66116
foreign import onError :: forall eff. ChildProcess -> (ChildProcessError -> Eff eff Unit) -> Eff eff Unit
67117

68-
foreign import spawn :: forall eff. String -> Array String -> SpawnOptions -> Eff (spawn :: Spawn | eff) ChildProcess
118+
foreign import spawn :: forall eff. String -> Array String -> SpawnOptions -> Eff (cp :: CHILD_PROCESS | eff) ChildProcess
69119

70120
defaultSpawnOptions :: SpawnOptions
71121
defaultSpawnOptions =
72122
{ cwd: undefined
73123
, stdio: ["pipe", "pipe", "pipe"]
74-
, env: process.env
124+
, env: toNullable Nothing
75125
, detached: false
76126
, uid: undefined
77127
, gid: undefined
@@ -85,14 +135,13 @@ type ChildProcessError =
85135

86136
foreign import mkOnExit :: forall a eff.
87137
Maybe a -> (a -> Maybe a) -> (String -> Signal) ->
88-
ChildProcess -> (Maybe Number -> Maybe Signal -> Eff eff Unit) -> Eff eff Unit
138+
ChildProcess -> (Maybe Int -> Maybe Signal -> Eff eff Unit) -> Eff eff Unit
89139
foreign import mkOnMessage :: forall a eff.
90140
Maybe a -> (a -> Maybe a) ->
91141
ChildProcess -> (Foreign -> Maybe Handle -> Eff eff Unit) -> Eff eff Unit
92142
foreign import mkOnClose :: forall a eff.
93143
Maybe a -> (a -> Maybe a) -> (String -> Signal) ->
94-
ChildProcess -> (Maybe Number -> Maybe Signal -> Eff eff Unit) -> Eff eff Unit
144+
ChildProcess -> (Maybe Int -> Maybe Signal -> Eff eff Unit) -> Eff eff Unit
95145

96146
-- There's gotta be a better way.
97147
foreign import undefined :: forall a. a
98-
foreign import process :: forall r. {env :: { | r}}

src/Node/ChildProcess/Signal.purs

Lines changed: 42 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -40,48 +40,48 @@ module Node.ChildProcess.Signal
4040
, sigxfsz
4141
) where
4242

43-
import Prelude (Show)
43+
import Prelude (Show)
4444

45-
newtype Signal = Signal String
45+
newtype Signal = Signal String
4646

47-
sigabrt = Signal "SIGABRT"
48-
sigalrm = Signal "SIGALRM"
49-
sigbus = Signal "SIGBUS"
50-
sigchld = Signal "SIGCHLD"
51-
sigcld = Signal "SIGCLD"
52-
sigcont = Signal "SIGCONT"
53-
sigemt = Signal "SIGEMT"
54-
sigfpe = Signal "SIGFPE"
55-
sighup = Signal "SIGHUP"
56-
sigill = Signal "SIGILL"
57-
siginfo = Signal "SIGINFO"
58-
sigint = Signal "SIGINT"
59-
sigio = Signal "SIGIO"
60-
sigiot = Signal "SIGIOT"
61-
sigkill = Signal "SIGKILL"
62-
siglost = Signal "SIGLOST"
63-
sigpipe = Signal "SIGPIPE"
64-
sigpoll = Signal "SIGPOLL"
65-
sigprof = Signal "SIGPROF"
66-
sigpwr = Signal "SIGPWR"
67-
sigquit = Signal "SIGQUIT"
68-
sigsegv = Signal "SIGSEGV"
69-
sigstkflt = Signal "SIGSTKFLT"
70-
sigstop = Signal "SIGSTOP"
71-
sigsys = Signal "SIGSYS"
72-
sigterm = Signal "SIGTERM"
73-
sigtrap = Signal "SIGTRAP"
74-
sigtstp = Signal "SIGTSTP"
75-
sigttin = Signal "SIGTTIN"
76-
sigttou = Signal "SIGTTOU"
77-
sigunused = Signal "SIGUNUSED"
78-
sigurg = Signal "SIGURG"
79-
sigusr1 = Signal "SIGUSR1"
80-
sigusr2 = Signal "SIGUSR2"
81-
sigvtalrm = Signal "SIGVTALRM"
82-
sigwinch = Signal "SIGWINCH"
83-
sigxcpu = Signal "SIGXCPU"
84-
sigxfsz = Signal "SIGXFSZ"
47+
sigabrt = Signal "SIGABRT"
48+
sigalrm = Signal "SIGALRM"
49+
sigbus = Signal "SIGBUS"
50+
sigchld = Signal "SIGCHLD"
51+
sigcld = Signal "SIGCLD"
52+
sigcont = Signal "SIGCONT"
53+
sigemt = Signal "SIGEMT"
54+
sigfpe = Signal "SIGFPE"
55+
sighup = Signal "SIGHUP"
56+
sigill = Signal "SIGILL"
57+
siginfo = Signal "SIGINFO"
58+
sigint = Signal "SIGINT"
59+
sigio = Signal "SIGIO"
60+
sigiot = Signal "SIGIOT"
61+
sigkill = Signal "SIGKILL"
62+
siglost = Signal "SIGLOST"
63+
sigpipe = Signal "SIGPIPE"
64+
sigpoll = Signal "SIGPOLL"
65+
sigprof = Signal "SIGPROF"
66+
sigpwr = Signal "SIGPWR"
67+
sigquit = Signal "SIGQUIT"
68+
sigsegv = Signal "SIGSEGV"
69+
sigstkflt = Signal "SIGSTKFLT"
70+
sigstop = Signal "SIGSTOP"
71+
sigsys = Signal "SIGSYS"
72+
sigterm = Signal "SIGTERM"
73+
sigtrap = Signal "SIGTRAP"
74+
sigtstp = Signal "SIGTSTP"
75+
sigttin = Signal "SIGTTIN"
76+
sigttou = Signal "SIGTTOU"
77+
sigunused = Signal "SIGUNUSED"
78+
sigurg = Signal "SIGURG"
79+
sigusr1 = Signal "SIGUSR1"
80+
sigusr2 = Signal "SIGUSR2"
81+
sigvtalrm = Signal "SIGVTALRM"
82+
sigwinch = Signal "SIGWINCH"
83+
sigxcpu = Signal "SIGXCPU"
84+
sigxfsz = Signal "SIGXFSZ"
8585

86-
instance showSignal :: Show Signal where
87-
show (Signal sig) = sig
86+
instance showSignal :: Show Signal where
87+
show (Signal sig) = sig

test/Main.purs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
module Test.Main where
2+
3+
import Prelude
4+
import Control.Bind
5+
6+
import Control.Monad.Eff.Console
7+
8+
import Node.Encoding (Encoding(UTF8))
9+
import Node.Buffer as Buffer
10+
import Node.ChildProcess
11+
import Node.ChildProcess.Signal
12+
import Node.Stream (onData)
13+
14+
main = do
15+
ls <- spawn "ls" ["-la"] defaultSpawnOptions
16+
onClose ls \code sig ->
17+
log $ "ls exited with code: " ++ (show code) ++ "\nfrom signal: " ++ (show sig)
18+
onData (stdout ls) (Buffer.toString UTF8 >=> log)
19+
kill sigterm ls
20+
log "Killed."

test/Node/ChildProcess.js

Lines changed: 0 additions & 8 deletions
This file was deleted.

test/Node/ChildProcess.purs

Lines changed: 0 additions & 20 deletions
This file was deleted.

0 commit comments

Comments
 (0)