evaluator.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. // Except for a block in #visitImport, everything here is an exact copy of the
  2. // source in stylus this currently depends on. If stylus is updated, update
  3. // this appropriately.
  4. var Evaluator = require('stylus/lib/visitor/evaluator')
  5. , nodes = require('stylus/lib/nodes')
  6. , Stack = require('stylus/lib/stack')
  7. , Frame = require('stylus/lib/stack/frame')
  8. , Scope = require('stylus/lib/stack/scope')
  9. , utils = require('stylus/lib/utils')
  10. , bifs = require('stylus/lib/functions')
  11. , basename = require('path').basename
  12. , dirname = require('path').dirname
  13. , relative = require('path').relative
  14. , join = require('path').join
  15. , colors = require('stylus/lib/colors')
  16. // , debug = require('debug')('stylus:evaluator')
  17. , fs = require('fs');
  18. module.exports = CachedPathEvaluator;
  19. /**
  20. * Import `file` and return Block node.
  21. *
  22. * @api private
  23. */
  24. function importFile(node, file, literal, index) {
  25. var importStack = this.importStack
  26. , Parser = require('stylus/lib/parser')
  27. , stat;
  28. // Handling the `require`
  29. if (node.once) {
  30. if (this.requireHistory[file]) return nodes.null;
  31. this.requireHistory[file] = true;
  32. if (literal && !this.includeCSS) {
  33. return node;
  34. }
  35. }
  36. // Expose imports
  37. node.path = file;
  38. node.dirname = dirname(file);
  39. // Store the modified time
  40. stat = fs.statSync(file);
  41. node.mtime = stat.mtime;
  42. this.paths.push(node.dirname);
  43. // Avoid overflows from importing the same file over again
  44. if (file === importStack[importStack.length - 1]) return nodes.null;
  45. if (this.options._imports) this.options._imports.push(node.clone());
  46. // Parse the file
  47. importStack.push(file);
  48. nodes.filename = file;
  49. var str;
  50. if (this.cache.sources && this.cache.sources[file]) {
  51. str = this.cache.sources[file];
  52. } else {
  53. str = fs.readFileSync(file, 'utf8');
  54. }
  55. if (literal && !this.resolveURL) return new nodes.Literal(str.replace(/\r\n?/g, '\n'));
  56. // parse
  57. var block = new nodes.Block
  58. , parser = new Parser(str, utils.merge({ root: block }, this.options));
  59. try {
  60. block = parser.parse();
  61. } catch (err) {
  62. err.filename = file;
  63. err.lineno = parser.lexer.lineno;
  64. err.input = str;
  65. throw err;
  66. }
  67. // Evaluate imported "root"
  68. block.parent = this.root;
  69. block.scope = false;
  70. var ret = this.visit(block);
  71. importStack.pop();
  72. if (importStack.length || index) this.paths.pop();
  73. return ret;
  74. }
  75. function CachedPathEvaluator(root, options) {
  76. Evaluator.apply(this, arguments);
  77. this.cache = options.cache;
  78. }
  79. CachedPathEvaluator.prototype = Object.create(Evaluator.prototype);
  80. CachedPathEvaluator.prototype.constructor = CachedPathEvaluator;
  81. CachedPathEvaluator.prototype.visitImport = function(imported) {
  82. this.return++;
  83. var path = this.visit(imported.path).first
  84. , nodeName = imported.once ? 'require' : 'import'
  85. , found
  86. , literal
  87. , index;
  88. this.return--;
  89. // debug('import %s', path);
  90. // url() passed
  91. if ('url' == path.name) {
  92. if (imported.once) throw new Error('You cannot @require a url');
  93. return imported;
  94. }
  95. // Ensure string
  96. if (!path.string) throw new Error('@' + nodeName + ' string expected');
  97. var name = path = path.string;
  98. // Absolute URL
  99. if (/url\s*\(\s*['"]?(?:https?:)?\/\//i.test(path)) {
  100. if (imported.once) throw new Error('You cannot @require a url');
  101. return imported;
  102. }
  103. // Literal
  104. if (/\.css(?:"|$)/.test(path)) {
  105. literal = true;
  106. if (!imported.once && !this.includeCSS) {
  107. return imported;
  108. }
  109. }
  110. // support optional .styl
  111. if (!literal && !/\.styl$/i.test(path)) path += '.styl';
  112. /*****************************************************************************
  113. * THIS IS THE ONLY BLOCK THAT DIFFERS FROM THE ACTUAL STYLUS IMPLEMENTATION. *
  114. *****************************************************************************/
  115. // Lookup
  116. var dirname = this.paths[this.paths.length - 1];
  117. found = this.cache.find(path, dirname);
  118. index = this.cache.isIndex(path, dirname);
  119. if (!found) {
  120. found = utils.find(path, this.paths, this.filename);
  121. if (!found) {
  122. found = utils.lookupIndex(name, this.paths, this.filename);
  123. index = true;
  124. }
  125. }
  126. // Throw if import failed
  127. if (!found) throw new Error('failed to locate @' + nodeName + ' file ' + path);
  128. var block = new nodes.Block;
  129. for (var i = 0, len = found.length; i < len; ++i) {
  130. block.push(importFile.call(this, imported, found[i], literal, index));
  131. }
  132. return block;
  133. }