index.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. /* jshint rhino:true, unused: false */
  2. /* jscs:disable validateIndentation */
  3. /*global name:true, less, loadStyleSheet, os */
  4. function formatError(ctx, options) {
  5. options = options || {};
  6. var message = "";
  7. var extract = ctx.extract;
  8. var error = [];
  9. // var stylize = options.color ? require('./lessc_helper').stylize : function (str) { return str; };
  10. var stylize = function (str) { return str; };
  11. // only output a stack if it isn't a less error
  12. if (ctx.stack && !ctx.type) { return stylize(ctx.stack, 'red'); }
  13. if (!ctx.hasOwnProperty('index') || !extract) {
  14. return ctx.stack || ctx.message;
  15. }
  16. if (typeof extract[0] === 'string') {
  17. error.push(stylize((ctx.line - 1) + ' ' + extract[0], 'grey'));
  18. }
  19. if (typeof extract[1] === 'string') {
  20. var errorTxt = ctx.line + ' ';
  21. if (extract[1]) {
  22. errorTxt += extract[1].slice(0, ctx.column) +
  23. stylize(stylize(stylize(extract[1][ctx.column], 'bold') +
  24. extract[1].slice(ctx.column + 1), 'red'), 'inverse');
  25. }
  26. error.push(errorTxt);
  27. }
  28. if (typeof extract[2] === 'string') {
  29. error.push(stylize((ctx.line + 1) + ' ' + extract[2], 'grey'));
  30. }
  31. error = error.join('\n') + stylize('', 'reset') + '\n';
  32. message += stylize(ctx.type + 'Error: ' + ctx.message, 'red');
  33. if (ctx.filename) {
  34. message += stylize(' in ', 'red') + ctx.filename +
  35. stylize(' on line ' + ctx.line + ', column ' + (ctx.column + 1) + ':', 'grey');
  36. }
  37. message += '\n' + error;
  38. if (ctx.callLine) {
  39. message += stylize('from ', 'red') + (ctx.filename || '') + '/n';
  40. message += stylize(ctx.callLine, 'grey') + ' ' + ctx.callExtract + '/n';
  41. }
  42. return message;
  43. }
  44. function writeError(ctx, options) {
  45. options = options || {};
  46. if (options.silent) { return; }
  47. var message = formatError(ctx, options);
  48. throw new Error(message);
  49. }
  50. function loadStyleSheet(sheet, callback, reload, remaining) {
  51. var endOfPath = Math.max(name.lastIndexOf('/'), name.lastIndexOf('\\')),
  52. sheetName = name.slice(0, endOfPath + 1) + sheet.href,
  53. contents = sheet.contents || {},
  54. input = readFile(sheetName);
  55. input = input.replace(/^\xEF\xBB\xBF/, '');
  56. contents[sheetName] = input;
  57. var parser = new less.Parser({
  58. paths: [sheet.href.replace(/[\w\.-]+$/, '')],
  59. contents: contents
  60. });
  61. parser.parse(input, function (e, root) {
  62. if (e) {
  63. return writeError(e);
  64. }
  65. try {
  66. callback(e, root, input, sheet, { local: false, lastModified: 0, remaining: remaining }, sheetName);
  67. } catch(e) {
  68. writeError(e);
  69. }
  70. });
  71. }
  72. less.Parser.fileLoader = function (file, currentFileInfo, callback, env) {
  73. var href = file;
  74. if (currentFileInfo && currentFileInfo.currentDirectory && !/^\//.test(file)) {
  75. href = less.modules.path.join(currentFileInfo.currentDirectory, file);
  76. }
  77. var path = less.modules.path.dirname(href);
  78. var newFileInfo = {
  79. currentDirectory: path + '/',
  80. filename: href
  81. };
  82. if (currentFileInfo) {
  83. newFileInfo.entryPath = currentFileInfo.entryPath;
  84. newFileInfo.rootpath = currentFileInfo.rootpath;
  85. newFileInfo.rootFilename = currentFileInfo.rootFilename;
  86. newFileInfo.relativeUrls = currentFileInfo.relativeUrls;
  87. } else {
  88. newFileInfo.entryPath = path;
  89. newFileInfo.rootpath = less.rootpath || path;
  90. newFileInfo.rootFilename = href;
  91. newFileInfo.relativeUrls = env.relativeUrls;
  92. }
  93. var j = file.lastIndexOf('/');
  94. if (newFileInfo.relativeUrls && !/^(?:[a-z-]+:|\/)/.test(file) && j != -1) {
  95. var relativeSubDirectory = file.slice(0, j + 1);
  96. newFileInfo.rootpath = newFileInfo.rootpath + relativeSubDirectory; // append (sub|sup) directory path of imported file
  97. }
  98. newFileInfo.currentDirectory = path;
  99. newFileInfo.filename = href;
  100. var data = null;
  101. try {
  102. data = readFile(href);
  103. } catch (e) {
  104. callback({ type: 'File', message: "'" + less.modules.path.basename(href) + "' wasn't found" });
  105. return;
  106. }
  107. try {
  108. callback(null, data, href, newFileInfo, { lastModified: 0 });
  109. } catch (e) {
  110. callback(e, null, href);
  111. }
  112. };
  113. function writeFile(filename, content) {
  114. var fstream = new java.io.FileWriter(filename);
  115. var out = new java.io.BufferedWriter(fstream);
  116. out.write(content);
  117. out.close();
  118. }
  119. // Command line integration via Rhino
  120. (function (args) {
  121. var options = {
  122. depends: false,
  123. compress: false,
  124. cleancss: false,
  125. max_line_len: -1,
  126. silent: false,
  127. verbose: false,
  128. lint: false,
  129. paths: [],
  130. color: true,
  131. strictImports: false,
  132. rootpath: '',
  133. relativeUrls: false,
  134. ieCompat: true,
  135. strictMath: false,
  136. strictUnits: false
  137. };
  138. var continueProcessing = true,
  139. currentErrorcode;
  140. var checkArgFunc = function(arg, option) {
  141. if (!option) {
  142. print(arg + " option requires a parameter");
  143. continueProcessing = false;
  144. return false;
  145. }
  146. return true;
  147. };
  148. var checkBooleanArg = function(arg) {
  149. var onOff = /^((on|t|true|y|yes)|(off|f|false|n|no))$/i.exec(arg);
  150. if (!onOff) {
  151. print(" unable to parse " + arg + " as a boolean. use one of on/t/true/y/yes/off/f/false/n/no");
  152. continueProcessing = false;
  153. return false;
  154. }
  155. return Boolean(onOff[2]);
  156. };
  157. var warningMessages = "";
  158. var sourceMapFileInline = false;
  159. args = args.filter(function (arg) {
  160. var match = arg.match(/^-I(.+)$/);
  161. if (match) {
  162. options.paths.push(match[1]);
  163. return false;
  164. }
  165. match = arg.match(/^--?([a-z][0-9a-z-]*)(?:=(.*))?$/i);
  166. if (match) {
  167. arg = match[1];
  168. }
  169. else {
  170. return arg;
  171. }
  172. switch (arg) {
  173. case 'v':
  174. case 'version':
  175. console.log("lessc " + less.version.join('.') + " (Less Compiler) [JavaScript]");
  176. continueProcessing = false;
  177. break;
  178. case 'verbose':
  179. options.verbose = true;
  180. break;
  181. case 's':
  182. case 'silent':
  183. options.silent = true;
  184. break;
  185. case 'l':
  186. case 'lint':
  187. options.lint = true;
  188. break;
  189. case 'strict-imports':
  190. options.strictImports = true;
  191. break;
  192. case 'h':
  193. case 'help':
  194. //TODO
  195. // require('../lib/less/lessc_helper').printUsage();
  196. continueProcessing = false;
  197. break;
  198. case 'x':
  199. case 'compress':
  200. options.compress = true;
  201. break;
  202. case 'M':
  203. case 'depends':
  204. options.depends = true;
  205. break;
  206. case 'yui-compress':
  207. warningMessages += "yui-compress option has been removed. assuming clean-css.";
  208. options.cleancss = true;
  209. break;
  210. case 'clean-css':
  211. options.cleancss = true;
  212. break;
  213. case 'max-line-len':
  214. if (checkArgFunc(arg, match[2])) {
  215. options.maxLineLen = parseInt(match[2], 10);
  216. if (options.maxLineLen <= 0) {
  217. options.maxLineLen = -1;
  218. }
  219. }
  220. break;
  221. case 'no-color':
  222. options.color = false;
  223. break;
  224. case 'no-ie-compat':
  225. options.ieCompat = false;
  226. break;
  227. case 'no-js':
  228. options.javascriptEnabled = false;
  229. break;
  230. case 'include-path':
  231. if (checkArgFunc(arg, match[2])) {
  232. // support for both ; and : path separators
  233. // even on windows when using absolute paths with drive letters (eg C:\path:D:\path)
  234. options.paths = match[2]
  235. .split(os.type().match(/Windows/) ? /:(?!\\)|;/ : ':')
  236. .map(function(p) {
  237. if (p) {
  238. // return path.resolve(process.cwd(), p);
  239. return p;
  240. }
  241. });
  242. }
  243. break;
  244. case 'line-numbers':
  245. if (checkArgFunc(arg, match[2])) {
  246. options.dumpLineNumbers = match[2];
  247. }
  248. break;
  249. case 'source-map':
  250. if (!match[2]) {
  251. options.sourceMap = true;
  252. } else {
  253. options.sourceMap = match[2];
  254. }
  255. break;
  256. case 'source-map-rootpath':
  257. if (checkArgFunc(arg, match[2])) {
  258. options.sourceMapRootpath = match[2];
  259. }
  260. break;
  261. case 'source-map-basepath':
  262. if (checkArgFunc(arg, match[2])) {
  263. options.sourceMapBasepath = match[2];
  264. }
  265. break;
  266. case 'source-map-map-inline':
  267. sourceMapFileInline = true;
  268. options.sourceMap = true;
  269. break;
  270. case 'source-map-less-inline':
  271. options.outputSourceFiles = true;
  272. break;
  273. case 'source-map-url':
  274. if (checkArgFunc(arg, match[2])) {
  275. options.sourceMapURL = match[2];
  276. }
  277. break;
  278. case 'source-map-output-map-file':
  279. if (checkArgFunc(arg, match[2])) {
  280. options.writeSourceMap = function(sourceMapContent) {
  281. writeFile(match[2], sourceMapContent);
  282. };
  283. }
  284. break;
  285. case 'rp':
  286. case 'rootpath':
  287. if (checkArgFunc(arg, match[2])) {
  288. options.rootpath = match[2].replace(/\\/g, '/');
  289. }
  290. break;
  291. case "ru":
  292. case "relative-urls":
  293. options.relativeUrls = true;
  294. break;
  295. case "sm":
  296. case "strict-math":
  297. if (checkArgFunc(arg, match[2])) {
  298. options.strictMath = checkBooleanArg(match[2]);
  299. }
  300. break;
  301. case "su":
  302. case "strict-units":
  303. if (checkArgFunc(arg, match[2])) {
  304. options.strictUnits = checkBooleanArg(match[2]);
  305. }
  306. break;
  307. default:
  308. console.log('invalid option ' + arg);
  309. continueProcessing = false;
  310. }
  311. });
  312. if (!continueProcessing) {
  313. return;
  314. }
  315. var name = args[0];
  316. if (name && name != '-') {
  317. // name = path.resolve(process.cwd(), name);
  318. }
  319. var output = args[1];
  320. var outputbase = args[1];
  321. if (output) {
  322. options.sourceMapOutputFilename = output;
  323. // output = path.resolve(process.cwd(), output);
  324. if (warningMessages) {
  325. console.log(warningMessages);
  326. }
  327. }
  328. // options.sourceMapBasepath = process.cwd();
  329. // options.sourceMapBasepath = '';
  330. if (options.sourceMap === true) {
  331. console.log("output: " + output);
  332. if (!output && !sourceMapFileInline) {
  333. console.log("the sourcemap option only has an optional filename if the css filename is given");
  334. return;
  335. }
  336. options.sourceMapFullFilename = options.sourceMapOutputFilename + ".map";
  337. options.sourceMap = less.modules.path.basename(options.sourceMapFullFilename);
  338. } else if (options.sourceMap) {
  339. options.sourceMapOutputFilename = options.sourceMap;
  340. }
  341. if (!name) {
  342. console.log("lessc: no inout files");
  343. console.log("");
  344. // TODO
  345. // require('../lib/less/lessc_helper').printUsage();
  346. currentErrorcode = 1;
  347. return;
  348. }
  349. // var ensureDirectory = function (filepath) {
  350. // var dir = path.dirname(filepath),
  351. // cmd,
  352. // existsSync = fs.existsSync || path.existsSync;
  353. // if (!existsSync(dir)) {
  354. // if (mkdirp === undefined) {
  355. // try {mkdirp = require('mkdirp');}
  356. // catch(e) { mkdirp = null; }
  357. // }
  358. // cmd = mkdirp && mkdirp.sync || fs.mkdirSync;
  359. // cmd(dir);
  360. // }
  361. // };
  362. if (options.depends) {
  363. if (!outputbase) {
  364. console.log("option --depends requires an output path to be specified");
  365. return;
  366. }
  367. console.log(outputbase + ": ");
  368. }
  369. if (!name) {
  370. console.log('No files present in the fileset');
  371. quit(1);
  372. }
  373. var input = null;
  374. try {
  375. input = readFile(name, 'utf-8');
  376. } catch (e) {
  377. console.log('lesscss: couldn\'t open file ' + name);
  378. quit(1);
  379. }
  380. options.filename = name;
  381. var result;
  382. try {
  383. var parser = new less.Parser(options);
  384. parser.parse(input, function (e, root) {
  385. if (e) {
  386. writeError(e, options);
  387. quit(1);
  388. } else {
  389. result = root.toCSS(options);
  390. if (output) {
  391. writeFile(output, result);
  392. console.log("Written to " + output);
  393. } else {
  394. print(result);
  395. }
  396. quit(0);
  397. }
  398. });
  399. }
  400. catch(e) {
  401. writeError(e, options);
  402. quit(1);
  403. }
  404. }(arguments));