colour.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. /*
  2. Copyright (c) 2010 Marak Squires, Alexis Sellier (cloudhead)
  3. Copyright (c) 2013 Daniel Wirtz <dcode@dcode.io>
  4. Permission is hereby granted, free of charge, to any person obtaining a copy
  5. of this software and associated documentation files (the "Software"), to deal
  6. in the Software without restriction, including without limitation the rights
  7. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. copies of the Software, and to permit persons to whom the Software is
  9. furnished to do so, subject to the following conditions:
  10. The above copyright notice and this permission notice shall be included in
  11. all copies or substantial portions of the Software.
  12. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  15. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  18. THE SOFTWARE.
  19. */
  20. /**
  21. * @license colour.js (c) 2013 Daniel Wirtz <dcode@dcode.io>
  22. * Released under the MIT-License
  23. * see: https://github.com/dcodeIO/colour.js for details
  24. */
  25. (function (global) { // #22
  26. 'use strict';
  27. /**
  28. * colour namespace.
  29. * @namespace
  30. */
  31. var colour = {};
  32. /**
  33. * Colour mode. To generate html in node, use colour.setTheme("html") instead.
  34. * @type {string} May be "console", "browser" or "none".
  35. * @expose
  36. */
  37. colour.mode = "console";
  38. /**
  39. * Whether running in a headless environment like node.js or not.
  40. * @type {boolean}
  41. * @expose
  42. */
  43. colour.headless = typeof global['window'] === 'undefined';
  44. /**
  45. * All themes we know about. Contains only the "default" theme by default.
  46. * @type {!Object.<string,!Object>}
  47. * @expose
  48. */
  49. colour.themes = {};
  50. /**
  51. * Terminal colours.
  52. * @type {!Object} Hello Closure
  53. */
  54. var consoleStyles = {
  55. 'bold': ['\x1B[1m', '\x1B[22m'],
  56. 'italic': ['\x1B[3m', '\x1B[23m'],
  57. 'underline': ['\x1B[4m', '\x1B[24m'],
  58. 'inverse': ['\x1B[7m', '\x1B[27m'],
  59. 'strikethrough': ['\x1B[9m', '\x1B[29m'],
  60. 'white': ['\x1B[37m', '\x1B[39m'],
  61. 'gray': ['\x1B[90m', '\x1B[39m'],
  62. 'grey': ['\x1B[90m', '\x1B[39m'],
  63. 'black': ['\x1B[30m', '\x1B[39m'],
  64. 'blue': ['\x1B[34m', '\x1B[39m'],
  65. 'cyan': ['\x1B[36m', '\x1B[39m'],
  66. 'green': ['\x1B[32m', '\x1B[39m'],
  67. 'magenta': ['\x1B[35m', '\x1B[39m'],
  68. 'red': ['\x1B[31m', '\x1B[39m'],
  69. 'yellow': ['\x1B[33m', '\x1B[39m']
  70. };
  71. /**
  72. * HTML.
  73. * @type {!Object.<string,Array.<string>>}
  74. */
  75. var browserStyles = {
  76. 'bold' : ['<b>', '</b>'],
  77. 'italic' : ['<i>', '</i>'],
  78. 'underline' : ['<u>', '</u>'],
  79. 'inverse' : ['<span style="background-color:black;color:white;">', '</span>'],
  80. 'strikethrough' : ['<del>', '</del>'],
  81. 'white' : ['<span style="color:white;">', '</span>'],
  82. 'gray' : ['<span style="color:gray;">', '</span>'], // #46
  83. 'grey' : ['<span style="color:grey;">', '</span>'], // Let the user decide
  84. 'black' : ['<span style="color:black;">', '</span>'],
  85. 'blue' : ['<span style="color:blue;">', '</span>'],
  86. 'cyan' : ['<span style="color:cyan;">', '</span>'],
  87. 'green' : ['<span style="color:green;">', '</span>'],
  88. 'magenta' : ['<span style="color:magenta;">', '</span>'],
  89. 'red' : ['<span style="color:red;">', '</span>'],
  90. 'yellow' : ['<span style="color:yellow;">', '</span>']
  91. };
  92. /**
  93. * CSS.
  94. * @type {!Object.<string,Array.<string>>}
  95. */
  96. var cssStyles = { // #39
  97. 'bold' : ['<span class="ansi-escape ansi-escape-bold">', '</span>'],
  98. 'italic' : ['<span class="ansi-escape ansi-escape-italic">', '</span>'],
  99. 'underline' : ['<span class="ansi-escape ansi-escape-underline">', '</span>'],
  100. 'inverse' : ['<span class="ansi-escape ansi-escape-inverse">', '</span>'],
  101. 'strikethrough' : ['<span class="ansi-escape ansi-escape-strikethrough">', '</span>'],
  102. 'white' : ['<span class="ansi-escape ansi-escape-white">', '</span>'],
  103. 'gray' : ['<span class="ansi-escape ansi-escape-gray">', '</span>'],
  104. 'grey' : ['<span class="ansi-escape ansi-escape-grey">', '</span>'],
  105. 'black' : ['<span class="ansi-escape ansi-escape-black">', '</span>'],
  106. 'blue' : ['<span class="ansi-escape ansi-escape-blue">', '</span>'],
  107. 'cyan' : ['<span class="ansi-escape ansi-escape-cyan">', '</span>'],
  108. 'green' : ['<span class="ansi-escape ansi-escape-green">', '</span>'],
  109. 'magenta' : ['<span class="ansi-escape ansi-escape-magenta">', '</span>'],
  110. 'red' : ['<span class="ansi-escape ansi-escape-red">', '</span>'],
  111. 'yellow' : ['<span class="ansi-escape ansi-escape-yellow">', '</span>']
  112. };
  113. /**
  114. * Remember all getters that we defined.
  115. * @type {!Object}
  116. */
  117. var definedGetters = {};
  118. /**
  119. * Prototypes the string object to have additional properties that wraps the current string in colours when accessed.
  120. * @param {string} col Colour / property name
  121. * @param {function(string):string} func Wrapper function
  122. * @private
  123. */
  124. function addProperty(col, func) {
  125. // Exposed on top of the namespace
  126. colour[col] = function(str) {
  127. return func.apply(str);
  128. };
  129. // And on top of all strings
  130. try {
  131. String.prototype.__defineGetter__(col, func);
  132. definedGetters[col] = func;
  133. } catch (e) {} // #25
  134. }
  135. /**
  136. * Whether colour are currently installed on the global scope.
  137. * @type {boolean}
  138. * @private
  139. **/
  140. var installed = true;
  141. /**
  142. * Uninstalls colour from the global scope.
  143. * @returns {boolean} true if successfully uninstalled, false if already uninstalled
  144. * @expose
  145. */
  146. colour.uninstall = function() { // #41
  147. if (installed) {
  148. Object.keys(definedGetters).forEach(function(color) {
  149. try {
  150. String.prototype.__defineGetter__(color, null);
  151. } catch (e) {
  152. delete String.prototype[color];
  153. }
  154. });
  155. installed = false;
  156. return true;
  157. }
  158. return false;
  159. };
  160. /**
  161. * Reinstalls colour on the global scope.
  162. * @returns {boolean} true if successfully reinstalled, false if already installed
  163. * @expose
  164. */
  165. colour.install = function() {
  166. if (!installed) {
  167. Object.keys(definedGetters).forEach(function(color) {
  168. String.prototype.__defineGetter__(color, definedGetters[color]);
  169. });
  170. installed = true;
  171. return true;
  172. }
  173. return false;
  174. };
  175. /**
  176. * Applies a style to a string.
  177. * @param {string} str String to stylize
  178. * @param {string} style Style to apply
  179. * @returns {string}
  180. * @private
  181. */
  182. function stylize(str, style) {
  183. if (colour.mode == 'console') {
  184. return consoleStyles[style][0] + str + consoleStyles[style][1];
  185. } else if (colour.mode == 'browser') {
  186. return browserStyles[style][0] + str + browserStyles[style][1];
  187. } else if (colour.mode == 'browser-css') {
  188. return cssStyles[style][0] + str + browserStyles[style][1];
  189. }
  190. return str+'';
  191. }
  192. /**
  193. * Rainbow colours.
  194. * @type {!Array.<string>}
  195. * @const
  196. * @private
  197. */
  198. var rainbowColours = ['red', 'yellow', 'green', 'blue', 'magenta'];
  199. /**
  200. * String properties that should never be overwritten.
  201. * @type {!Array.<string>}
  202. * @const
  203. */
  204. var prototypeBlacklist = [
  205. '__defineGetter__', '__defineSetter__', '__lookupGetter__', '__lookupSetter__', 'charAt', 'constructor',
  206. 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'valueOf', 'charCodeAt',
  207. 'indexOf', 'lastIndexof', 'length', 'localeCompare', 'match', 'replace', 'search', 'slice', 'split', 'substring',
  208. 'toLocaleLowerCase', 'toLocaleUpperCase', 'toLowerCase', 'toUpperCase', 'trim', 'trimLeft', 'trimRight'
  209. ];
  210. /**
  211. * Applies a theme.
  212. * @param {!Object} theme Theme to apply
  213. */
  214. function applyTheme(theme) {
  215. Object.keys(theme).forEach(function(prop) {
  216. if (prototypeBlacklist.indexOf(prop) >= 0) {
  217. return;
  218. }
  219. if (typeof theme[prop] == 'string') {
  220. // Multiple colours white-space seperated #45, e.g. "red bold", #18
  221. theme[prop] = theme[prop].split(' ');
  222. }
  223. addProperty(prop, function () {
  224. var ret = this;
  225. for (var t=0; t<theme[prop].length; t++) {
  226. ret = colour[theme[prop][t]](ret);
  227. }
  228. return ret;
  229. });
  230. });
  231. }
  232. /**
  233. * Sets another theme.
  234. * @param {string|!Object} theme Theme name or
  235. * @returns {!Object|!Error|undefined}
  236. * @expose
  237. */
  238. colour.setTheme = function(theme) {
  239. if (typeof theme === 'string') {
  240. if (typeof colour.themes[theme] != 'undefined') {
  241. applyTheme(colour.themes[theme]);
  242. return colour.themes[theme];
  243. }
  244. /* else */ try /* to load it */ {
  245. colour.themes[theme] = require(theme);
  246. applyTheme(colour.themes[theme]);
  247. return colour.themes[theme];
  248. } catch (err) {
  249. return err;
  250. }
  251. } else {
  252. applyTheme(theme);
  253. }
  254. };
  255. /**
  256. * Extends a mapper with the current index inside the string as a second argument.
  257. * @param {function(string, number):string} map Sequencing function
  258. * @returns {function(string):string} Wrapped sequencer
  259. * @private
  260. */
  261. function sequencer(map) {
  262. return function () {
  263. if (this == undefined) return "";
  264. var i=0;
  265. return String.prototype.split.apply(this, [""]).map(map).join("");
  266. };
  267. }
  268. /**
  269. * Adds a sequencer that generates output depending on the index inside a string.
  270. * @param {string} name Sequencer name
  271. * @param {function(string, number):string} map Mapping function called for every character as the first and the
  272. * current index of that character as the second argument.
  273. * @expose
  274. */
  275. colour.addSequencer = function (name, map) {
  276. addProperty(name, sequencer(map));
  277. };
  278. // Apply defaults
  279. Object.keys(consoleStyles).forEach(
  280. function (style) {
  281. addProperty(style, function () {
  282. return stylize(this, style);
  283. });
  284. }
  285. );
  286. colour.addSequencer('rainbow', function(letter, i) {
  287. return letter === " " ? letter : stylize(letter, rainbowColours[i++ % rainbowColours.length]);
  288. });
  289. colour.addSequencer('zebra', sequencer(function (letter, i) {
  290. return i % 2 === 0 ? letter : letter.inverse;
  291. }));
  292. function strip() {
  293. return this.replace(/\x1B\[\d+m/g, '')
  294. .replace(/<\/?(?:span|u|i|u|del)\b[^>]*>/g, '');
  295. }
  296. addProperty('strip', strip);
  297. addProperty('stripColors', strip); // Compatibility
  298. if (typeof module !== 'undefined' && module['exports']) {
  299. module.exports = colour;
  300. } else if (typeof define !== 'undefined' && define.amd) {
  301. define("colour", function() { return colour; });
  302. define("colors", function() { return colour; });
  303. } else {
  304. colour.mode = 'browser';
  305. global['colour'] = global['colors'] = colour;
  306. }
  307. })(this);