1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
module.exports = read
var buffer = ""
, tty = require("tty")
, StringDecoder = require("string_decoder").StringDecoder
function read (opts, cb) {
if (!cb) cb = opts, opts = {}
var p = opts.prompt || ""
, def = opts.default
, silent = opts.silent
, timeout = opts.timeout
, num = opts.num || null
, delim = opts.delim || "\n"
if (p && def) p += "("+(silent ? "<default hidden>" : def)+") "
// switching into raw mode is a little bit painful.
// avoid if possible.
var r = silent || num || delim !== "\n" ? rawRead : normalRead
if (timeout) {
cb = (function (cb) {
var called = false
var t = setTimeout(function () {
tty.setRawMode(false)
process.stdout.write("\n")
if (def) done(null, def)
else done(new Error("timeout"))
}, timeout)
function done (er, data) {
clearTimeout(t)
if (called) return
// stop reading!
stdin.pause()
called = true
cb(er, data)
}
return done
})(cb)
}
if (p && !process.stdout.write(p)) {
process.stdout.on("drain", function D () {
process.stdout.removeListener("drain", D)
r(def, timeout, delim, silent, num, cb)
})
} else {
process.nextTick(function () {
r(def, timeout, delim, silent, num, cb)
})
}
}
function normalRead (def, timeout, delim, silent, num, cb) {
var stdin = process.openStdin()
, val = ""
, decoder = new StringDecoder("utf8")
stdin.resume()
stdin.on("error", cb)
stdin.on("data", function D (chunk) {
// get the characters that are completed.
val += buffer + decoder.write(chunk)
buffer = ""
// \r has no place here.
// XXX But what if \r is the delim or something dumb like that?
// Meh. If anyone complains about this, deal with it.
val = val.replace(/\r/g, "")
// TODO Make delim configurable
if (val.indexOf(delim) !== -1) {
// pluck off any delims at the beginning.
if (val !== delim) {
var i, l
for (i = 0, l = val.length; i < l; i ++) {
if (val.charAt(i) !== delim) break
}
if (i !== 0) val = val.substr(i)
}
// buffer whatever might have come *after* the delimter
var delimIndex = val.indexOf(delim)
if (delimIndex !== -1) {
buffer = val.substr(delimIndex)
val = val.substr(0, delimIndex)
} else {
buffer = ""
}
stdin.pause()
stdin.removeListener("data", D)
stdin.removeListener("error", cb)
// read(1) trims
val = val.trim() || def
cb(null, val)
}
})
}
function rawRead (def, timeout, delim, silent, num, cb) {
var stdin = process.openStdin()
, val = ""
, decoder = new StringDecoder
tty.setRawMode(true)
stdin.resume()
stdin.on("error", cb)
stdin.on("data", function D (c) {
// \r is my enemy.
c = decoder.write(c).replace(/\r/g, "\n")
switch (c) {
case "": // probably just a \r that was ignored.
break
case "\u0004": // EOF
case delim:
tty.setRawMode(false)
stdin.removeListener("data", D)
stdin.removeListener("error", cb)
val = val.trim() || def
process.stdout.write("\n")
stdin.pause()
return cb(null, val)
case "\u0003": case "\0": // ^C or other signal abort
tty.setRawMode(false)
stdin.removeListener("data", D)
stdin.removeListener("error", cb)
stdin.pause()
return cb(new Error("cancelled"))
break
default: // just a normal char
val += buffer + c
buffer = ""
if (!silent) process.stdout.write(c)
// explicitly process a delim if we have enough chars.
if (num && val.length >= num) D(delim)
break
}
})
}
|