/
index.js
219 lines (206 loc) · 5.85 KB
/
index.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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
var assert = require('assert');
var fs = require('fs');
var path = require('path'),
basename = path.basename,
dirname = path.dirname;
var Writable = require('stream').Writable;
var util = require('util');
var config = {
name: basename(process.argv[1]),
start: 64,
prefix: true,
lang: 'en',
pad: ' ',
log: null,
flags: null,
locales: path.join(path.normalize(dirname(process.argv[1])), 'locales'),
lc: ['LC_ALL', 'LC_MESSAGES']
}
var cache = {}, stream;
var errors = {};
var ErrorDefinition = require('./lib/definition');
var CliError = require('./lib/error')(config).CliError;
var lc = require('cli-locale');
/**
* Raise an error.
*
* @param err The error definition.
* @param ... Message replacement parameters.
*/
function raise(err) {
var parameters = [].slice.call(arguments, 1);
assert(err instanceof ErrorDefinition,
'argument to raise must be error definition');
var e = err.toError();
var listeners = process.listeners('uncaughtException');
if(!listeners.length) {
process.once('uncaughtException', function(err) {
parameters.unshift(false);
e.source = err;
e.error.apply(e, parameters);
// NOTE: this is only really required to mock
// NOTE: this method invocation in test, see test/unit/exit.js
process.emit('exception', e);
e.exit();
});
}else{
e.message = e.format.apply(e, parameters);
}
throw e;
}
/**
* Print a warning from an error definition.
*
* @param err The error definition.
* @param trace Whether to include the stack trace.
* @param ... Message replacement parameters.
*/
function warn(err, trace) {
assert((err instanceof ErrorDefinition),
'argument to warn must be error definition');
var e = err.toError();
// remove this method from the stack trace
e.stacktrace.shift();
var parameters = [].slice.call(arguments, 2);
parameters.unshift(trace);
e.warn.apply(e, parameters);
return e;
}
/**
* Exit the program from an error definition.
*
* @param err The error definition.
* @param trace Whether to include the stack trace.
* @param ... Message replacement parameters.
*/
function exit(err, trace) {
assert(err instanceof ErrorDefinition,
'argument to exit must be error definition');
var e = err.toError();
// remove this method from the stack trace
e.stacktrace.shift();
var parameters = [].slice.call(arguments, 2);
parameters.unshift(trace);
e.error.apply(e, parameters);
return e.exit();
}
/**
* Import definitions from an object.
*
* @param source An array defining error messages.
*/
function load(source) {
assert(Array.isArray(source), 'argument to load must be an array');
var i, v, d;
for(i = 0;i < source.length;i++) {
v = source[i];
assert(typeof v.key == 'string', 'error definition must have string key')
d = define(v.key, v.message, v.parameters, v.code);
if(v.description) d.description = v.description;
}
}
/**
* Clear all defined errors.
*/
function clear() {
// NOTE: we clear this way so that modules
// NOTE: that reference the errors do not
// NOTE: have their references broken after clear()
for(var z in errors) {
delete errors[z];
}
return errors;
}
/**
* Define an error for the program.
*
* If the exit status code is not specified it is auto
* incremented based on the previously defined errors.
*
* @param key The error key.
* @param message The error message.
* @param parameters Array of message replacement parameters (optional).
* @param code Exit status code (optional).
*/
function define(key, message, parameters, code) {
if(typeof parameters == 'number') {
code = parameters;
parameters = null;
}
var start = typeof config.start == 'number' ? config.start : 128;
if(!code && errors[key] && errors[key].code) {
code = errors[key].code;
}
if(code === undefined) code = Object.keys(errors).length + start;
// NOTE: we have to clamp code to 255 limit as POSIX systems
// NOTE: use an unsigned 8-bit integer, if you process.exit(256)
// NOTE: in node it will exit with a zero exit code which
// NOTE: is completely undesirable
if(code > 255) code = 255;
// re-use error code if overwriting
var err = new ErrorDefinition(key, message, code, parameters);
errors[key] = err;
return err;
}
var file = require('./lib/file')(config, errors, load).file;
/**
* Open a log file write stream and overwrite the error
* and warn console methods to write to the log file.
*/
function open(log, flags) {
log = log || config.log;
if(!log || typeof log !== 'string' && typeof log.write !== 'function') {
throw new TypeError('Cannot open log file, invalid argument');
}
flags = flags || config.flags || 'a';
stream = (typeof log === 'string')
? fs.createWriteStream(log, {flags: flags})
: log;
cache.error = console.error;
cache.warn = console.warn;
console.error = function() {
var msg = util.format.apply(util, arguments) + '\n';
stream.write(msg);
}
console.warn = function() {
var msg = util.format.apply(util, arguments) + '\n';
stream.write(msg);
}
return stream;
}
/**
* Close a log file stream.
*/
function close() {
if(!stream) {
throw new Error('Log file stream is not open');
}
console.error = cache.error;
console.warn = cache.warn;
stream.end();
stream = null;
cache = {};
}
module.exports = function configure(conf) {
for(var z in conf) {
config[z] = conf[z];
}
lc.language = config.lang;
if(config.log) {
open();
}
return module.exports;
}
module.exports.clear = clear;
module.exports.config = config;
module.exports.define = define;
module.exports.ErrorDefinition = ErrorDefinition;
module.exports.CliError = CliError;
module.exports.errors = errors;
module.exports.exit = exit;
module.exports.file = file;
module.exports.load = load;
module.exports.raise = raise;
module.exports.warn = warn;
module.exports.open = open;
module.exports.close = close;