index.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. /*
  2. * grunt
  3. * http://gruntjs.com/
  4. *
  5. * Copyright (c) 2018 "Cowboy" Ben Alman
  6. * Licensed under the MIT license.
  7. * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
  8. */
  9. 'use strict';
  10. // Nodejs libs.
  11. var util = require('util');
  12. // External libs.
  13. var hooker = require('hooker');
  14. // Requiring this here modifies the String prototype!
  15. var colors = require('colors');
  16. var _ = require('lodash');
  17. // TODO: ADD CHALK
  18. var logUtils = require('grunt-legacy-log-utils');
  19. function Log(options) {
  20. // This property always refers to the "base" logger.
  21. this.always = this;
  22. // Extend options.
  23. this.options = _.extend({}, {
  24. // Show colors in output?
  25. color: true,
  26. // Enable verbose-mode logging?
  27. verbose: false,
  28. // Enable debug logging statement?
  29. debug: false,
  30. // Where should messages be output?
  31. outStream: process.stdout,
  32. // NOTE: the color, verbose, debug options will be ignored if the
  33. // "grunt" option is specified! See the Log.prototype.option and
  34. // the Log.prototype.error methods for more info.
  35. grunt: null,
  36. // Where should output wrap? If null, use legacy Grunt defaults.
  37. maxCols: null,
  38. // Should logger start muted?
  39. muted: false,
  40. }, options);
  41. // True once anything has actually been logged.
  42. this.hasLogged = false;
  43. // Related verbose / notverbose loggers.
  44. this.verbose = new VerboseLog(this, true);
  45. this.notverbose = new VerboseLog(this, false);
  46. this.verbose.or = this.notverbose;
  47. this.notverbose.or = this.verbose;
  48. // Apparently, people have using grunt.log in interesting ways. Just bind
  49. // all methods so that "this" is irrelevant.
  50. if (this.options.grunt) {
  51. var properties = [
  52. 'write',
  53. 'writeln',
  54. 'writetableln',
  55. 'writelns',
  56. 'writeflags',
  57. 'warn',
  58. 'error',
  59. 'ok',
  60. 'errorlns',
  61. 'oklns',
  62. 'success',
  63. 'fail',
  64. 'header',
  65. 'subhead',
  66. 'debug'
  67. ];
  68. _.bindAll(this, properties);
  69. _.bindAll(this.verbose, properties);
  70. _.bindAll(this.notverbose, properties);
  71. }
  72. }
  73. exports.Log = Log;
  74. // Am I doing it wrong? :P
  75. function VerboseLog(parentLog, verbose) {
  76. // Keep track of the original, base "Log" instance.
  77. this.always = parentLog;
  78. // This logger is either verbose (true) or notverbose (false).
  79. this._isVerbose = verbose;
  80. }
  81. util.inherits(VerboseLog, Log);
  82. VerboseLog.prototype._write = function() {
  83. // Abort if not in correct verbose mode.
  84. if (Boolean(this.option('verbose')) !== this._isVerbose) { return; }
  85. // Otherwise... log!
  86. return VerboseLog.super_.prototype._write.apply(this, arguments);
  87. };
  88. // Create read/write accessors that prefer the parent log's properties (in
  89. // the case of verbose/notverbose) to the current log's properties.
  90. function makeSmartAccessor(name, isOption) {
  91. Object.defineProperty(Log.prototype, name, {
  92. enumerable: true,
  93. configurable: true,
  94. get: function() {
  95. return isOption ? this.always._options[name] : this.always['_' + name];
  96. },
  97. set: function(value) {
  98. if (isOption) {
  99. this.always._options[name] = value;
  100. } else {
  101. this.always['_' + name] = value;
  102. }
  103. },
  104. });
  105. }
  106. makeSmartAccessor('options');
  107. makeSmartAccessor('hasLogged');
  108. makeSmartAccessor('muted', true);
  109. // Disable colors if --no-colors was passed.
  110. Log.prototype.initColors = function() {
  111. if (this.option('no-color')) {
  112. // String color getters should just return the string.
  113. colors.mode = 'none';
  114. // Strip colors from strings passed to console.log.
  115. hooker.hook(console, 'log', function() {
  116. var args = _.toArray(arguments);
  117. return hooker.filter(this, args.map(function(arg) {
  118. return typeof arg === 'string' ? colors.stripColors(arg) : arg;
  119. }));
  120. });
  121. }
  122. };
  123. // Check for color, verbose, debug options through Grunt if specified,
  124. // otherwise defer to options object properties.
  125. Log.prototype.option = function(name) {
  126. if (this.options.grunt && this.options.grunt.option) {
  127. return this.options.grunt.option(name);
  128. }
  129. var no = name.match(/^no-(.+)$/);
  130. return no ? !this.options[no[1]] : this.options[name];
  131. };
  132. // Parse certain markup in strings to be logged.
  133. Log.prototype._markup = function(str) {
  134. str = str || '';
  135. // Make _foo_ underline.
  136. str = str.replace(/(\s|^)_(\S|\S[\s\S]+?\S)_(?=[\s,.!?]|$)/g, '$1' + '$2'.underline);
  137. // Make *foo* bold.
  138. str = str.replace(/(\s|^)\*(\S|\S[\s\S]+?\S)\*(?=[\s,.!?]|$)/g, '$1' + '$2'.bold);
  139. return str;
  140. };
  141. // Similar to util.format in the standard library, however it'll always
  142. // convert the first argument to a string and treat it as the format string.
  143. Log.prototype._format = function(args) {
  144. args = _.toArray(args);
  145. if (args.length > 0) {
  146. args[0] = String(args[0]);
  147. }
  148. return util.format.apply(util, args);
  149. };
  150. Log.prototype._write = function(msg) {
  151. // Abort if muted.
  152. if (this.muted) { return; }
  153. // Actually write output.
  154. this.hasLogged = true;
  155. msg = msg || '';
  156. // Users should probably use the colors-provided methods, but if they
  157. // don't, this should strip extraneous color codes.
  158. if (this.option('no-color')) { msg = colors.stripColors(msg); }
  159. // Actually write to stdout.
  160. this.options.outStream.write(this._markup(msg));
  161. };
  162. Log.prototype._writeln = function(msg) {
  163. // Write blank line if no msg is passed in.
  164. this._write((msg || '') + '\n');
  165. };
  166. // Write output.
  167. Log.prototype.write = function() {
  168. this._write(this._format(arguments));
  169. return this;
  170. };
  171. // Write a line of output.
  172. Log.prototype.writeln = function() {
  173. this._writeln(this._format(arguments));
  174. return this;
  175. };
  176. Log.prototype.warn = function() {
  177. var msg = this._format(arguments);
  178. if (arguments.length > 0) {
  179. this._writeln('>> '.red + _.trim(msg).replace(/\n/g, '\n>> '.red));
  180. } else {
  181. this._writeln('ERROR'.red);
  182. }
  183. return this;
  184. };
  185. Log.prototype.error = function() {
  186. if (this.options.grunt && this.options.grunt.fail) {
  187. this.options.grunt.fail.errorcount++;
  188. }
  189. this.warn.apply(this, arguments);
  190. return this;
  191. };
  192. Log.prototype.ok = function() {
  193. var msg = this._format(arguments);
  194. if (arguments.length > 0) {
  195. this._writeln('>> '.green + _.trim(msg).replace(/\n/g, '\n>> '.green));
  196. } else {
  197. this._writeln('OK'.green);
  198. }
  199. return this;
  200. };
  201. Log.prototype.errorlns = function() {
  202. var msg = this._format(arguments);
  203. this.error(this.wraptext(this.options.maxCols || 77, msg));
  204. return this;
  205. };
  206. Log.prototype.oklns = function() {
  207. var msg = this._format(arguments);
  208. this.ok(this.wraptext(this.options.maxCols || 77, msg));
  209. return this;
  210. };
  211. Log.prototype.success = function() {
  212. var msg = this._format(arguments);
  213. this._writeln(msg.green);
  214. return this;
  215. };
  216. Log.prototype.fail = function() {
  217. var msg = this._format(arguments);
  218. this._writeln(msg.red);
  219. return this;
  220. };
  221. Log.prototype.header = function() {
  222. var msg = this._format(arguments);
  223. // Skip line before header, but not if header is the very first line output.
  224. if (this.hasLogged) { this._writeln(); }
  225. this._writeln(msg.underline);
  226. return this;
  227. };
  228. Log.prototype.subhead = function() {
  229. var msg = this._format(arguments);
  230. // Skip line before subhead, but not if subhead is the very first line output.
  231. if (this.hasLogged) { this._writeln(); }
  232. this._writeln(msg.bold);
  233. return this;
  234. };
  235. // For debugging.
  236. Log.prototype.debug = function() {
  237. var msg = this._format(arguments);
  238. if (this.option('debug')) {
  239. this._writeln('[D] ' + msg.magenta);
  240. }
  241. return this;
  242. };
  243. // Write a line of a table.
  244. Log.prototype.writetableln = function(widths, texts) {
  245. this._writeln(this.table(widths, texts));
  246. return this;
  247. };
  248. // Wrap a long line of text.
  249. Log.prototype.writelns = function() {
  250. var msg = this._format(arguments);
  251. this._writeln(this.wraptext(this.options.maxCols || 80, msg));
  252. return this;
  253. };
  254. // Display flags in verbose mode.
  255. Log.prototype.writeflags = function(obj, prefix) {
  256. var wordlist;
  257. if (Array.isArray(obj)) {
  258. wordlist = this.wordlist(obj);
  259. } else if (typeof obj === 'object' && obj) {
  260. wordlist = this.wordlist(Object.keys(obj).map(function(key) {
  261. var val = obj[key];
  262. return key + (val === true ? '' : '=' + JSON.stringify(val));
  263. }));
  264. }
  265. this._writeln((prefix || 'Flags') + ': ' + (wordlist || '(none)'.cyan));
  266. return this;
  267. };
  268. // Add static methods.
  269. [
  270. 'wordlist',
  271. 'uncolor',
  272. 'wraptext',
  273. 'table',
  274. ].forEach(function(prop) {
  275. Log.prototype[prop] = exports[prop] = logUtils[prop];
  276. });