如何在purescript中捕获子进程的输出?

时间:2017-01-03 13:59:44

标签: purescript

我是purescript和haskell(甚至是javascript和节点)的新手,所以我很想知道如何在purescript中保存子进程的输出。我正在使用purescript-node-childprocess和purescript-node-fs模块。基本上,我遇到的问题是:

import Node.ChildProcess (CHILD_PROCESS, SpawnOptions, defaultSpawnOptions, Exit(..), spawn, onExit, stdout)
import Node.Stream (onData)

type CPEffect = forall e
              .  Eff (cp :: CHILD_PROCESS
                     , console :: CONSOLE
                     , err :: EXCEPTION
                     , buffer :: BUFFER | e
                     ) Unit

-- | Basically a wrapper around the spawn command.
-- | Takes a command, an array of arguments, and a record of 
-- | options to pass to spawn.
launch :: String -> Array String -> SpawnOptions -> CPEffect
launch cmd args opts = do
  cmd' <- spawn cmd args opts
  onExit cmd' defaultExitHdlr
  -- My problem is with onData due to its return type 
  onData (stdout cmd') -- what do I put as the callback handler arg?
  log $ "done with " <> cmd

如上面的评论所示,问题是来自Node.Stream模块的onData函数。问题出在第二个arg和返回类型:

onData :: forall w eff
        . Readable w (err :: EXCEPTION | eff)
       -> (Buffer -> Eff (err :: EXCEPTION | eff) Unit)
       -> Eff (err :: EXCEPTION | eff) Unit

由于返回是返回Unit的Eff,如何保存子进程输出?第二个参数是一个函数,它接受一个Buffer并返回相同的类型。实际上,正是这个函数从Readable获取数据(这是子进程的stdout)。换句话说,第一个参数是来自子进程节点的stdout流,第二个参数是一个回调处理程序,它将从stdout流中填充缓冲区。

但是由于回调处理程序返回Unit,我看不出如何累积子进程的输出。我还在学习Monad变形金刚,这是一个解决方案吗?我可以创建一个以某种方式包装它的Writer monad吗?

2 个答案:

答案 0 :(得分:4)

如果我要在不使用FFI的情况下在purescript中执行此操作,我将使用Ref,并在onData回调中读取ref值,通过将新数据附加到ref值来创建新字符串,然后使用这个新字符串更新ref值。

WriterT确实对这种事情很有用,虽然我可能不会在这种特殊情况下使用它,因为找出如何使类型排队可能有点尴尬,因为回调需要Eff,而不是WriterT Eff。

我用来解决Pulp中这个问题的另一个选择是使用npm包concat-stream。事实上,您可能会发现Pulp作为使用这些库的真实示例很有用 - 我认为Pulp.Exec和Pulp.System.Stream模块是您感兴趣的模块。

答案 1 :(得分:2)

pscid中,我正在使用STRef来连接进程的输出,并在进程退出时将其结果打印到控制台。我复制了相关代码,以便您有一个例子:

execCommand
  ∷ ∀ e
  . String
  → String
  → Eff (cp ∷ CHILD_PROCESS, console ∷ CONSOLE | e) Unit
execCommand name command =
   catchLog (name <> " threw an exception") $
    runST do
      let cmd = unsafePartial fromJust (uncons (split (Pattern " ") command))
      output ← newSTRef ""
      log ("Running: \"" <> command <> "\"")
      cp ← spawn cmd.head cmd.tail defaultSpawnOptions

      let stout = stdout cp
          sterr = stderr cp

      onDataString stout UTF8 \s →
        modifySTRef output (_ <> s) $> unit

      onDataString sterr UTF8 \s →
        modifySTRef output (_ <> s) $> unit

      onExit cp \e → case e of
        Normally 0 → logColored Green (name <> " successful!")
        Normally code → do
          log =<< readSTRef output
          logColored Red (name <> " errored with code: " <> show code)
        BySignal _       → pure unit