• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1const readline = require('readline')
2const Mute = require('mute-stream')
3
4module.exports = async function read ({
5  default: def = '',
6  input = process.stdin,
7  output = process.stdout,
8  completer,
9  prompt = '',
10  silent,
11  timeout,
12  edit,
13  terminal,
14  replace,
15}) {
16  if (typeof def !== 'undefined' && typeof def !== 'string' && typeof def !== 'number') {
17    throw new Error('default value must be string or number')
18  }
19
20  let editDef = false
21  prompt = prompt.trim() + ' '
22  terminal = !!(terminal || output.isTTY)
23
24  if (def) {
25    if (silent) {
26      prompt += '(<default hidden>) '
27    } else if (edit) {
28      editDef = true
29    } else {
30      prompt += '(' + def + ') '
31    }
32  }
33
34  const m = new Mute({ replace, prompt })
35  m.pipe(output, { end: false })
36  output = m
37
38  return new Promise((resolve, reject) => {
39    const rl = readline.createInterface({ input, output, terminal, silent: true, completer })
40    const timer = timeout && setTimeout(() => onError(new Error('timed out')), timeout)
41
42    output.unmute()
43    rl.setPrompt(prompt)
44    rl.prompt()
45
46    if (silent) {
47      output.mute()
48    } else if (editDef) {
49      rl.line = def
50      rl.cursor = def.length
51      rl._refreshLine()
52    }
53
54    const done = () => {
55      rl.close()
56      clearTimeout(timer)
57      output.mute()
58      output.end()
59    }
60
61    const onError = (er) => {
62      done()
63      reject(er)
64    }
65
66    rl.on('error', onError)
67    rl.on('line', (line) => {
68      if (silent && terminal) {
69        output.unmute()
70      }
71      done()
72      // truncate the \n at the end.
73      const res = line.replace(/\r?\n?$/, '') || def || ''
74      return resolve(res)
75    })
76
77    rl.on('SIGINT', () => {
78      rl.close()
79      onError(new Error('canceled'))
80    })
81  })
82}
83