Capturing Terminal Output With Node.js

January 20, 2018

In this post I’ll demonstrate how to capture and act upon output a Node.js program.

The code that does the capturing:

// capture-internal-process.js
//
// ======================================
// Capture and Trigger Function on Output
// stdout produced internally
//
// We accept:
// success_condition
//   - check this on output
//
// success_fn
//   - invoked when success_condition is true

function capture(success_condition, success_fn) {
  const real_stdout    = process.stdout.write
  process.stdout.write = process_output

  function process_output(output) {
    real_stdout.apply(process.stdout, [output])

    if (success_condition(output)) {

      // Restore stdout
      process.stdout.write = real_stdout

      success_fn()
    }
  }

}

Test case:

In this test case, we’ll re-enact a scene from 2001: A Space Odyssey. Here astronaut Dave is trying to persuade Hal to open the pod bay doors.

I’ve altered the scene so that if Hal says the word “mission”, the pod bay doors will open and the program will stop.

If we don’t successfully capture the word “mission”, Dave is doomed to perish in outer space.

// capture-internal-process.js (continued)
//
// =========
// Test case

function test_case() {

  const lines = [
    "DAVE: Open the pod bay doors, Hal.",
    "HAL:  I’m sorry, Dave. I’m afraid I can’t do that.",
    "DAVE: What’s the problem?",
    "HAL:  I think you know what the problem is just as well as I do.",
    "DAVE: What are you talking about, Hal?",
    "HAL:  This mission is too important ...",
    "HAL:  ... for me to allow you to jeopardize it.",
    "DAVE: I don’t know what you're talking about, Hal.",
    "HAL:  I know that you and Frank were planning to disconnect me,",
    "HAL:  ... and I’m afraid that's something I can’t allow to happen.",
    "DAVE: Where the hell did you get that idea, Hal?",
    "HAL:  Although you took very thorough precautions in the pod",
    "HAL:  ... against my hearing you, I could see your lips move.",
    "DAVE: All right, Hal. I’ll go in through the emergency air lock.",
    "HAL:  Without your space helmet, Dave, ...",
    "HAL:  ... you’re going to find that rather difficult.",
    "DAVE: Hal, I won’t argue with you anymore. Open the doors!",
    "HAL:  Dave...This conversation can serve no purpose anymore.",
    "HAL:  Goodbye."
  ]

  // Display the next line in `lines` and
  // remove it from the array
  function display_line() {
    console.log(lines.shift())

    // If we're properly capturing output, the program
    // will end before this if statement is true
    if (lines.length == 0)
      dave_never_gets_back_in()
  }

  // This function should execute when Hal says 'mission'
  function dave_gets_back_in() {

    console.log(`
      Hal says 'mission' and the pod bay doors open!
      Dave climbs back into the spacecraft and disables Hal.
    `)
    process.exit()
  }

  // This function should never execute
  function dave_never_gets_back_in() {
    console.log(`
      This line should never be reached.
      Dave is trapped out in space forever!
    `)
    process.exit()
  }

  // Check to see if the output contains the word 'mission'
  const hal_says_mission = output => output.includes('mission')

  // Capture and check all output
  capture(hal_says_mission, dave_gets_back_in)

  // Output each line each line, one a second
  setInterval(display_line, 1000)

}

test_case()

In an upcoming post I’ll demonstrate capturing output from an external program spawned by Node (update: that post is now up).

Enjoy!