UNPKG

8.03 kBJavaScriptView Raw
1/*
2 * grunt
3 * https://github.com/cowboy/grunt
4 *
5 * Copyright (c) 2012 "Cowboy" Ben Alman
6 * Licensed under the MIT license.
7 * http://benalman.com/about/license/
8 */
9
10var grunt = require('../grunt');
11
12// Nodejs libs.
13var util = require('util');
14
15// The module to be exported.
16var log = module.exports = {};
17
18// Temporarily suppress output.
19var suppressOutput;
20
21// Allow external muting of output.
22log.muted = false;
23
24// True once anything has actually been logged.
25var hasLogged;
26
27// Parse certain markup in strings to be logged.
28function markup(str) {
29 str = str || '';
30 // Make _foo_ underline.
31 str = str.replace(/(\s|^)_([\s\S]+?)_(?=\s|$)/g, '$1' + '$2'.underline);
32 // Make *foo* bold.
33 str = str.replace(/(\s|^)\*([\s\S]+?)\*(?=\s|$)/g, '$1' + '$2'.bold);
34 return str;
35}
36
37// Write output.
38log.write = function(msg) {
39 // Actually write output.
40 if (!log.muted && !suppressOutput) {
41 hasLogged = true;
42 process.stdout.write(markup(msg));
43 }
44 // Chainable!
45 return this;
46};
47
48// Write a line of output.
49log.writeln = function(msg) {
50 // Actually write output.
51 this.write((msg || '') + '\n');
52 // Chainable!
53 return this;
54};
55
56// Stuff.
57
58log.error = function(msg) {
59 if (msg) {
60 grunt.fail.errorcount++;
61 return this.writeln('>> '.red + grunt.utils._.trim(msg).replace(/\n/g, '\n>> '.red));
62 } else {
63 return this.writeln('ERROR'.red);
64 }
65};
66log.ok = function(msg) {
67 if (msg) {
68 return this.writeln('>> '.green + grunt.utils._.trim(msg).replace(/\n/g, '\n>> '.green));
69 } else {
70 return this.writeln('OK'.green);
71 }
72};
73log.errorlns = function(msg) { return this.error(log.wraptext(77, msg)); };
74log.oklns = function(msg) { return this.ok(log.wraptext(77, msg)); };
75log.success = function(msg) { return this.writeln(msg.green); };
76log.fail = function(msg) { return this.writeln(msg.red); };
77log.header = function(msg) {
78 // Skip line before header, but not if header is the very first line output.
79 if (hasLogged) { this.writeln(); }
80 return this.writeln(msg.underline);
81};
82log.subhead = function(msg) {
83 // Skip line before subhead, but not if subhead is the very first line output.
84 if (hasLogged) { this.writeln(); }
85 return this.writeln(msg.bold);
86};
87// For debugging.
88log.debug = function() {
89 if (grunt.option('debug')) {
90 this.writeln(('[D] ' + util.format.apply(this, arguments)).magenta);
91 }
92 return this;
93};
94
95// Write a line of a table.
96log.writetableln = function(widths, texts) {
97 return this.writeln(this.table(widths, texts));
98};
99
100// Wrap a long line of text to 80 columns.
101log.writelns = function(msg) {
102 return this.writeln(this.wraptext(80, msg));
103};
104
105// Display flags in verbose mode.
106log.writeflags = function(obj, prefix) {
107 var wordlist;
108 if (Array.isArray(obj)) {
109 wordlist = log.wordlist(obj);
110 } else if (typeof obj === 'object' && obj) {
111 wordlist = log.wordlist(Object.keys(obj).map(function(key) {
112 var val = obj[key];
113 return key + (val === true ? '' : '=' + JSON.stringify(val));
114 }));
115 }
116 return this.writeln((prefix || 'Flags') + ': ' + (wordlist || '(none)'.cyan));
117};
118
119// Create explicit "verbose" and "notverbose" functions, one for each already-
120// defined log function, that do the same thing but ONLY if -v or --verbose is
121// specified (or not specified).
122log.verbose = {};
123log.notverbose = {};
124
125// Iterate over all exported functions.
126Object.keys(log).filter(function(key) {
127 return typeof log[key] === 'function';
128}).forEach(function(key) {
129 // Like any other log function, but suppresses output if the "verbose" option
130 // IS NOT set.
131 log.verbose[key] = function() {
132 suppressOutput = !grunt.option('verbose');
133 log[key].apply(this, arguments);
134 suppressOutput = false;
135 return this;
136 };
137 // Like any other log function, but suppresses output if the "verbose" option
138 // IS set.
139 log.notverbose[key] = function() {
140 suppressOutput = grunt.option('verbose');
141 log[key].apply(this, arguments);
142 suppressOutput = false;
143 return this;
144 };
145});
146
147// A way to switch between verbose and notverbose modes. For example, this will
148// write 'foo' if verbose logging is enabled, otherwise write 'bar':
149// verbose.write('foo').or.write('bar');
150log.verbose.or = log.notverbose;
151log.notverbose.or = log.verbose;
152
153// Static methods.
154
155// Pretty-format a word list.
156log.wordlist = function(arr, separator) {
157 return arr.map(function(item) {
158 return item.cyan;
159 }).join(separator || ', ');
160};
161
162// Return a string, uncolored (suitable for testing .length, etc).
163log.uncolor = function(str) {
164 return str.replace(/\x1B\[\d+m/g, '');
165};
166
167// Word-wrap text to a given width, permitting ANSI color codes.
168log.wraptext = function(width, text) {
169 // notes to self:
170 // grab 1st character or ansi code from string
171 // if ansi code, add to array and save for later, strip from front of string
172 // if character, add to array and increment counter, strip from front of string
173 // if width + 1 is reached and current character isn't space:
174 // slice off everything after last space in array and prepend it to string
175 // etc
176
177 // This result array will be joined on \n.
178 var result = [];
179 var matches, color, tmp;
180 var captured = [];
181 var charlen = 0;
182
183 while (matches = text.match(/(?:(\x1B\[\d+m)|\n|(.))([\s\S]*)/)) {
184 // Updated text to be everything not matched.
185 text = matches[3];
186
187 // Matched a color code?
188 if (matches[1]) {
189 // Save last captured color code for later use.
190 color = matches[1];
191 // Capture color code.
192 captured.push(matches[1]);
193 continue;
194
195 // Matched a non-newline character?
196 } else if (matches[2]) {
197 // If this is the first character and a previous color code was set, push
198 // that onto the captured array first.
199 if (charlen === 0 && color) { captured.push(color); }
200 // Push the matched character.
201 captured.push(matches[2]);
202 // Increment the current charlen.
203 charlen++;
204 // If not yet at the width limit or a space was matched, continue.
205 if (charlen <= width || matches[2] === ' ') { continue; }
206 // The current charlen exceeds the width and a space wasn't matched.
207 // "Roll everything back" until the last space character.
208 tmp = captured.lastIndexOf(' ');
209 text = captured.slice(tmp === -1 ? tmp : tmp + 1).join('') + text;
210 captured = captured.slice(0, tmp);
211 }
212
213 // The limit has been reached. Push captured string onto result array.
214 result.push(captured.join(''));
215
216 // Reset captured array and charlen.
217 captured = [];
218 charlen = 0;
219 }
220
221 result.push(captured.join(''));
222 return result.join('\n');
223};
224
225// todo: write unit tests
226//
227// function logs(text) {
228// [4, 6, 10, 15, 20, 25, 30, 40].forEach(function(n) {
229// log(n, text);
230// });
231// }
232//
233// function log(n, text) {
234// console.log(Array(n + 1).join('-'));
235// console.log(wrap(n, text));
236// }
237//
238// var text = 'this is '.red + 'a simple'.yellow.inverse + ' test of'.green + ' ' + 'some wrapped'.blue + ' text over '.inverse.magenta + 'many lines'.red;
239// logs(text);
240//
241// var text = 'foolish '.red.inverse + 'monkeys'.yellow + ' eating'.green + ' ' + 'delicious'.inverse.blue + ' bananas '.magenta + 'forever'.red;
242// logs(text);
243//
244// var text = 'foolish monkeys eating delicious bananas forever'.rainbow;
245// logs(text);
246
247// Format output into columns, wrapping words as-necessary.
248log.table = function(widths, texts) {
249 var rows = [];
250 widths.forEach(function(width, i) {
251 var lines = log.wraptext(width, texts[i]).split('\n');
252 lines.forEach(function(line, j) {
253 var row = rows[j];
254 if (!row) { row = rows[j] = []; }
255 row[i] = line;
256 });
257 });
258
259 var lines = [];
260 rows.forEach(function(row) {
261 var txt = '';
262 var column;
263 for (var i = 0; i < row.length; i++) {
264 column = row[i] || '';
265 txt += column;
266 var diff = widths[i] - log.uncolor(column).length;
267 if (diff > 0) { txt += grunt.utils.repeat(diff, ' '); }
268 }
269 lines.push(txt);
270 });
271
272 return lines.join('\n');
273};