/
args.js
202 lines (174 loc) · 6.07 KB
/
args.js
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
"use strict"
var hasOwn = Object.prototype.hasOwnProperty
// TODO: Duplicate this in the `tl` binary to just preparse for known/unknown
// options when starting up initially, so it's more future-proof
// Errors that aren't from this borking up. It intentionally doesn't have a
// stack trace.
function warn(message) {
return {message: "Warning: " + message}
}
/**
* Properties:
*
* color: If not `null`, force the color on if `true`, off if `false`.
*
* config: The config file to use. The default is inferred from
* `${args.files[0]}/.tl.${ext}`, taking the first `ext` from
* `--require` or whatever's inferred from node-interpret.
*
* cwd: This changes the default current working directory. It normally defaults
* to `process.cwd()` or whatever was passed for `--cwd`, but the unit
* tests may change that default.
*
* env: This is a key-value mapping for the environment Node is to be respawned
* with.
*
* files: A list of file globs to load.
*
* help: If set to `"simple"` or `"detailed"`, display the relevant help prompt.
*
* opts: The `.tl.opts` file as used in the init script.
*
* require: A list of extensions + possible modules to require/register. This
* effectively disables much of the inferrence magic based on `cwd`,
* the first `files` glob, and `config` to come up with something
* sensible.
*
* respawnAs: If set, it's the binary to respawn with. Setting this also implies
* setting `respawn` to `true` ignorant of other options.
*
* unknown: This contains all unknown flags, so they can be passed to Node
* transparently (unless `respawn === false`).
*/
exports.Args = Args
function Args() {
this.color = undefined
this.config = undefined
this.cwd = undefined
this.env = undefined
this.files = []
this.help = undefined
this.opts = undefined
this.require = []
this.respawnAs = undefined
this.unknown = []
}
// `true` means it requires a value. If the key doesn't exist, the flag is
// implicitly false, since this table is checked for truthiness, not actual
// existence + true/false.
var requiresValue = {
"config": true,
"cwd": true,
"env": true,
"opts": true,
"require": true,
"respawn-as": true,
}
var aliases = {
c: "config",
e: "env",
H: "help-detailed",
h: "help",
r: "require",
}
/**
* Serializes `args` into a list of tokens.
*/
function serialize(args, call) {
var boolean = true
var i = 0
while (i < args.length && args[i] !== "--") {
var entry = args[i++]
if (!boolean || entry[0] !== "-") {
// Allow anything other than literally `--` as a value. If it's a
// mistake, this'll likely complain later, anyways.
call("value", entry, boolean)
boolean = true
} else if (entry[1] === "-") {
var value = entry.slice(2)
boolean = !requiresValue[value]
call("flag", value, boolean)
} else {
var last
for (var j = 1; j < entry.length; j++) {
var short = entry[j]
// If we're not yet done parsing the shorthand alias, then the
// current binary option *clearly* won't have a value to use.
if (!boolean) {
throw warn(
"Shorthand option -" + last + " requires a value " +
"immediately after it")
}
// Silently ignore invalid short flags - V8 doesn't use any.
if (hasOwn.call(aliases, short)) {
var alias = aliases[short]
boolean = !requiresValue[alias]
call("flag", alias, boolean)
}
last = short
}
}
}
if (!boolean) {
throw warn(
"Shorthand option -" + args[i - 1] + " requires a value " +
"immediately after it")
}
// The above loop only breaks early with an `--` argument, and this loop's
// preincrement in its condition handles this as well.
while (++i < args.length) {
call("file", args[i], true)
}
}
exports.parse = function (args) {
var result = new Args()
var lastBoolean = false
var lastValue
serialize(args, function (type, value, boolean) {
if (type === "flag") {
switch (value) {
case "help": result.help = "simple"; break
case "help-detailed": result.help = "detailed"; break
case "color": result.color = true; break
case "no-color": result.color = false; break
case "config": case "cwd": case "env":
case "opts": case "require": case "respawn-as":
lastValue = value
lastBoolean = boolean
return
// Legacy options - ignore them, and remove in 0.5.
case "force-local": case "no-force-local":
case "respawn": case "no-respawn":
break
default:
result.unknown.push("--" + value)
}
} else if (lastValue == null || type === "file") {
result.files.push(value)
} else if (lastValue === "env") {
var index = value.indexOf("=")
if (result.env == null) result.env = Object.create(null)
result.env[value.slice(0, index)] = value.slice(index + 1)
} else {
if (lastValue === "respawn-as") {
lastValue = "respawnAs"
}
// Silently ignore invalid arguments
var current = result[lastValue]
if (Array.isArray(current)) {
current.push(value)
} else if (lastBoolean) {
result[lastValue] = true
result.files.push(value)
} else {
result[lastValue] = value
}
}
lastValue = null
})
if (lastValue != null && !lastBoolean) {
throw warn(
"Option was passed without a required argument: " + lastValue)
}
return result
}