1 (function() { 2 // 3 // Stub out `require` in rhino 4 // 5 function require(arg) { 6 return less[arg.split('/')[1]]; 7 }; 8 9 10 // ecma-5.js 11 // 12 // -- kriskowal Kris Kowal Copyright (C) 2009-2010 MIT License 13 // -- tlrobinson Tom Robinson 14 // dantman Daniel Friesen 15 16 // 17 // Array 18 // 19 if (!Array.isArray) { 20 Array.isArray = function(obj) { 21 return Object.prototype.toString.call(obj) === "[object Array]" || 22 (obj instanceof Array); 23 }; 24 } 25 if (!Array.prototype.forEach) { 26 Array.prototype.forEach = function(block, thisObject) { 27 var len = this.length >>> 0; 28 for (var i = 0; i < len; i++) { 29 if (i in this) { 30 block.call(thisObject, this[i], i, this); 31 } 32 } 33 }; 34 } 35 if (!Array.prototype.map) { 36 Array.prototype.map = function(fun /*, thisp*/) { 37 var len = this.length >>> 0; 38 var res = new Array(len); 39 var thisp = arguments[1]; 40 41 for (var i = 0; i < len; i++) { 42 if (i in this) { 43 res[i] = fun.call(thisp, this[i], i, this); 44 } 45 } 46 return res; 47 }; 48 } 49 if (!Array.prototype.filter) { 50 Array.prototype.filter = function (block /*, thisp */) { 51 var values = []; 52 var thisp = arguments[1]; 53 for (var i = 0; i < this.length; i++) { 54 if (block.call(thisp, this[i])) { 55 values.push(this[i]); 56 } 57 } 58 return values; 59 }; 60 } 61 if (!Array.prototype.reduce) { 62 Array.prototype.reduce = function(fun /*, initial*/) { 63 var len = this.length >>> 0; 64 var i = 0; 65 66 // no value to return if no initial value and an empty array 67 if (len === 0 && arguments.length === 1) throw new TypeError(); 68 69 if (arguments.length >= 2) { 70 var rv = arguments[1]; 71 } else { 72 do { 73 if (i in this) { 74 rv = this[i++]; 75 break; 76 } 77 // if array contains no values, no initial value to return 78 if (++i >= len) throw new TypeError(); 79 } while (true); 80 } 81 for (; i < len; i++) { 82 if (i in this) { 83 rv = fun.call(null, rv, this[i], i, this); 84 } 85 } 86 return rv; 87 }; 88 } 89 if (!Array.prototype.indexOf) { 90 Array.prototype.indexOf = function (value /*, fromIndex */ ) { 91 var length = this.length; 92 var i = arguments[1] || 0; 93 94 if (!length) return -1; 95 if (i >= length) return -1; 96 if (i < 0) i += length; 97 98 for (; i < length; i++) { 99 if (!Object.prototype.hasOwnProperty.call(this, i)) { continue } 100 if (value === this[i]) return i; 101 } 102 return -1; 103 }; 104 } 105 106 // 107 // Object 108 // 109 if (!Object.keys) { 110 Object.keys = function (object) { 111 var keys = []; 112 for (var name in object) { 113 if (Object.prototype.hasOwnProperty.call(object, name)) { 114 keys.push(name); 115 } 116 } 117 return keys; 118 }; 119 } 120 121 // 122 // String 123 // 124 if (!String.prototype.trim) { 125 String.prototype.trim = function () { 126 return String(this).replace(/^\s\s*/, '').replace(/\s\s*$/, ''); 127 }; 128 } 129 var less, tree; 130 131 if (typeof HopObject === 'function') { 132 // Helma 133 // Fully qualified condition would be helma.main.Server.getServer() === 'string' 134 less = global.less = {} 135 tree = less.tree = {} 136 less.mode = 'rhino'; 137 } else if (typeof environment === "object" && ({}).toString.call(environment) === "[object Environment]") { 138 // Rhino 139 // Details on how to detect Rhino: https://github.com/ringo/ringojs/issues/88 140 if (typeof(window) === 'undefined') { less = {} } 141 else { less = window.less = {} } 142 tree = less.tree = {}; 143 less.mode = 'rhino'; 144 } else if (typeof(window) === 'undefined') { 145 // Node.js 146 less = exports, 147 tree = require('./tree'); 148 less.mode = 'node'; 149 } else { 150 // Browser 151 if (typeof(window.less) === 'undefined') { window.less = {} } 152 less = window.less, 153 tree = window.less.tree = {}; 154 less.mode = 'browser'; 155 } 156 // 157 // less.js - parser 158 // 159 // A relatively straight-forward predictive parser. 160 // There is no tokenization/lexing stage, the input is parsed 161 // in one sweep. 162 // 163 // To make the parser fast enough to run in the browser, several 164 // optimization had to be made: 165 // 166 // - Matching and slicing on a huge input is often cause of slowdowns. 167 // The solution is to chunkify the input into smaller strings. 168 // The chunks are stored in the `chunks` var, 169 // `j` holds the current chunk index, and `current` holds 170 // the index of the current chunk in relation to `input`. 171 // This gives us an almost 4x speed-up. 172 // 173 // - In many cases, we don't need to match individual tokens; 174 // for example, if a value doesn't hold any variables, operations 175 // or dynamic references, the parser can effectively 'skip' it, 176 // treating it as a literal. 177 // An example would be '1px solid #000' - which evaluates to itself, 178 // we don't need to know what the individual components are. 179 // The drawback, of course is that you don't get the benefits of 180 // syntax-checking on the CSS. This gives us a 50% speed-up in the parser, 181 // and a smaller speed-up in the code-gen. 182 // 183 // 184 // Token matching is done with the `$` function, which either takes 185 // a terminal string or regexp, or a non-terminal function to call. 186 // It also takes care of moving all the indices forwards. 187 // 188 // 189 less.Parser = function Parser(env) { 190 var input, // LeSS input string 191 i, // current index in `input` 192 j, // current chunk 193 temp, // temporarily holds a chunk's state, for backtracking 194 memo, // temporarily holds `i`, when backtracking 195 furthest, // furthest index the parser has gone to 196 chunks, // chunkified input 197 current, // index of current chunk, in `input` 198 parser; 199 200 var that = this; 201 202 // This function is called after all files 203 // have been imported through `@import`. 204 var finish = function () {}; 205 206 var imports = this.imports = { 207 paths: env && env.paths || [], // Search paths, when importing 208 queue: [], // Files which haven't been imported yet 209 files: {}, // Holds the imported parse trees 210 contents: {}, // Holds the imported file contents 211 mime: env && env.mime, // MIME type of .less files 212 error: null, // Error in parsing/evaluating an import 213 push: function (path, callback) { 214 var that = this; 215 this.queue.push(path); 216 217 // 218 // Import a file asynchronously 219 // 220 less.Parser.importer(path, this.paths, function (e, root, contents) { 221 that.queue.splice(that.queue.indexOf(path), 1); // Remove the path from the queue 222 223 var imported = path in that.files; 224 225 that.files[path] = root; // Store the root 226 that.contents[path] = contents; 227 228 if (e && !that.error) { that.error = e } 229 230 callback(e, root, imported); 231 232 if (that.queue.length === 0) { finish() } // Call `finish` if we're done importing 233 }, env); 234 } 235 }; 236 237 function save() { temp = chunks[j], memo = i, current = i } 238 function restore() { chunks[j] = temp, i = memo, current = i } 239 240 function sync() { 241 if (i > current) { 242 chunks[j] = chunks[j].slice(i - current); 243 current = i; 244 } 245 } 246 // 247 // Parse from a token, regexp or string, and move forward if match 248 // 249 function $(tok) { 250 var match, args, length, c, index, endIndex, k, mem; 251 252 // 253 // Non-terminal 254 // 255 if (tok instanceof Function) { 256 return tok.call(parser.parsers); 257 // 258 // Terminal 259 // 260 // Either match a single character in the input, 261 // or match a regexp in the current chunk (chunk[j]). 262 // 263 } else if (typeof(tok) === 'string') { 264 match = input.charAt(i) === tok ? tok : null; 265 length = 1; 266 sync (); 267 } else { 268 sync (); 269 270 if (match = tok.exec(chunks[j])) { 271 length = match[0].length; 272 } else { 273 return null; 274 } 275 } 276 277 // The match is confirmed, add the match length to `i`, 278 // and consume any extra white-space characters (' ' || '\n') 279 // which come after that. The reason for this is that LeSS's 280 // grammar is mostly white-space insensitive. 281 // 282 if (match) { 283 mem = i += length; 284 endIndex = i + chunks[j].length - length; 285 286 while (i < endIndex) { 287 c = input.charCodeAt(i); 288 if (! (c === 32 || c === 10 || c === 9)) { break } 289 i++; 290 } 291 chunks[j] = chunks[j].slice(length + (i - mem)); 292 current = i; 293 294 if (chunks[j].length === 0 && j < chunks.length - 1) { j++ } 295 296 if(typeof(match) === 'string') { 297 return match; 298 } else { 299 return match.length === 1 ? match[0] : match; 300 } 301 } 302 } 303 304 function expect(arg, msg) { 305 var result = $(arg); 306 if (! result) { 307 error(msg || (typeof(arg) === 'string' ? "expected '" + arg + "' got '" + input.charAt(i) + "'" 308 : "unexpected token")); 309 } else { 310 return result; 311 } 312 } 313 314 function error(msg, type) { 315 throw { index: i, type: type || 'Syntax', message: msg }; 316 } 317 318 // Same as $(), but don't change the state of the parser, 319 // just return the match. 320 function peek(tok) { 321 if (typeof(tok) === 'string') { 322 return input.charAt(i) === tok; 323 } else { 324 if (tok.test(chunks[j])) { 325 return true; 326 } else { 327 return false; 328 } 329 } 330 } 331 332 function basename(pathname) { 333 if (less.mode === 'node') { 334 return require('path').basename(pathname); 335 } else { 336 return pathname.match(/[^\/]+$/)[0]; 337 } 338 } 339 340 function getInput(e, env) { 341 if (e.filename && env.filename && (e.filename !== env.filename)) { 342 return parser.imports.contents[basename(e.filename)]; 343 } else { 344 return input; 345 } 346 } 347 348 function getLocation(index, input) { 349 for (var n = index, column = -1; 350 n >= 0 && input.charAt(n) !== '\n'; 351 n--) { column++ } 352 353 return { line: typeof(index) === 'number' ? (input.slice(0, index).match(/\n/g) || "").length : null, 354 column: column }; 355 } 356 357 function LessError(e, env) { 358 var input = getInput(e, env), 359 loc = getLocation(e.index, input), 360 line = loc.line, 361 col = loc.column, 362 lines = input.split('\n'); 363 364 this.type = e.type || 'Syntax'; 365 this.message = e.message; 366 this.filename = e.filename || env.filename; 367 this.index = e.index; 368 this.line = typeof(line) === 'number' ? line + 1 : null; 369 this.callLine = e.call && (getLocation(e.call, input).line + 1); 370 this.callExtract = lines[getLocation(e.call, input).line]; 371 this.stack = e.stack; 372 this.column = col; 373 this.extract = [ 374 lines[line - 1], 375 lines[line], 376 lines[line + 1] 377 ]; 378 } 379 380 this.env = env = env || {}; 381 382 // The optimization level dictates the thoroughness of the parser, 383 // the lower the number, the less nodes it will create in the tree. 384 // This could matter for debugging, or if you want to access 385 // the individual nodes in the tree. 386 this.optimization = ('optimization' in this.env) ? this.env.optimization : 1; 387 388 this.env.filename = this.env.filename || null; 389 390 // 391 // The Parser 392 // 393 return parser = { 394 395 imports: imports, 396 // 397 // Parse an input string into an abstract syntax tree, 398 // call `callback` when done. 399 // 400 parse: function (str, callback) { 401 var root, start, end, zone, line, lines, buff = [], c, error = null; 402 403 i = j = current = furthest = 0; 404 input = str.replace(/\r\n/g, '\n'); 405 406 // Split the input into chunks. 407 chunks = (function (chunks) { 408 var j = 0, 409 skip = /[^"'`\{\}\/\(\)\\]+/g, 410 comment = /\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g, 411 string = /"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'|`((?:[^`\\\r\n]|\\.)*)`/g, 412 level = 0, 413 match, 414 chunk = chunks[0], 415 inParam; 416 417 for (var i = 0, c, cc; i < input.length; i++) { 418 skip.lastIndex = i; 419 if (match = skip.exec(input)) { 420 if (match.index === i) { 421 i += match[0].length; 422 chunk.push(match[0]); 423 } 424 } 425 c = input.charAt(i); 426 comment.lastIndex = string.lastIndex = i; 427 428 if (match = string.exec(input)) { 429 if (match.index === i) { 430 i += match[0].length; 431 chunk.push(match[0]); 432 c = input.charAt(i); 433 } 434 } 435 436 if (!inParam && c === '/') { 437 cc = input.charAt(i + 1); 438 if (cc === '/' || cc === '*') { 439 if (match = comment.exec(input)) { 440 if (match.index === i) { 441 i += match[0].length; 442 chunk.push(match[0]); 443 c = input.charAt(i); 444 } 445 } 446 } 447 } 448 449 switch (c) { 450 case '{': if (! inParam) { level ++; chunk.push(c); break } 451 case '}': if (! inParam) { level --; chunk.push(c); chunks[++j] = chunk = []; break } 452 case '(': if (! inParam) { inParam = true; chunk.push(c); break } 453 case ')': if ( inParam) { inParam = false; chunk.push(c); break } 454 default: chunk.push(c); 455 } 456 } 457 if (level > 0) { 458 error = new(LessError)({ 459 index: i, 460 type: 'Parse', 461 message: "missing closing `}`", 462 filename: env.filename 463 }, env); 464 } 465 466 return chunks.map(function (c) { return c.join('') });; 467 })([[]]); 468 469 if (error) { 470 return callback(error); 471 } 472 473 // Start with the primary rule. 474 // The whole syntax tree is held under a Ruleset node, 475 // with the `root` property set to true, so no `{}` are 476 // output. The callback is called when the input is parsed. 477 try { 478 root = new(tree.Ruleset)([], $(this.parsers.primary)); 479 root.root = true; 480 } catch (e) { 481 return callback(new(LessError)(e, env)); 482 } 483 484 root.toCSS = (function (evaluate) { 485 var line, lines, column; 486 487 return function (options, variables) { 488 var frames = [], importError; 489 490 options = options || {}; 491 // 492 // Allows setting variables with a hash, so: 493 // 494 // `{ color: new(tree.Color)('#f01') }` will become: 495 // 496 // new(tree.Rule)('@color', 497 // new(tree.Value)([ 498 // new(tree.Expression)([ 499 // new(tree.Color)('#f01') 500 // ]) 501 // ]) 502 // ) 503 // 504 if (typeof(variables) === 'object' && !Array.isArray(variables)) { 505 variables = Object.keys(variables).map(function (k) { 506 var value = variables[k]; 507 508 if (! (value instanceof tree.Value)) { 509 if (! (value instanceof tree.Expression)) { 510 value = new(tree.Expression)([value]); 511 } 512 value = new(tree.Value)([value]); 513 } 514 return new(tree.Rule)('@' + k, value, false, 0); 515 }); 516 frames = [new(tree.Ruleset)(null, variables)]; 517 } 518 519 try { 520 var css = evaluate.call(this, { frames: frames }) 521 .toCSS([], { compress: options.compress || false }); 522 } catch (e) { 523 throw new(LessError)(e, env); 524 } 525 526 if ((importError = parser.imports.error)) { // Check if there was an error during importing 527 if (importError instanceof LessError) throw importError; 528 else throw new(LessError)(importError, env); 529 } 530 531 if (options.yuicompress && less.mode === 'node') { 532 return require('./cssmin').compressor.cssmin(css); 533 } else if (options.compress) { 534 return css.replace(/(\s)+/g, "$1"); 535 } else { 536 return css; 537 } 538 }; 539 })(root.eval); 540 541 // If `i` is smaller than the `input.length - 1`, 542 // it means the parser wasn't able to parse the whole 543 // string, so we've got a parsing error. 544 // 545 // We try to extract a \n delimited string, 546 // showing the line where the parse error occured. 547 // We split it up into two parts (the part which parsed, 548 // and the part which didn't), so we can color them differently. 549 if (i < input.length - 1) { 550 i = furthest; 551 lines = input.split('\n'); 552 line = (input.slice(0, i).match(/\n/g) || "").length + 1; 553 554 for (var n = i, column = -1; n >= 0 && input.charAt(n) !== '\n'; n--) { column++ } 555 556 error = { 557 type: "Parse", 558 message: "Syntax Error on line " + line, 559 index: i, 560 filename: env.filename, 561 line: line, 562 column: column, 563 extract: [ 564 lines[line - 2], 565 lines[line - 1], 566 lines[line] 567 ] 568 }; 569 } 570 571 if (this.imports.queue.length > 0) { 572 finish = function () { callback(error, root) }; 573 } else { 574 callback(error, root); 575 } 576 }, 577 578 // 579 // Here in, the parsing rules/functions 580 // 581 // The basic structure of the syntax tree generated is as follows: 582 // 583 // Ruleset -> Rule -> Value -> Expression -> Entity 584 // 585 // Here's some LESS code: 586 // 587 // .class { 588 // color: #fff; 589 // border: 1px solid #000; 590 // width: @w + 4px; 591 // > .child {...} 592 // } 593 // 594 // And here's what the parse tree might look like: 595 // 596 // Ruleset (Selector '.class', [ 597 // Rule ("color", Value ([Expression [Color #fff]])) 598 // Rule ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]])) 599 // Rule ("width", Value ([Expression [Operation "+" [Variable "@w"][Dimension 4px]]])) 600 // Ruleset (Selector [Element '>', '.child'], [...]) 601 // ]) 602 // 603 // In general, most rules will try to parse a token with the `$()` function, and if the return 604 // value is truly, will return a new node, of the relevant type. Sometimes, we need to check 605 // first, before parsing, that's when we use `peek()`. 606 // 607 parsers: { 608 // 609 // The `primary` rule is the *entry* and *exit* point of the parser. 610 // The rules here can appear at any level of the parse tree. 611 // 612 // The recursive nature of the grammar is an interplay between the `block` 613 // rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule, 614 // as represented by this simplified grammar: 615 // 616 // primary → (ruleset | rule)+ 617 // ruleset → selector+ block 618 // block → '{' primary '}' 619 // 620 // Only at one point is the primary rule not called from the 621 // block rule: at the root level. 622 // 623 primary: function () { 624 var node, root = []; 625 626 while ((node = $(this.mixin.definition) || $(this.rule) || $(this.ruleset) || 627 $(this.mixin.call) || $(this.comment) || $(this.directive)) 628 || $(/^[\s\n]+/)) { 629 node && root.push(node); 630 } 631 return root; 632 }, 633 634 // We create a Comment node for CSS comments `/* */`, 635 // but keep the LeSS comments `//` silent, by just skipping 636 // over them. 637 comment: function () { 638 var comment; 639 640 if (input.charAt(i) !== '/') return; 641 642 if (input.charAt(i + 1) === '/') { 643 return new(tree.Comment)($(/^\/\/.*/), true); 644 } else if (comment = $(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/)) { 645 return new(tree.Comment)(comment); 646 } 647 }, 648 649 // 650 // Entities are tokens which can be found inside an Expression 651 // 652 entities: { 653 // 654 // A string, which supports escaping " and ' 655 // 656 // "milky way" 'he\'s the one!' 657 // 658 quoted: function () { 659 var str, j = i, e; 660 661 if (input.charAt(j) === '~') { j++, e = true } // Escaped strings 662 if (input.charAt(j) !== '"' && input.charAt(j) !== "'") return; 663 664 e && $('~'); 665 666 if (str = $(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/)) { 667 return new(tree.Quoted)(str[0], str[1] || str[2], e); 668 } 669 }, 670 671 // 672 // A catch-all word, such as: 673 // 674 // black border-collapse 675 // 676 keyword: function () { 677 var k; 678 679 if (k = $(/^[_A-Za-z-][_A-Za-z0-9-]*/)) { 680 if (tree.colors.hasOwnProperty(k)) { 681 // detect named color 682 return new(tree.Color)(tree.colors[k].slice(1)); 683 } else { 684 return new(tree.Keyword)(k); 685 } 686 } 687 }, 688 689 // 690 // A function call 691 // 692 // rgb(255, 0, 255) 693 // 694 // We also try to catch IE's `alpha()`, but let the `alpha` parser 695 // deal with the details. 696 // 697 // The arguments are parsed with the `entities.arguments` parser. 698 // 699 call: function () { 700 var name, args, alpha_ret, index = i; 701 702 if (! (name = /^([\w-]+|%|progid:[\w\.]+)\(/.exec(chunks[j]))) return; 703 704 name = name[1].toLowerCase(); 705 706 if (name === 'url') { return null } 707 else { i += name.length } 708 709 if (name === 'alpha') { 710 alpha_ret = $(this.alpha); 711 if(typeof alpha_ret !== 'undefined') { 712 return alpha_ret; 713 } 714 } 715 716 $('('); // Parse the '(' and consume whitespace. 717 718 args = $(this.entities.arguments); 719 720 if (! $(')')) return; 721 722 if (name) { return new(tree.Call)(name, args, index, env.filename) } 723 }, 724 arguments: function () { 725 var args = [], arg; 726 727 while (arg = $(this.entities.assignment) || $(this.expression)) { 728 args.push(arg); 729 if (! $(',')) { break } 730 } 731 return args; 732 }, 733 literal: function () { 734 return $(this.entities.dimension) || 735 $(this.entities.color) || 736 $(this.entities.quoted); 737 }, 738 739 // Assignments are argument entities for calls. 740 // They are present in ie filter properties as shown below. 741 // 742 // filter: progid:DXImageTransform.Microsoft.Alpha( *opacity=50* ) 743 // 744 745 assignment: function () { 746 var key, value; 747 if ((key = $(/^\w+(?=\s?=)/i)) && $('=') && (value = $(this.entity))) { 748 return new(tree.Assignment)(key, value); 749 } 750 }, 751 752 // 753 // Parse url() tokens 754 // 755 // We use a specific rule for urls, because they don't really behave like 756 // standard function calls. The difference is that the argument doesn't have 757 // to be enclosed within a string, so it can't be parsed as an Expression. 758 // 759 url: function () { 760 var value; 761 762 if (input.charAt(i) !== 'u' || !$(/^url\(/)) return; 763 value = $(this.entities.quoted) || $(this.entities.variable) || 764 $(this.entities.dataURI) || $(/^[-\w%@$\/.&=:;#+?~]+/) || ""; 765 766 expect(')'); 767 768 return new(tree.URL)((value.value || value.data || value instanceof tree.Variable) 769 ? value : new(tree.Anonymous)(value), imports.paths); 770 }, 771 772 dataURI: function () { 773 var obj; 774 775 if ($(/^data:/)) { 776 obj = {}; 777 obj.mime = $(/^[^\/]+\/[^,;)]+/) || ''; 778 obj.charset = $(/^;\s*charset=[^,;)]+/) || ''; 779 obj.base64 = $(/^;\s*base64/) || ''; 780 obj.data = $(/^,\s*[^)]+/); 781 782 if (obj.data) { return obj } 783 } 784 }, 785 786 // 787 // A Variable entity, such as `@fink`, in 788 // 789 // width: @fink + 2px 790 // 791 // We use a different parser for variable definitions, 792 // see `parsers.variable`. 793 // 794 variable: function () { 795 var name, index = i; 796 797 if (input.charAt(i) === '@' && (name = $(/^@@?[\w-]+/))) { 798 return new(tree.Variable)(name, index, env.filename); 799 } 800 }, 801 802 // 803 // A Hexadecimal color 804 // 805 // #4F3C2F 806 // 807 // `rgb` and `hsl` colors are parsed through the `entities.call` parser. 808 // 809 color: function () { 810 var rgb; 811 812 if (input.charAt(i) === '#' && (rgb = $(/^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})/))) { 813 return new(tree.Color)(rgb[1]); 814 } 815 }, 816 817 // 818 // A Dimension, that is, a number and a unit 819 // 820 // 0.5em 95% 821 // 822 dimension: function () { 823 var value, c = input.charCodeAt(i); 824 if ((c > 57 || c < 45) || c === 47) return; 825 826 if (value = $(/^(-?\d*\.?\d+)(px|%|em|rem|pc|ex|in|deg|s|ms|pt|cm|mm|rad|grad|turn)?/)) { 827 return new(tree.Dimension)(value[1], value[2]); 828 } 829 }, 830 831 // 832 // JavaScript code to be evaluated 833 // 834 // `window.location.href` 835 // 836 javascript: function () { 837 var str, j = i, e; 838 839 if (input.charAt(j) === '~') { j++, e = true } // Escaped strings 840 if (input.charAt(j) !== '`') { return } 841 842 e && $('~'); 843 844 if (str = $(/^`([^`]*)`/)) { 845 return new(tree.JavaScript)(str[1], i, e); 846 } 847 } 848 }, 849 850 // 851 // The variable part of a variable definition. Used in the `rule` parser 852 // 853 // @fink: 854 // 855 variable: function () { 856 var name; 857 858 if (input.charAt(i) === '@' && (name = $(/^(@[\w-]+)\s*:/))) { return name[1] } 859 }, 860 861 // 862 // A font size/line-height shorthand 863 // 864 // small/12px 865 // 866 // We need to peek first, or we'll match on keywords and dimensions 867 // 868 shorthand: function () { 869 var a, b; 870 871 if (! peek(/^[@\w.%-]+\/[@\w.-]+/)) return; 872 873 if ((a = $(this.entity)) && $('/') && (b = $(this.entity))) { 874 return new(tree.Shorthand)(a, b); 875 } 876 }, 877 878 // 879 // Mixins 880 // 881 mixin: { 882 // 883 // A Mixin call, with an optional argument list 884 // 885 // #mixins > .square(#fff); 886 // .rounded(4px, black); 887 // .button; 888 // 889 // The `while` loop is there because mixins can be 890 // namespaced, but we only support the child and descendant 891 // selector for now. 892 // 893 call: function () { 894 var elements = [], e, c, args = [], arg, index = i, s = input.charAt(i), name, value, important = false; 895 896 if (s !== '.' && s !== '#') { return } 897 898 while (e = $(/^[#.](?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+/)) { 899 elements.push(new(tree.Element)(c, e, i)); 900 c = $('>'); 901 } 902 if ($('(')) { 903 while (arg = $(this.expression)) { 904 value = arg; 905 name = null; 906 907 // Variable 908 if (arg.value.length = 1) { 909 var val = arg.value[0]; 910 if (val instanceof tree.Variable) { 911 if ($(':')) { 912 if (value = $(this.expression)) { 913 name = val.name; 914 } else { 915 throw new(Error)("Expected value"); 916 } 917 } 918 } 919 } 920 921 args.push({ name: name, value: value }); 922 923 if (! $(',')) { break } 924 } 925 if (! $(')')) throw new(Error)("Expected )"); 926 } 927 928 if ($(this.important)) { 929 important = true; 930 } 931 932 if (elements.length > 0 && ($(';') || peek('}'))) { 933 return new(tree.mixin.Call)(elements, args, index, env.filename, important); 934 } 935 }, 936 937 // 938 // A Mixin definition, with a list of parameters 939 // 940 // .rounded (@radius: 2px, @color) { 941 // ... 942 // } 943 // 944 // Until we have a finer grained state-machine, we have to 945 // do a look-ahead, to make sure we don't have a mixin call. 946 // See the `rule` function for more information. 947 // 948 // We start by matching `.rounded (`, and then proceed on to 949 // the argument list, which has optional default values. 950 // We store the parameters in `params`, with a `value` key, 951 // if there is a value, such as in the case of `@radius`. 952 // 953 // Once we've got our params list, and a closing `)`, we parse 954 // the `{...}` block. 955 // 956 definition: function () { 957 var name, params = [], match, ruleset, param, value, cond, variadic = false; 958 if ((input.charAt(i) !== '.' && input.charAt(i) !== '#') || 959 peek(/^[^{]*(;|})/)) return; 960 961 save(); 962 963 if (match = $(/^([#.](?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+)\s*\(/)) { 964 name = match[1]; 965 966 do { 967 if (input.charAt(i) === '.' && $(/^\.{3}/)) { 968 variadic = true; 969 break; 970 } else if (param = $(this.entities.variable) || $(this.entities.literal) 971 || $(this.entities.keyword)) { 972 // Variable 973 if (param instanceof tree.Variable) { 974 if ($(':')) { 975 value = expect(this.expression, 'expected expression'); 976 params.push({ name: param.name, value: value }); 977 } else if ($(/^\.{3}/)) { 978 params.push({ name: param.name, variadic: true }); 979 variadic = true; 980 break; 981 } else { 982 params.push({ name: param.name }); 983 } 984 } else { 985 params.push({ value: param }); 986 } 987 } else { 988 break; 989 } 990 } while ($(',')) 991 992 expect(')'); 993 994 if ($(/^when/)) { // Guard 995 cond = expect(this.conditions, 'expected condition'); 996 } 997 998 ruleset = $(this.block); 999 1000 if (ruleset) { 1001 return new(tree.mixin.Definition)(name, params, ruleset, cond, variadic); 1002 } else { 1003 restore(); 1004 } 1005 } 1006 } 1007 }, 1008 1009 // 1010 // Entities are the smallest recognized token, 1011 // and can be found inside a rule's value. 1012 // 1013 entity: function () { 1014 return $(this.entities.literal) || $(this.entities.variable) || $(this.entities.url) || 1015 $(this.entities.call) || $(this.entities.keyword) || $(this.entities.javascript) || 1016 $(this.comment); 1017 }, 1018 1019 // 1020 // A Rule terminator. Note that we use `peek()` to check for '}', 1021 // because the `block` rule will be expecting it, but we still need to make sure 1022 // it's there, if ';' was ommitted. 1023 // 1024 end: function () { 1025 return $(';') || peek('}'); 1026 }, 1027 1028 // 1029 // IE's alpha function 1030 // 1031 // alpha(opacity=88) 1032 // 1033 alpha: function () { 1034 var value; 1035 1036 if (! $(/^\(opacity=/i)) return; 1037 if (value = $(/^\d+/) || $(this.entities.variable)) { 1038 expect(')'); 1039 return new(tree.Alpha)(value); 1040 } 1041 }, 1042 1043 // 1044 // A Selector Element 1045 // 1046 // div 1047 // + h1 1048 // #socks 1049 // input[type="text"] 1050 // 1051 // Elements are the building blocks for Selectors, 1052 // they are made out of a `Combinator` (see combinator rule), 1053 // and an element name, such as a tag a class, or `*`. 1054 // 1055 element: function () { 1056 var e, t, c, v; 1057 1058 c = $(this.combinator); 1059 e = $(/^(?:\d+\.\d+|\d+)%/) || $(/^(?:[.#]?|:*)(?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+/) || 1060 $('*') || $(this.attribute) || $(/^\([^)@]+\)/); 1061 1062 if (! e) { 1063 $('(') && (v = $(this.entities.variable)) && $(')') && (e = new(tree.Paren)(v)); 1064 } 1065 1066 if (e) { return new(tree.Element)(c, e, i) } 1067 1068 if (c.value && c.value.charAt(0) === '&') { 1069 return new(tree.Element)(c, null, i); 1070 } 1071 }, 1072 1073 // 1074 // Combinators combine elements together, in a Selector. 1075 // 1076 // Because our parser isn't white-space sensitive, special care 1077 // has to be taken, when parsing the descendant combinator, ` `, 1078 // as it's an empty space. We have to check the previous character 1079 // in the input, to see if it's a ` ` character. More info on how 1080 // we deal with this in *combinator.js*. 1081 // 1082 combinator: function () { 1083 var match, c = input.charAt(i); 1084 1085 if (c === '>' || c === '+' || c === '~') { 1086 i++; 1087 while (input.charAt(i) === ' ') { i++ } 1088 return new(tree.Combinator)(c); 1089 } else if (c === '&') { 1090 match = '&'; 1091 i++; 1092 if(input.charAt(i) === ' ') { 1093 match = '& '; 1094 } 1095 while (input.charAt(i) === ' ') { i++ } 1096 return new(tree.Combinator)(match); 1097 } else if (input.charAt(i - 1) === ' ') { 1098 return new(tree.Combinator)(" "); 1099 } else { 1100 return new(tree.Combinator)(null); 1101 } 1102 }, 1103 1104 // 1105 // A CSS Selector 1106 // 1107 // .class > div + h1 1108 // li a:hover 1109 // 1110 // Selectors are made out of one or more Elements, see above. 1111 // 1112 selector: function () { 1113 var sel, e, elements = [], c, match; 1114 1115 if ($('(')) { 1116 sel = $(this.entity); 1117 expect(')'); 1118 return new(tree.Selector)([new(tree.Element)('', sel, i)]); 1119 } 1120 1121 while (e = $(this.element)) { 1122 c = input.charAt(i); 1123 elements.push(e) 1124 if (c === '{' || c === '}' || c === ';' || c === ',') { break } 1125 } 1126 1127 if (elements.length > 0) { return new(tree.Selector)(elements) } 1128 }, 1129 tag: function () { 1130 return $(/^[a-zA-Z][a-zA-Z-]*[0-9]?/) || $('*'); 1131 }, 1132 attribute: function () { 1133 var attr = '', key, val, op; 1134 1135 if (! $('[')) return; 1136 1137 if (key = $(/^[a-zA-Z-]+/) || $(this.entities.quoted)) { 1138 if ((op = $(/^[|~*$^]?=/)) && 1139 (val = $(this.entities.quoted) || $(/^[\w-]+/))) { 1140 attr = [key, op, val.toCSS ? val.toCSS() : val].join(''); 1141 } else { attr = key } 1142 } 1143 1144 if (! $(']')) return; 1145 1146 if (attr) { return "[" + attr + "]" } 1147 }, 1148 1149 // 1150 // The `block` rule is used by `ruleset` and `mixin.definition`. 1151 // It's a wrapper around the `primary` rule, with added `{}`. 1152 // 1153 block: function () { 1154 var content; 1155 1156 if ($('{') && (content = $(this.primary)) && $('}')) { 1157 return content; 1158 } 1159 }, 1160 1161 // 1162 // div, .class, body > p {...} 1163 // 1164 ruleset: function () { 1165 var selectors = [], s, rules, match; 1166 save(); 1167 1168 while (s = $(this.selector)) { 1169 selectors.push(s); 1170 $(this.comment); 1171 if (! $(',')) { break } 1172 $(this.comment); 1173 } 1174 1175 if (selectors.length > 0 && (rules = $(this.block))) { 1176 return new(tree.Ruleset)(selectors, rules, env.strictImports); 1177 } else { 1178 // Backtrack 1179 furthest = i; 1180 restore(); 1181 } 1182 }, 1183 rule: function () { 1184 var name, value, c = input.charAt(i), important, match; 1185 save(); 1186 1187 if (c === '.' || c === '#' || c === '&') { return } 1188 1189 if (name = $(this.variable) || $(this.property)) { 1190 if ((name.charAt(0) != '@') && (match = /^([^@+\/'"*`(;{}-]*);/.exec(chunks[j]))) { 1191 i += match[0].length - 1; 1192 value = new(tree.Anonymous)(match[1]); 1193 } else if (name === "font") { 1194 value = $(this.font); 1195 } else { 1196 value = $(this.value); 1197 } 1198 important = $(this.important); 1199 1200 if (value && $(this.end)) { 1201 return new(tree.Rule)(name, value, important, memo); 1202 } else { 1203 furthest = i; 1204 restore(); 1205 } 1206 } 1207 }, 1208 1209 // 1210 // An @import directive 1211 // 1212 // @import "lib"; 1213 // 1214 // Depending on our environemnt, importing is done differently: 1215 // In the browser, it's an XHR request, in Node, it would be a 1216 // file-system operation. The function used for importing is 1217 // stored in `import`, which we pass to the Import constructor. 1218 // 1219 "import": function () { 1220 var path, features, index = i; 1221 var dir = $(/^@import(?:-(once))?\s+/); 1222 1223 if (dir && (path = $(this.entities.quoted) || $(this.entities.url))) { 1224 features = $(this.mediaFeatures); 1225 if ($(';')) { 1226 return new(tree.Import)(path, imports, features, (dir[1] === 'once'), index); 1227 } 1228 } 1229 }, 1230 1231 mediaFeature: function () { 1232 var e, p, nodes = []; 1233 1234 do { 1235 if (e = $(this.entities.keyword)) { 1236 nodes.push(e); 1237 } else if ($('(')) { 1238 p = $(this.property); 1239 e = $(this.entity); 1240 if ($(')')) { 1241 if (p && e) { 1242 nodes.push(new(tree.Paren)(new(tree.Rule)(p, e, null, i, true))); 1243 } else if (e) { 1244 nodes.push(new(tree.Paren)(e)); 1245 } else { 1246 return null; 1247 } 1248 } else { return null } 1249 } 1250 } while (e); 1251 1252 if (nodes.length > 0) { 1253 return new(tree.Expression)(nodes); 1254 } 1255 }, 1256 1257 mediaFeatures: function () { 1258 var e, features = []; 1259 1260 do { 1261 if (e = $(this.mediaFeature)) { 1262 features.push(e); 1263 if (! $(',')) { break } 1264 } else if (e = $(this.entities.variable)) { 1265 features.push(e); 1266 if (! $(',')) { break } 1267 } 1268 } while (e); 1269 1270 return features.length > 0 ? features : null; 1271 }, 1272 1273 media: function () { 1274 var features, rules; 1275 1276 if ($(/^@media/)) { 1277 features = $(this.mediaFeatures); 1278 1279 if (rules = $(this.block)) { 1280 return new(tree.Media)(rules, features); 1281 } 1282 } 1283 }, 1284 1285 // 1286 // A CSS Directive 1287 // 1288 // @charset "utf-8"; 1289 // 1290 directive: function () { 1291 var name, value, rules, types, e, nodes; 1292 1293 if (input.charAt(i) !== '@') return; 1294 1295 if (value = $(this['import']) || $(this.media)) { 1296 return value; 1297 } else if (name = $(/^@page|@keyframes/) || $(/^@(?:-webkit-|-moz-|-o-|-ms-)[a-z0-9-]+/)) { 1298 types = ($(/^[^{]+/) || '').trim(); 1299 if (rules = $(this.block)) { 1300 return new(tree.Directive)(name + " " + types, rules); 1301 } 1302 } else if (name = $(/^@[-a-z]+/)) { 1303 if (name === '@font-face') { 1304 if (rules = $(this.block)) { 1305 return new(tree.Directive)(name, rules); 1306 } 1307 } else if ((value = $(this.entity)) && $(';')) { 1308 return new(tree.Directive)(name, value); 1309 } 1310 } 1311 }, 1312 font: function () { 1313 var value = [], expression = [], weight, shorthand, font, e; 1314 1315 while (e = $(this.shorthand) || $(this.entity)) { 1316 expression.push(e); 1317 } 1318 value.push(new(tree.Expression)(expression)); 1319 1320 if ($(',')) { 1321 while (e = $(this.expression)) { 1322 value.push(e); 1323 if (! $(',')) { break } 1324 } 1325 } 1326 return new(tree.Value)(value); 1327 }, 1328 1329 // 1330 // A Value is a comma-delimited list of Expressions 1331 // 1332 // font-family: Baskerville, Georgia, serif; 1333 // 1334 // In a Rule, a Value represents everything after the `:`, 1335 // and before the `;`. 1336 // 1337 value: function () { 1338 var e, expressions = [], important; 1339 1340 while (e = $(this.expression)) { 1341 expressions.push(e); 1342 if (! $(',')) { break } 1343 } 1344 1345 if (expressions.length > 0) { 1346 return new(tree.Value)(expressions); 1347 } 1348 }, 1349 important: function () { 1350 if (input.charAt(i) === '!') { 1351 return $(/^! *important/); 1352 } 1353 }, 1354 sub: function () { 1355 var e; 1356 1357 if ($('(') && (e = $(this.expression)) && $(')')) { 1358 return e; 1359 } 1360 }, 1361 multiplication: function () { 1362 var m, a, op, operation; 1363 if (m = $(this.operand)) { 1364 while (!peek(/^\/\*/) && (op = ($('/') || $('*'))) && (a = $(this.operand))) { 1365 operation = new(tree.Operation)(op, [operation || m, a]); 1366 } 1367 return operation || m; 1368 } 1369 }, 1370 addition: function () { 1371 var m, a, op, operation; 1372 if (m = $(this.multiplication)) { 1373 while ((op = $(/^[-+]\s+/) || (input.charAt(i - 1) != ' ' && ($('+') || $('-')))) && 1374 (a = $(this.multiplication))) { 1375 operation = new(tree.Operation)(op, [operation || m, a]); 1376 } 1377 return operation || m; 1378 } 1379 }, 1380 conditions: function () { 1381 var a, b, index = i, condition; 1382 1383 if (a = $(this.condition)) { 1384 while ($(',') && (b = $(this.condition))) { 1385 condition = new(tree.Condition)('or', condition || a, b, index); 1386 } 1387 return condition || a; 1388 } 1389 }, 1390 condition: function () { 1391 var a, b, c, op, index = i, negate = false; 1392 1393 if ($(/^not/)) { negate = true } 1394 expect('('); 1395 if (a = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) { 1396 if (op = $(/^(?:>=|=<|[<=>])/)) { 1397 if (b = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) { 1398 c = new(tree.Condition)(op, a, b, index, negate); 1399 } else { 1400 error('expected expression'); 1401 } 1402 } else { 1403 c = new(tree.Condition)('=', a, new(tree.Keyword)('true'), index, negate); 1404 } 1405 expect(')'); 1406 return $(/^and/) ? new(tree.Condition)('and', c, $(this.condition)) : c; 1407 } 1408 }, 1409 1410 // 1411 // An operand is anything that can be part of an operation, 1412 // such as a Color, or a Variable 1413 // 1414 operand: function () { 1415 var negate, p = input.charAt(i + 1); 1416 1417 if (input.charAt(i) === '-' && (p === '@' || p === '(')) { negate = $('-') } 1418 var o = $(this.sub) || $(this.entities.dimension) || 1419 $(this.entities.color) || $(this.entities.variable) || 1420 $(this.entities.call); 1421 return negate ? new(tree.Operation)('*', [new(tree.Dimension)(-1), o]) 1422 : o; 1423 }, 1424 1425 // 1426 // Expressions either represent mathematical operations, 1427 // or white-space delimited Entities. 1428 // 1429 // 1px solid black 1430 // @var * 2 1431 // 1432 expression: function () { 1433 var e, delim, entities = [], d; 1434 1435 while (e = $(this.addition) || $(this.entity)) { 1436 entities.push(e); 1437 } 1438 if (entities.length > 0) { 1439 return new(tree.Expression)(entities); 1440 } 1441 }, 1442 property: function () { 1443 var name; 1444 1445 if (name = $(/^(\*?-?[-a-z_0-9]+)\s*:/)) { 1446 return name[1]; 1447 } 1448 } 1449 } 1450 }; 1451 }; 1452 1453 if (less.mode === 'browser' || less.mode === 'rhino') { 1454 // 1455 // Used by `@import` directives 1456 // 1457 less.Parser.importer = function (path, paths, callback, env) { 1458 if (!/^([a-z]+:)?\//.test(path) && paths.length > 0) { 1459 path = paths[0] + path; 1460 } 1461 // We pass `true` as 3rd argument, to force the reload of the import. 1462 // This is so we can get the syntax tree as opposed to just the CSS output, 1463 // as we need this to evaluate the current stylesheet. 1464 loadStyleSheet({ href: path, title: path, type: env.mime }, function (e) { 1465 if (e && typeof(env.errback) === "function") { 1466 env.errback.call(null, path, paths, callback, env); 1467 } else { 1468 callback.apply(null, arguments); 1469 } 1470 }, true); 1471 }; 1472 } 1473 1474 (function (tree) { 1475 1476 tree.functions = { 1477 rgb: function (r, g, b) { 1478 return this.rgba(r, g, b, 1.0); 1479 }, 1480 rgba: function (r, g, b, a) { 1481 var rgb = [r, g, b].map(function (c) { return number(c) }), 1482 a = number(a); 1483 return new(tree.Color)(rgb, a); 1484 }, 1485 hsl: function (h, s, l) { 1486 return this.hsla(h, s, l, 1.0); 1487 }, 1488 hsla: function (h, s, l, a) { 1489 h = (number(h) % 360) / 360; 1490 s = number(s); l = number(l); a = number(a); 1491 1492 var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s; 1493 var m1 = l * 2 - m2; 1494 1495 return this.rgba(hue(h + 1/3) * 255, 1496 hue(h) * 255, 1497 hue(h - 1/3) * 255, 1498 a); 1499 1500 function hue(h) { 1501 h = h < 0 ? h + 1 : (h > 1 ? h - 1 : h); 1502 if (h * 6 < 1) return m1 + (m2 - m1) * h * 6; 1503 else if (h * 2 < 1) return m2; 1504 else if (h * 3 < 2) return m1 + (m2 - m1) * (2/3 - h) * 6; 1505 else return m1; 1506 } 1507 }, 1508 hue: function (color) { 1509 return new(tree.Dimension)(Math.round(color.toHSL().h)); 1510 }, 1511 saturation: function (color) { 1512 return new(tree.Dimension)(Math.round(color.toHSL().s * 100), '%'); 1513 }, 1514 lightness: function (color) { 1515 return new(tree.Dimension)(Math.round(color.toHSL().l * 100), '%'); 1516 }, 1517 alpha: function (color) { 1518 return new(tree.Dimension)(color.toHSL().a); 1519 }, 1520 saturate: function (color, amount) { 1521 var hsl = color.toHSL(); 1522 1523 hsl.s += amount.value / 100; 1524 hsl.s = clamp(hsl.s); 1525 return hsla(hsl); 1526 }, 1527 desaturate: function (color, amount) { 1528 var hsl = color.toHSL(); 1529 1530 hsl.s -= amount.value / 100; 1531 hsl.s = clamp(hsl.s); 1532 return hsla(hsl); 1533 }, 1534 lighten: function (color, amount) { 1535 var hsl = color.toHSL(); 1536 1537 hsl.l += amount.value / 100; 1538 hsl.l = clamp(hsl.l); 1539 return hsla(hsl); 1540 }, 1541 darken: function (color, amount) { 1542 var hsl = color.toHSL(); 1543 1544 hsl.l -= amount.value / 100; 1545 hsl.l = clamp(hsl.l); 1546 return hsla(hsl); 1547 }, 1548 fadein: function (color, amount) { 1549 var hsl = color.toHSL(); 1550 1551 hsl.a += amount.value / 100; 1552 hsl.a = clamp(hsl.a); 1553 return hsla(hsl); 1554 }, 1555 fadeout: function (color, amount) { 1556 var hsl = color.toHSL(); 1557 1558 hsl.a -= amount.value / 100; 1559 hsl.a = clamp(hsl.a); 1560 return hsla(hsl); 1561 }, 1562 fade: function (color, amount) { 1563 var hsl = color.toHSL(); 1564 1565 hsl.a = amount.value / 100; 1566 hsl.a = clamp(hsl.a); 1567 return hsla(hsl); 1568 }, 1569 spin: function (color, amount) { 1570 var hsl = color.toHSL(); 1571 var hue = (hsl.h + amount.value) % 360; 1572 1573 hsl.h = hue < 0 ? 360 + hue : hue; 1574 1575 return hsla(hsl); 1576 }, 1577 // 1578 // Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein 1579 // http://sass-lang.com 1580 // 1581 mix: function (color1, color2, weight) { 1582 var p = weight.value / 100.0; 1583 var w = p * 2 - 1; 1584 var a = color1.toHSL().a - color2.toHSL().a; 1585 1586 var w1 = (((w * a == -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; 1587 var w2 = 1 - w1; 1588 1589 var rgb = [color1.rgb[0] * w1 + color2.rgb[0] * w2, 1590 color1.rgb[1] * w1 + color2.rgb[1] * w2, 1591 color1.rgb[2] * w1 + color2.rgb[2] * w2]; 1592 1593 var alpha = color1.alpha * p + color2.alpha * (1 - p); 1594 1595 return new(tree.Color)(rgb, alpha); 1596 }, 1597 greyscale: function (color) { 1598 return this.desaturate(color, new(tree.Dimension)(100)); 1599 }, 1600 e: function (str) { 1601 return new(tree.Anonymous)(str instanceof tree.JavaScript ? str.evaluated : str); 1602 }, 1603 escape: function (str) { 1604 return new(tree.Anonymous)(encodeURI(str.value).replace(/=/g, "%3D").replace(/:/g, "%3A").replace(/#/g, "%23").replace(/;/g, "%3B").replace(/\(/g, "%28").replace(/\)/g, "%29")); 1605 }, 1606 '%': function (quoted /* arg, arg, ...*/) { 1607 var args = Array.prototype.slice.call(arguments, 1), 1608 str = quoted.value; 1609 1610 for (var i = 0; i < args.length; i++) { 1611 str = str.replace(/%[sda]/i, function(token) { 1612 var value = token.match(/s/i) ? args[i].value : args[i].toCSS(); 1613 return token.match(/[A-Z]$/) ? encodeURIComponent(value) : value; 1614 }); 1615 } 1616 str = str.replace(/%%/g, '%'); 1617 return new(tree.Quoted)('"' + str + '"', str); 1618 }, 1619 round: function (n) { 1620 return this._math('round', n); 1621 }, 1622 ceil: function (n) { 1623 return this._math('ceil', n); 1624 }, 1625 floor: function (n) { 1626 return this._math('floor', n); 1627 }, 1628 _math: function (fn, n) { 1629 if (n instanceof tree.Dimension) { 1630 return new(tree.Dimension)(Math[fn](number(n)), n.unit); 1631 } else if (typeof(n) === 'number') { 1632 return Math[fn](n); 1633 } else { 1634 throw { type: "Argument", message: "argument must be a number" }; 1635 } 1636 }, 1637 argb: function (color) { 1638 return new(tree.Anonymous)(color.toARGB()); 1639 1640 }, 1641 percentage: function (n) { 1642 return new(tree.Dimension)(n.value * 100, '%'); 1643 }, 1644 color: function (n) { 1645 if (n instanceof tree.Quoted) { 1646 return new(tree.Color)(n.value.slice(1)); 1647 } else { 1648 throw { type: "Argument", message: "argument must be a string" }; 1649 } 1650 }, 1651 iscolor: function (n) { 1652 return this._isa(n, tree.Color); 1653 }, 1654 isnumber: function (n) { 1655 return this._isa(n, tree.Dimension); 1656 }, 1657 isstring: function (n) { 1658 return this._isa(n, tree.Quoted); 1659 }, 1660 iskeyword: function (n) { 1661 return this._isa(n, tree.Keyword); 1662 }, 1663 isurl: function (n) { 1664 return this._isa(n, tree.URL); 1665 }, 1666 ispixel: function (n) { 1667 return (n instanceof tree.Dimension) && n.unit === 'px' ? tree.True : tree.False; 1668 }, 1669 ispercentage: function (n) { 1670 return (n instanceof tree.Dimension) && n.unit === '%' ? tree.True : tree.False; 1671 }, 1672 isem: function (n) { 1673 return (n instanceof tree.Dimension) && n.unit === 'em' ? tree.True : tree.False; 1674 }, 1675 _isa: function (n, Type) { 1676 return (n instanceof Type) ? tree.True : tree.False; 1677 } 1678 }; 1679 1680 function hsla(hsla) { 1681 return tree.functions.hsla(hsla.h, hsla.s, hsla.l, hsla.a); 1682 } 1683 1684 function number(n) { 1685 if (n instanceof tree.Dimension) { 1686 return parseFloat(n.unit == '%' ? n.value / 100 : n.value); 1687 } else if (typeof(n) === 'number') { 1688 return n; 1689 } else { 1690 throw { 1691 error: "RuntimeError", 1692 message: "color functions take numbers as parameters" 1693 }; 1694 } 1695 } 1696 1697 function clamp(val) { 1698 return Math.min(1, Math.max(0, val)); 1699 } 1700 1701 })(require('./tree')); 1702 (function (tree) { 1703 1704 tree.Alpha = function (val) { 1705 this.value = val; 1706 }; 1707 tree.Alpha.prototype = { 1708 toCSS: function () { 1709 return "alpha(opacity=" + 1710 (this.value.toCSS ? this.value.toCSS() : this.value) + ")"; 1711 }, 1712 eval: function (env) { 1713 if (this.value.eval) { this.value = this.value.eval(env) } 1714 return this; 1715 } 1716 }; 1717 1718 })(require('../tree')); 1719 (function (tree) { 1720 1721 tree.Anonymous = function (string) { 1722 this.value = string.value || string; 1723 }; 1724 tree.Anonymous.prototype = { 1725 toCSS: function () { 1726 return this.value; 1727 }, 1728 eval: function () { return this } 1729 }; 1730 1731 })(require('../tree')); 1732 (function (tree) { 1733 1734 tree.Assignment = function (key, val) { 1735 this.key = key; 1736 this.value = val; 1737 }; 1738 tree.Assignment.prototype = { 1739 toCSS: function () { 1740 return this.key + '=' + (this.value.toCSS ? this.value.toCSS() : this.value); 1741 }, 1742 eval: function (env) { 1743 if (this.value.eval) { this.value = this.value.eval(env) } 1744 return this; 1745 } 1746 }; 1747 1748 })(require('../tree'));(function (tree) { 1749 1750 // 1751 // A function call node. 1752 // 1753 tree.Call = function (name, args, index, filename) { 1754 this.name = name; 1755 this.args = args; 1756 this.index = index; 1757 this.filename = filename; 1758 }; 1759 tree.Call.prototype = { 1760 // 1761 // When evaluating a function call, 1762 // we either find the function in `tree.functions` [1], 1763 // in which case we call it, passing the evaluated arguments, 1764 // or we simply print it out as it appeared originally [2]. 1765 // 1766 // The *functions.js* file contains the built-in functions. 1767 // 1768 // The reason why we evaluate the arguments, is in the case where 1769 // we try to pass a variable to a function, like: `saturate(@color)`. 1770 // The function should receive the value, not the variable. 1771 // 1772 eval: function (env) { 1773 var args = this.args.map(function (a) { return a.eval(env) }); 1774 1775 if (this.name in tree.functions) { // 1. 1776 try { 1777 return tree.functions[this.name].apply(tree.functions, args); 1778 } catch (e) { 1779 throw { type: e.type || "Runtime", 1780 message: "error evaluating function `" + this.name + "`" + 1781 (e.message ? ': ' + e.message : ''), 1782 index: this.index, filename: this.filename }; 1783 } 1784 } else { // 2. 1785 return new(tree.Anonymous)(this.name + 1786 "(" + args.map(function (a) { return a.toCSS() }).join(', ') + ")"); 1787 } 1788 }, 1789 1790 toCSS: function (env) { 1791 return this.eval(env).toCSS(); 1792 } 1793 }; 1794 1795 })(require('../tree')); 1796 (function (tree) { 1797 // 1798 // RGB Colors - #ff0014, #eee 1799 // 1800 tree.Color = function (rgb, a) { 1801 // 1802 // The end goal here, is to parse the arguments 1803 // into an integer triplet, such as `128, 255, 0` 1804 // 1805 // This facilitates operations and conversions. 1806 // 1807 if (Array.isArray(rgb)) { 1808 this.rgb = rgb; 1809 } else if (rgb.length == 6) { 1810 this.rgb = rgb.match(/.{2}/g).map(function (c) { 1811 return parseInt(c, 16); 1812 }); 1813 } else { 1814 this.rgb = rgb.split('').map(function (c) { 1815 return parseInt(c + c, 16); 1816 }); 1817 } 1818 this.alpha = typeof(a) === 'number' ? a : 1; 1819 }; 1820 tree.Color.prototype = { 1821 eval: function () { return this }, 1822 1823 // 1824 // If we have some transparency, the only way to represent it 1825 // is via `rgba`. Otherwise, we use the hex representation, 1826 // which has better compatibility with older browsers. 1827 // Values are capped between `0` and `255`, rounded and zero-padded. 1828 // 1829 toCSS: function () { 1830 if (this.alpha < 1.0) { 1831 return "rgba(" + this.rgb.map(function (c) { 1832 return Math.round(c); 1833 }).concat(this.alpha).join(', ') + ")"; 1834 } else { 1835 return '#' + this.rgb.map(function (i) { 1836 i = Math.round(i); 1837 i = (i > 255 ? 255 : (i < 0 ? 0 : i)).toString(16); 1838 return i.length === 1 ? '0' + i : i; 1839 }).join(''); 1840 } 1841 }, 1842 1843 // 1844 // Operations have to be done per-channel, if not, 1845 // channels will spill onto each other. Once we have 1846 // our result, in the form of an integer triplet, 1847 // we create a new Color node to hold the result. 1848 // 1849 operate: function (op, other) { 1850 var result = []; 1851 1852 if (! (other instanceof tree.Color)) { 1853 other = other.toColor(); 1854 } 1855 1856 for (var c = 0; c < 3; c++) { 1857 result[c] = tree.operate(op, this.rgb[c], other.rgb[c]); 1858 } 1859 return new(tree.Color)(result, this.alpha + other.alpha); 1860 }, 1861 1862 toHSL: function () { 1863 var r = this.rgb[0] / 255, 1864 g = this.rgb[1] / 255, 1865 b = this.rgb[2] / 255, 1866 a = this.alpha; 1867 1868 var max = Math.max(r, g, b), min = Math.min(r, g, b); 1869 var h, s, l = (max + min) / 2, d = max - min; 1870 1871 if (max === min) { 1872 h = s = 0; 1873 } else { 1874 s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 1875 1876 switch (max) { 1877 case r: h = (g - b) / d + (g < b ? 6 : 0); break; 1878 case g: h = (b - r) / d + 2; break; 1879 case b: h = (r - g) / d + 4; break; 1880 } 1881 h /= 6; 1882 } 1883 return { h: h * 360, s: s, l: l, a: a }; 1884 }, 1885 toARGB: function () { 1886 var argb = [Math.round(this.alpha * 255)].concat(this.rgb); 1887 return '#' + argb.map(function (i) { 1888 i = Math.round(i); 1889 i = (i > 255 ? 255 : (i < 0 ? 0 : i)).toString(16); 1890 return i.length === 1 ? '0' + i : i; 1891 }).join(''); 1892 } 1893 }; 1894 1895 1896 })(require('../tree')); 1897 (function (tree) { 1898 1899 tree.Comment = function (value, silent) { 1900 this.value = value; 1901 this.silent = !!silent; 1902 }; 1903 tree.Comment.prototype = { 1904 toCSS: function (env) { 1905 return env.compress ? '' : this.value; 1906 }, 1907 eval: function () { return this } 1908 }; 1909 1910 })(require('../tree')); 1911 (function (tree) { 1912 1913 tree.Condition = function (op, l, r, i, negate) { 1914 this.op = op.trim(); 1915 this.lvalue = l; 1916 this.rvalue = r; 1917 this.index = i; 1918 this.negate = negate; 1919 }; 1920 tree.Condition.prototype.eval = function (env) { 1921 var a = this.lvalue.eval(env), 1922 b = this.rvalue.eval(env); 1923 1924 var i = this.index, result; 1925 1926 var result = (function (op) { 1927 switch (op) { 1928 case 'and': 1929 return a && b; 1930 case 'or': 1931 return a || b; 1932 default: 1933 if (a.compare) { 1934 result = a.compare(b); 1935 } else if (b.compare) { 1936 result = b.compare(a); 1937 } else { 1938 throw { type: "Type", 1939 message: "Unable to perform comparison", 1940 index: i }; 1941 } 1942 switch (result) { 1943 case -1: return op === '<' || op === '=<'; 1944 case 0: return op === '=' || op === '>=' || op === '=<'; 1945 case 1: return op === '>' || op === '>='; 1946 } 1947 } 1948 })(this.op); 1949 return this.negate ? !result : result; 1950 }; 1951 1952 })(require('../tree')); 1953 (function (tree) { 1954 1955 // 1956 // A number with a unit 1957 // 1958 tree.Dimension = function (value, unit) { 1959 this.value = parseFloat(value); 1960 this.unit = unit || null; 1961 }; 1962 1963 tree.Dimension.prototype = { 1964 eval: function () { return this }, 1965 toColor: function () { 1966 return new(tree.Color)([this.value, this.value, this.value]); 1967 }, 1968 toCSS: function () { 1969 var css = this.value + this.unit; 1970 return css; 1971 }, 1972 1973 // In an operation between two Dimensions, 1974 // we default to the first Dimension's unit, 1975 // so `1px + 2em` will yield `3px`. 1976 // In the future, we could implement some unit 1977 // conversions such that `100cm + 10mm` would yield 1978 // `101cm`. 1979 operate: function (op, other) { 1980 return new(tree.Dimension) 1981 (tree.operate(op, this.value, other.value), 1982 this.unit || other.unit); 1983 }, 1984 1985 // TODO: Perform unit conversion before comparing 1986 compare: function (other) { 1987 if (other instanceof tree.Dimension) { 1988 if (other.value > this.value) { 1989 return -1; 1990 } else if (other.value < this.value) { 1991 return 1; 1992 } else { 1993 return 0; 1994 } 1995 } else { 1996 return -1; 1997 } 1998 } 1999 }; 2000 2001 })(require('../tree')); 2002 (function (tree) { 2003 2004 tree.Directive = function (name, value, features) { 2005 this.name = name; 2006 2007 if (Array.isArray(value)) { 2008 this.ruleset = new(tree.Ruleset)([], value); 2009 this.ruleset.allowImports = true; 2010 } else { 2011 this.value = value; 2012 } 2013 }; 2014 tree.Directive.prototype = { 2015 toCSS: function (ctx, env) { 2016 if (this.ruleset) { 2017 this.ruleset.root = true; 2018 return this.name + (env.compress ? '{' : ' {\n ') + 2019 this.ruleset.toCSS(ctx, env).trim().replace(/\n/g, '\n ') + 2020 (env.compress ? '}': '\n}\n'); 2021 } else { 2022 return this.name + ' ' + this.value.toCSS() + ';\n'; 2023 } 2024 }, 2025 eval: function (env) { 2026 env.frames.unshift(this); 2027 this.ruleset = this.ruleset && this.ruleset.eval(env); 2028 env.frames.shift(); 2029 return this; 2030 }, 2031 variable: function (name) { return tree.Ruleset.prototype.variable.call(this.ruleset, name) }, 2032 find: function () { return tree.Ruleset.prototype.find.apply(this.ruleset, arguments) }, 2033 rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.ruleset) } 2034 }; 2035 2036 })(require('../tree')); 2037 (function (tree) { 2038 2039 tree.Element = function (combinator, value, index) { 2040 this.combinator = combinator instanceof tree.Combinator ? 2041 combinator : new(tree.Combinator)(combinator); 2042 2043 if (typeof(value) === 'string') { 2044 this.value = value.trim(); 2045 } else if (value) { 2046 this.value = value; 2047 } else { 2048 this.value = ""; 2049 } 2050 this.index = index; 2051 }; 2052 tree.Element.prototype.eval = function (env) { 2053 return new(tree.Element)(this.combinator, 2054 this.value.eval ? this.value.eval(env) : this.value, 2055 this.index); 2056 }; 2057 tree.Element.prototype.toCSS = function (env) { 2058 var value = (this.value.toCSS ? this.value.toCSS(env) : this.value); 2059 if (value == '' && this.combinator.value.charAt(0) == '&') { 2060 return ''; 2061 } else { 2062 return this.combinator.toCSS(env || {}) + value; 2063 } 2064 }; 2065 2066 tree.Combinator = function (value) { 2067 if (value === ' ') { 2068 this.value = ' '; 2069 } else if (value === '& ') { 2070 this.value = '& '; 2071 } else { 2072 this.value = value ? value.trim() : ""; 2073 } 2074 }; 2075 tree.Combinator.prototype.toCSS = function (env) { 2076 return { 2077 '' : '', 2078 ' ' : ' ', 2079 '&' : '', 2080 '& ' : ' ', 2081 ':' : ' :', 2082 '+' : env.compress ? '+' : ' + ', 2083 '~' : env.compress ? '~' : ' ~ ', 2084 '>' : env.compress ? '>' : ' > ' 2085 }[this.value]; 2086 }; 2087 2088 })(require('../tree')); 2089 (function (tree) { 2090 2091 tree.Expression = function (value) { this.value = value }; 2092 tree.Expression.prototype = { 2093 eval: function (env) { 2094 if (this.value.length > 1) { 2095 return new(tree.Expression)(this.value.map(function (e) { 2096 return e.eval(env); 2097 })); 2098 } else if (this.value.length === 1) { 2099 return this.value[0].eval(env); 2100 } else { 2101 return this; 2102 } 2103 }, 2104 toCSS: function (env) { 2105 return this.value.map(function (e) { 2106 return e.toCSS ? e.toCSS(env) : ''; 2107 }).join(' '); 2108 } 2109 }; 2110 2111 })(require('../tree')); 2112 (function (tree) { 2113 // 2114 // CSS @import node 2115 // 2116 // The general strategy here is that we don't want to wait 2117 // for the parsing to be completed, before we start importing 2118 // the file. That's because in the context of a browser, 2119 // most of the time will be spent waiting for the server to respond. 2120 // 2121 // On creation, we push the import path to our import queue, though 2122 // `import,push`, we also pass it a callback, which it'll call once 2123 // the file has been fetched, and parsed. 2124 // 2125 tree.Import = function (path, imports, features, once, index) { 2126 var that = this; 2127 2128 this.once = once; 2129 this.index = index; 2130 this._path = path; 2131 this.features = features && new(tree.Value)(features); 2132 2133 // The '.less' extension is optional 2134 if (path instanceof tree.Quoted) { 2135 this.path = /\.(le?|c)ss(\?.*)?$/.test(path.value) ? path.value : path.value + '.less'; 2136 } else { 2137 this.path = path.value.value || path.value; 2138 } 2139 2140 this.css = /css(\?.*)?$/.test(this.path); 2141 2142 // Only pre-compile .less files 2143 if (! this.css) { 2144 imports.push(this.path, function (e, root, imported) { 2145 if (e) { e.index = index } 2146 if (imported && that.once) that.skip = imported; 2147 that.root = root || new(tree.Ruleset)([], []); 2148 }); 2149 } 2150 }; 2151 2152 // 2153 // The actual import node doesn't return anything, when converted to CSS. 2154 // The reason is that it's used at the evaluation stage, so that the rules 2155 // it imports can be treated like any other rules. 2156 // 2157 // In `eval`, we make sure all Import nodes get evaluated, recursively, so 2158 // we end up with a flat structure, which can easily be imported in the parent 2159 // ruleset. 2160 // 2161 tree.Import.prototype = { 2162 toCSS: function (env) { 2163 var features = this.features ? ' ' + this.features.toCSS(env) : ''; 2164 2165 if (this.css) { 2166 return "@import " + this._path.toCSS() + features + ';\n'; 2167 } else { 2168 return ""; 2169 } 2170 }, 2171 eval: function (env) { 2172 var ruleset, features = this.features && this.features.eval(env); 2173 2174 if (this.skip) return []; 2175 2176 if (this.css) { 2177 return this; 2178 } else { 2179 ruleset = new(tree.Ruleset)([], this.root.rules.slice(0)); 2180 2181 for (var i = 0; i < ruleset.rules.length; i++) { 2182 if (ruleset.rules[i] instanceof tree.Import) { 2183 Array.prototype 2184 .splice 2185 .apply(ruleset.rules, 2186 [i, 1].concat(ruleset.rules[i].eval(env))); 2187 } 2188 } 2189 return this.features ? new(tree.Media)(ruleset.rules, this.features.value) : ruleset.rules; 2190 } 2191 } 2192 }; 2193 2194 })(require('../tree')); 2195 (function (tree) { 2196 2197 tree.JavaScript = function (string, index, escaped) { 2198 this.escaped = escaped; 2199 this.expression = string; 2200 this.index = index; 2201 }; 2202 tree.JavaScript.prototype = { 2203 eval: function (env) { 2204 var result, 2205 that = this, 2206 context = {}; 2207 2208 var expression = this.expression.replace(/@\{([\w-]+)\}/g, function (_, name) { 2209 return tree.jsify(new(tree.Variable)('@' + name, that.index).eval(env)); 2210 }); 2211 2212 try { 2213 expression = new(Function)('return (' + expression + ')'); 2214 } catch (e) { 2215 throw { message: "JavaScript evaluation error: `" + expression + "`" , 2216 index: this.index }; 2217 } 2218 2219 for (var k in env.frames[0].variables()) { 2220 context[k.slice(1)] = { 2221 value: env.frames[0].variables()[k].value, 2222 toJS: function () { 2223 return this.value.eval(env).toCSS(); 2224 } 2225 }; 2226 } 2227 2228 try { 2229 result = expression.call(context); 2230 } catch (e) { 2231 throw { message: "JavaScript evaluation error: '" + e.name + ': ' + e.message + "'" , 2232 index: this.index }; 2233 } 2234 if (typeof(result) === 'string') { 2235 return new(tree.Quoted)('"' + result + '"', result, this.escaped, this.index); 2236 } else if (Array.isArray(result)) { 2237 return new(tree.Anonymous)(result.join(', ')); 2238 } else { 2239 return new(tree.Anonymous)(result); 2240 } 2241 } 2242 }; 2243 2244 })(require('../tree')); 2245 2246 (function (tree) { 2247 2248 tree.Keyword = function (value) { this.value = value }; 2249 tree.Keyword.prototype = { 2250 eval: function () { return this }, 2251 toCSS: function () { return this.value }, 2252 compare: function (other) { 2253 if (other instanceof tree.Keyword) { 2254 return other.value === this.value ? 0 : 1; 2255 } else { 2256 return -1; 2257 } 2258 } 2259 }; 2260 2261 tree.True = new(tree.Keyword)('true'); 2262 tree.False = new(tree.Keyword)('false'); 2263 2264 })(require('../tree')); 2265 (function (tree) { 2266 2267 tree.Media = function (value, features) { 2268 var el = new(tree.Element)('&', null, 0), 2269 selectors = [new(tree.Selector)([el])]; 2270 2271 this.features = new(tree.Value)(features); 2272 this.ruleset = new(tree.Ruleset)(selectors, value); 2273 this.ruleset.allowImports = true; 2274 }; 2275 tree.Media.prototype = { 2276 toCSS: function (ctx, env) { 2277 var features = this.features.toCSS(env); 2278 2279 this.ruleset.root = (ctx.length === 0 || ctx[0].multiMedia); 2280 return '@media ' + features + (env.compress ? '{' : ' {\n ') + 2281 this.ruleset.toCSS(ctx, env).trim().replace(/\n/g, '\n ') + 2282 (env.compress ? '}': '\n}\n'); 2283 }, 2284 eval: function (env) { 2285 if (!env.mediaBlocks) { 2286 env.mediaBlocks = []; 2287 env.mediaPath = []; 2288 } 2289 2290 var blockIndex = env.mediaBlocks.length; 2291 env.mediaPath.push(this); 2292 env.mediaBlocks.push(this); 2293 2294 var media = new(tree.Media)([], []); 2295 media.features = this.features.eval(env); 2296 2297 env.frames.unshift(this.ruleset); 2298 media.ruleset = this.ruleset.eval(env); 2299 env.frames.shift(); 2300 2301 env.mediaBlocks[blockIndex] = media; 2302 env.mediaPath.pop(); 2303 2304 return env.mediaPath.length === 0 ? media.evalTop(env) : 2305 media.evalNested(env) 2306 }, 2307 variable: function (name) { return tree.Ruleset.prototype.variable.call(this.ruleset, name) }, 2308 find: function () { return tree.Ruleset.prototype.find.apply(this.ruleset, arguments) }, 2309 rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.ruleset) }, 2310 2311 evalTop: function (env) { 2312 var result = this; 2313 2314 // Render all dependent Media blocks. 2315 if (env.mediaBlocks.length > 1) { 2316 var el = new(tree.Element)('&', null, 0); 2317 var selectors = [new(tree.Selector)([el])]; 2318 result = new(tree.Ruleset)(selectors, env.mediaBlocks); 2319 result.multiMedia = true; 2320 } 2321 2322 delete env.mediaBlocks; 2323 delete env.mediaPath; 2324 2325 return result; 2326 }, 2327 evalNested: function (env) { 2328 var i, value, 2329 path = env.mediaPath.concat([this]); 2330 2331 // Extract the media-query conditions separated with `,` (OR). 2332 for (i = 0; i < path.length; i++) { 2333 value = path[i].features instanceof tree.Value ? 2334 path[i].features.value : path[i].features; 2335 path[i] = Array.isArray(value) ? value : [value]; 2336 } 2337 2338 // Trace all permutations to generate the resulting media-query. 2339 // 2340 // (a, b and c) with nested (d, e) -> 2341 // a and d 2342 // a and e 2343 // b and c and d 2344 // b and c and e 2345 this.features = new(tree.Value)(this.permute(path).map(function (path) { 2346 path = path.map(function (fragment) { 2347 return fragment.toCSS ? fragment : new(tree.Anonymous)(fragment); 2348 }); 2349 2350 for(i = path.length - 1; i > 0; i--) { 2351 path.splice(i, 0, new(tree.Anonymous)("and")); 2352 } 2353 2354 return new(tree.Expression)(path); 2355 })); 2356 2357 // Fake a tree-node that doesn't output anything. 2358 return new(tree.Ruleset)([], []); 2359 }, 2360 permute: function (arr) { 2361 if (arr.length === 0) { 2362 return []; 2363 } else if (arr.length === 1) { 2364 return arr[0]; 2365 } else { 2366 var result = []; 2367 var rest = this.permute(arr.slice(1)); 2368 for (var i = 0; i < rest.length; i++) { 2369 for (var j = 0; j < arr[0].length; j++) { 2370 result.push([arr[0][j]].concat(rest[i])); 2371 } 2372 } 2373 return result; 2374 } 2375 } 2376 }; 2377 2378 })(require('../tree')); 2379 (function (tree) { 2380 2381 tree.mixin = {}; 2382 tree.mixin.Call = function (elements, args, index, filename, important) { 2383 this.selector = new(tree.Selector)(elements); 2384 this.arguments = args; 2385 this.index = index; 2386 this.filename = filename; 2387 this.important = important; 2388 }; 2389 tree.mixin.Call.prototype = { 2390 eval: function (env) { 2391 var mixins, args, rules = [], match = false; 2392 2393 for (var i = 0; i < env.frames.length; i++) { 2394 if ((mixins = env.frames[i].find(this.selector)).length > 0) { 2395 args = this.arguments && this.arguments.map(function (a) { 2396 return { name: a.name, value: a.value.eval(env) }; 2397 }); 2398 for (var m = 0; m < mixins.length; m++) { 2399 if (mixins[m].match(args, env)) { 2400 try { 2401 Array.prototype.push.apply( 2402 rules, mixins[m].eval(env, this.arguments, this.important).rules); 2403 match = true; 2404 } catch (e) { 2405 throw { message: e.message, index: this.index, filename: this.filename, stack: e.stack }; 2406 } 2407 } 2408 } 2409 if (match) { 2410 return rules; 2411 } else { 2412 throw { type: 'Runtime', 2413 message: 'No matching definition was found for `' + 2414 this.selector.toCSS().trim() + '(' + 2415 this.arguments.map(function (a) { 2416 return a.toCSS(); 2417 }).join(', ') + ")`", 2418 index: this.index, filename: this.filename }; 2419 } 2420 } 2421 } 2422 throw { type: 'Name', 2423 message: this.selector.toCSS().trim() + " is undefined", 2424 index: this.index, filename: this.filename }; 2425 } 2426 }; 2427 2428 tree.mixin.Definition = function (name, params, rules, condition, variadic) { 2429 this.name = name; 2430 this.selectors = [new(tree.Selector)([new(tree.Element)(null, name)])]; 2431 this.params = params; 2432 this.condition = condition; 2433 this.variadic = variadic; 2434 this.arity = params.length; 2435 this.rules = rules; 2436 this._lookups = {}; 2437 this.required = params.reduce(function (count, p) { 2438 if (!p.name || (p.name && !p.value)) { return count + 1 } 2439 else { return count } 2440 }, 0); 2441 this.parent = tree.Ruleset.prototype; 2442 this.frames = []; 2443 }; 2444 tree.mixin.Definition.prototype = { 2445 toCSS: function () { return "" }, 2446 variable: function (name) { return this.parent.variable.call(this, name) }, 2447 variables: function () { return this.parent.variables.call(this) }, 2448 find: function () { return this.parent.find.apply(this, arguments) }, 2449 rulesets: function () { return this.parent.rulesets.apply(this) }, 2450 2451 evalParams: function (env, args) { 2452 var frame = new(tree.Ruleset)(null, []), varargs, arg; 2453 2454 for (var i = 0, val, name; i < this.params.length; i++) { 2455 arg = args && args[i] 2456 2457 if (arg && arg.name) { 2458 frame.rules.unshift(new(tree.Rule)(arg.name, arg.value.eval(env))); 2459 args.splice(i, 1); 2460 i--; 2461 continue; 2462 } 2463 2464 if (name = this.params[i].name) { 2465 if (this.params[i].variadic && args) { 2466 varargs = []; 2467 for (var j = i; j < args.length; j++) { 2468 varargs.push(args[j].eval(env)); 2469 } 2470 frame.rules.unshift(new(tree.Rule)(name, new(tree.Expression)(varargs).eval(env))); 2471 } else if (val = (arg && arg.value) || this.params[i].value) { 2472 frame.rules.unshift(new(tree.Rule)(name, val.eval(env))); 2473 } else { 2474 throw { type: 'Runtime', message: "wrong number of arguments for " + this.name + 2475 ' (' + args.length + ' for ' + this.arity + ')' }; 2476 } 2477 } 2478 } 2479 return frame; 2480 }, 2481 eval: function (env, args, important) { 2482 var frame = this.evalParams(env, args), context, _arguments = [], rules, start; 2483 2484 for (var i = 0; i < Math.max(this.params.length, args && args.length); i++) { 2485 _arguments.push((args[i] && args[i].value) || this.params[i].value); 2486 } 2487 frame.rules.unshift(new(tree.Rule)('@arguments', new(tree.Expression)(_arguments).eval(env))); 2488 2489 rules = important ? 2490 this.rules.map(function (r) { 2491 return new(tree.Rule)(r.name, r.value, '!important', r.index); 2492 }) : this.rules.slice(0); 2493 2494 return new(tree.Ruleset)(null, rules).eval({ 2495 frames: [this, frame].concat(this.frames, env.frames) 2496 }); 2497 }, 2498 match: function (args, env) { 2499 var argsLength = (args && args.length) || 0, len, frame; 2500 2501 if (! this.variadic) { 2502 if (argsLength < this.required) { return false } 2503 if (argsLength > this.params.length) { return false } 2504 if ((this.required > 0) && (argsLength > this.params.length)) { return false } 2505 } 2506 2507 if (this.condition && !this.condition.eval({ 2508 frames: [this.evalParams(env, args)].concat(env.frames) 2509 })) { return false } 2510 2511 len = Math.min(argsLength, this.arity); 2512 2513 for (var i = 0; i < len; i++) { 2514 if (!this.params[i].name) { 2515 if (args[i].value.eval(env).toCSS() != this.params[i].value.eval(env).toCSS()) { 2516 return false; 2517 } 2518 } 2519 } 2520 return true; 2521 } 2522 }; 2523 2524 })(require('../tree')); 2525 (function (tree) { 2526 2527 tree.Operation = function (op, operands) { 2528 this.op = op.trim(); 2529 this.operands = operands; 2530 }; 2531 tree.Operation.prototype.eval = function (env) { 2532 var a = this.operands[0].eval(env), 2533 b = this.operands[1].eval(env), 2534 temp; 2535 2536 if (a instanceof tree.Dimension && b instanceof tree.Color) { 2537 if (this.op === '*' || this.op === '+') { 2538 temp = b, b = a, a = temp; 2539 } else { 2540 throw { name: "OperationError", 2541 message: "Can't substract or divide a color from a number" }; 2542 } 2543 } 2544 return a.operate(this.op, b); 2545 }; 2546 2547 tree.operate = function (op, a, b) { 2548 switch (op) { 2549 case '+': return a + b; 2550 case '-': return a - b; 2551 case '*': return a * b; 2552 case '/': return a / b; 2553 } 2554 }; 2555 2556 })(require('../tree')); 2557 2558 (function (tree) { 2559 2560 tree.Paren = function (node) { 2561 this.value = node; 2562 }; 2563 tree.Paren.prototype = { 2564 toCSS: function (env) { 2565 return '(' + this.value.toCSS(env) + ')'; 2566 }, 2567 eval: function (env) { 2568 return new(tree.Paren)(this.value.eval(env)); 2569 } 2570 }; 2571 2572 })(require('../tree')); 2573 (function (tree) { 2574 2575 tree.Quoted = function (str, content, escaped, i) { 2576 this.escaped = escaped; 2577 this.value = content || ''; 2578 this.quote = str.charAt(0); 2579 this.index = i; 2580 }; 2581 tree.Quoted.prototype = { 2582 toCSS: function () { 2583 if (this.escaped) { 2584 return this.value; 2585 } else { 2586 return this.quote + this.value + this.quote; 2587 } 2588 }, 2589 eval: function (env) { 2590 var that = this; 2591 var value = this.value.replace(/`([^`]+)`/g, function (_, exp) { 2592 return new(tree.JavaScript)(exp, that.index, true).eval(env).value; 2593 }).replace(/@\{([\w-]+)\}/g, function (_, name) { 2594 var v = new(tree.Variable)('@' + name, that.index).eval(env); 2595 return ('value' in v) ? v.value : v.toCSS(); 2596 }); 2597 return new(tree.Quoted)(this.quote + value + this.quote, value, this.escaped, this.index); 2598 } 2599 }; 2600 2601 })(require('../tree')); 2602 (function (tree) { 2603 2604 tree.Rule = function (name, value, important, index, inline) { 2605 this.name = name; 2606 this.value = (value instanceof tree.Value) ? value : new(tree.Value)([value]); 2607 this.important = important ? ' ' + important.trim() : ''; 2608 this.index = index; 2609 this.inline = inline || false; 2610 2611 if (name.charAt(0) === '@') { 2612 this.variable = true; 2613 } else { this.variable = false } 2614 }; 2615 tree.Rule.prototype.toCSS = function (env) { 2616 if (this.variable) { return "" } 2617 else { 2618 return this.name + (env.compress ? ':' : ': ') + 2619 this.value.toCSS(env) + 2620 this.important + (this.inline ? "" : ";"); 2621 } 2622 }; 2623 2624 tree.Rule.prototype.eval = function (context) { 2625 return new(tree.Rule)(this.name, 2626 this.value.eval(context), 2627 this.important, 2628 this.index, this.inline); 2629 }; 2630 2631 tree.Shorthand = function (a, b) { 2632 this.a = a; 2633 this.b = b; 2634 }; 2635 2636 tree.Shorthand.prototype = { 2637 toCSS: function (env) { 2638 return this.a.toCSS(env) + "/" + this.b.toCSS(env); 2639 }, 2640 eval: function () { return this } 2641 }; 2642 2643 })(require('../tree')); 2644 (function (tree) { 2645 2646 tree.Ruleset = function (selectors, rules, strictImports) { 2647 this.selectors = selectors; 2648 this.rules = rules; 2649 this._lookups = {}; 2650 this.strictImports = strictImports; 2651 }; 2652 tree.Ruleset.prototype = { 2653 eval: function (env) { 2654 var selectors = this.selectors && this.selectors.map(function (s) { return s.eval(env) }); 2655 var ruleset = new(tree.Ruleset)(selectors, this.rules.slice(0), this.strictImports); 2656 2657 ruleset.root = this.root; 2658 ruleset.allowImports = this.allowImports; 2659 2660 // push the current ruleset to the frames stack 2661 env.frames.unshift(ruleset); 2662 2663 // Evaluate imports 2664 if (ruleset.root || ruleset.allowImports || !ruleset.strictImports) { 2665 for (var i = 0; i < ruleset.rules.length; i++) { 2666 if (ruleset.rules[i] instanceof tree.Import) { 2667 Array.prototype.splice 2668 .apply(ruleset.rules, [i, 1].concat(ruleset.rules[i].eval(env))); 2669 } 2670 } 2671 } 2672 2673 // Store the frames around mixin definitions, 2674 // so they can be evaluated like closures when the time comes. 2675 for (var i = 0; i < ruleset.rules.length; i++) { 2676 if (ruleset.rules[i] instanceof tree.mixin.Definition) { 2677 ruleset.rules[i].frames = env.frames.slice(0); 2678 } 2679 } 2680 2681 // Evaluate mixin calls. 2682 for (var i = 0; i < ruleset.rules.length; i++) { 2683 if (ruleset.rules[i] instanceof tree.mixin.Call) { 2684 Array.prototype.splice 2685 .apply(ruleset.rules, [i, 1].concat(ruleset.rules[i].eval(env))); 2686 } 2687 } 2688 2689 // Evaluate everything else 2690 for (var i = 0, rule; i < ruleset.rules.length; i++) { 2691 rule = ruleset.rules[i]; 2692 2693 if (! (rule instanceof tree.mixin.Definition)) { 2694 ruleset.rules[i] = rule.eval ? rule.eval(env) : rule; 2695 } 2696 } 2697 2698 // Pop the stack 2699 env.frames.shift(); 2700 2701 return ruleset; 2702 }, 2703 match: function (args) { 2704 return !args || args.length === 0; 2705 }, 2706 variables: function () { 2707 if (this._variables) { return this._variables } 2708 else { 2709 return this._variables = this.rules.reduce(function (hash, r) { 2710 if (r instanceof tree.Rule && r.variable === true) { 2711 hash[r.name] = r; 2712 } 2713 return hash; 2714 }, {}); 2715 } 2716 }, 2717 variable: function (name) { 2718 return this.variables()[name]; 2719 }, 2720 rulesets: function () { 2721 if (this._rulesets) { return this._rulesets } 2722 else { 2723 return this._rulesets = this.rules.filter(function (r) { 2724 return (r instanceof tree.Ruleset) || (r instanceof tree.mixin.Definition); 2725 }); 2726 } 2727 }, 2728 find: function (selector, self) { 2729 self = self || this; 2730 var rules = [], rule, match, 2731 key = selector.toCSS(); 2732 2733 if (key in this._lookups) { return this._lookups[key] } 2734 2735 this.rulesets().forEach(function (rule) { 2736 if (rule !== self) { 2737 for (var j = 0; j < rule.selectors.length; j++) { 2738 if (match = selector.match(rule.selectors[j])) { 2739 if (selector.elements.length > rule.selectors[j].elements.length) { 2740 Array.prototype.push.apply(rules, rule.find( 2741 new(tree.Selector)(selector.elements.slice(1)), self)); 2742 } else { 2743 rules.push(rule); 2744 } 2745 break; 2746 } 2747 } 2748 } 2749 }); 2750 return this._lookups[key] = rules; 2751 }, 2752 // 2753 // Entry point for code generation 2754 // 2755 // `context` holds an array of arrays. 2756 // 2757 toCSS: function (context, env) { 2758 var css = [], // The CSS output 2759 rules = [], // node.Rule instances 2760 _rules = [], // 2761 rulesets = [], // node.Ruleset instances 2762 paths = [], // Current selectors 2763 selector, // The fully rendered selector 2764 rule; 2765 2766 if (! this.root) { 2767 if (context.length === 0) { 2768 paths = this.selectors.map(function (s) { return [s] }); 2769 } else { 2770 this.joinSelectors(paths, context, this.selectors); 2771 } 2772 } 2773 2774 // Compile rules and rulesets 2775 for (var i = 0; i < this.rules.length; i++) { 2776 rule = this.rules[i]; 2777 2778 if (rule.rules || (rule instanceof tree.Directive) || (rule instanceof tree.Media)) { 2779 rulesets.push(rule.toCSS(paths, env)); 2780 } else if (rule instanceof tree.Comment) { 2781 if (!rule.silent) { 2782 if (this.root) { 2783 rulesets.push(rule.toCSS(env)); 2784 } else { 2785 rules.push(rule.toCSS(env)); 2786 } 2787 } 2788 } else { 2789 if (rule.toCSS && !rule.variable) { 2790 rules.push(rule.toCSS(env)); 2791 } else if (rule.value && !rule.variable) { 2792 rules.push(rule.value.toString()); 2793 } 2794 } 2795 } 2796 2797 rulesets = rulesets.join(''); 2798 2799 // If this is the root node, we don't render 2800 // a selector, or {}. 2801 // Otherwise, only output if this ruleset has rules. 2802 if (this.root) { 2803 css.push(rules.join(env.compress ? '' : '\n')); 2804 } else { 2805 if (rules.length > 0) { 2806 selector = paths.map(function (p) { 2807 return p.map(function (s) { 2808 return s.toCSS(env); 2809 }).join('').trim(); 2810 }).join(env.compress ? ',' : ',\n'); 2811 2812 // Remove duplicates 2813 for (var i = rules.length - 1; i >= 0; i--) { 2814 if (_rules.indexOf(rules[i]) === -1) { 2815 _rules.unshift(rules[i]); 2816 } 2817 } 2818 rules = _rules; 2819 2820 css.push(selector, 2821 (env.compress ? '{' : ' {\n ') + 2822 rules.join(env.compress ? '' : '\n ') + 2823 (env.compress ? '}' : '\n}\n')); 2824 } 2825 } 2826 css.push(rulesets); 2827 2828 return css.join('') + (env.compress ? '\n' : ''); 2829 }, 2830 2831 joinSelectors: function (paths, context, selectors) { 2832 for (var s = 0; s < selectors.length; s++) { 2833 this.joinSelector(paths, context, selectors[s]); 2834 } 2835 }, 2836 2837 joinSelector: function (paths, context, selector) { 2838 var before = [], after = [], beforeElements = [], 2839 afterElements = [], hasParentSelector = false, el; 2840 2841 for (var i = 0; i < selector.elements.length; i++) { 2842 el = selector.elements[i]; 2843 if (el.combinator.value.charAt(0) === '&') { 2844 hasParentSelector = true; 2845 } 2846 if (hasParentSelector) afterElements.push(el); 2847 else beforeElements.push(el); 2848 } 2849 2850 if (! hasParentSelector) { 2851 afterElements = beforeElements; 2852 beforeElements = []; 2853 } 2854 2855 if (beforeElements.length > 0) { 2856 before.push(new(tree.Selector)(beforeElements)); 2857 } 2858 2859 if (afterElements.length > 0) { 2860 after.push(new(tree.Selector)(afterElements)); 2861 } 2862 2863 for (var c = 0; c < context.length; c++) { 2864 paths.push(before.concat(context[c]).concat(after)); 2865 } 2866 } 2867 }; 2868 })(require('../tree')); 2869 (function (tree) { 2870 2871 tree.Selector = function (elements) { 2872 this.elements = elements; 2873 if (this.elements[0].combinator.value === "") { 2874 this.elements[0].combinator.value = ' '; 2875 } 2876 }; 2877 tree.Selector.prototype.match = function (other) { 2878 var len = this.elements.length, 2879 olen = other.elements.length, 2880 max = Math.min(len, olen); 2881 2882 if (len < olen) { 2883 return false; 2884 } else { 2885 for (var i = 0; i < max; i++) { 2886 if (this.elements[i].value !== other.elements[i].value) { 2887 return false; 2888 } 2889 } 2890 } 2891 return true; 2892 }; 2893 tree.Selector.prototype.eval = function (env) { 2894 return new(tree.Selector)(this.elements.map(function (e) { 2895 return e.eval(env); 2896 })); 2897 }; 2898 tree.Selector.prototype.toCSS = function (env) { 2899 if (this._css) { return this._css } 2900 2901 return this._css = this.elements.map(function (e) { 2902 if (typeof(e) === 'string') { 2903 return ' ' + e.trim(); 2904 } else { 2905 return e.toCSS(env); 2906 } 2907 }).join(''); 2908 }; 2909 2910 })(require('../tree')); 2911 (function (tree) { 2912 2913 tree.URL = function (val, paths) { 2914 if (val.data) { 2915 this.attrs = val; 2916 } else { 2917 // Add the base path if the URL is relative and we are in the browser 2918 if (typeof(window) !== 'undefined' && !/^(?:https?:\/\/|file:\/\/|data:|\/)/.test(val.value) && paths.length > 0) { 2919 val.value = paths[0] + (val.value.charAt(0) === '/' ? val.value.slice(1) : val.value); 2920 } 2921 this.value = val; 2922 this.paths = paths; 2923 } 2924 }; 2925 tree.URL.prototype = { 2926 toCSS: function () { 2927 return "url(" + (this.attrs ? 'data:' + this.attrs.mime + this.attrs.charset + this.attrs.base64 + this.attrs.data 2928 : this.value.toCSS()) + ")"; 2929 }, 2930 eval: function (ctx) { 2931 return this.attrs ? this : new(tree.URL)(this.value.eval(ctx), this.paths); 2932 } 2933 }; 2934 2935 })(require('../tree')); 2936 (function (tree) { 2937 2938 tree.Value = function (value) { 2939 this.value = value; 2940 this.is = 'value'; 2941 }; 2942 tree.Value.prototype = { 2943 eval: function (env) { 2944 if (this.value.length === 1) { 2945 return this.value[0].eval(env); 2946 } else { 2947 return new(tree.Value)(this.value.map(function (v) { 2948 return v.eval(env); 2949 })); 2950 } 2951 }, 2952 toCSS: function (env) { 2953 return this.value.map(function (e) { 2954 return e.toCSS(env); 2955 }).join(env.compress ? ',' : ', '); 2956 } 2957 }; 2958 2959 })(require('../tree')); 2960 (function (tree) { 2961 2962 tree.Variable = function (name, index, file) { this.name = name, this.index = index, this.file = file }; 2963 tree.Variable.prototype = { 2964 eval: function (env) { 2965 var variable, v, name = this.name; 2966 2967 if (name.indexOf('@@') == 0) { 2968 name = '@' + new(tree.Variable)(name.slice(1)).eval(env).value; 2969 } 2970 2971 if (variable = tree.find(env.frames, function (frame) { 2972 if (v = frame.variable(name)) { 2973 return v.value.eval(env); 2974 } 2975 })) { return variable } 2976 else { 2977 throw { type: 'Name', 2978 message: "variable " + name + " is undefined", 2979 filename: this.file, 2980 index: this.index }; 2981 } 2982 } 2983 }; 2984 2985 })(require('../tree')); 2986 (function (tree) { 2987 2988 tree.find = function (obj, fun) { 2989 for (var i = 0, r; i < obj.length; i++) { 2990 if (r = fun.call(obj, obj[i])) { return r } 2991 } 2992 return null; 2993 }; 2994 tree.jsify = function (obj) { 2995 if (Array.isArray(obj.value) && (obj.value.length > 1)) { 2996 return '[' + obj.value.map(function (v) { return v.toCSS(false) }).join(', ') + ']'; 2997 } else { 2998 return obj.toCSS(false); 2999 } 3000 }; 3001 3002 })(require('./tree')); 3003 (function (tree) { 3004 tree.colors = { 3005 'aliceblue':'#f0f8ff', 3006 'antiquewhite':'#faebd7', 3007 'aqua':'#00ffff', 3008 'aquamarine':'#7fffd4', 3009 'azure':'#f0ffff', 3010 'beige':'#f5f5dc', 3011 'bisque':'#ffe4c4', 3012 'black':'#000000', 3013 'blanchedalmond':'#ffebcd', 3014 'blue':'#0000ff', 3015 'blueviolet':'#8a2be2', 3016 'brown':'#a52a2a', 3017 'burlywood':'#deb887', 3018 'cadetblue':'#5f9ea0', 3019 'chartreuse':'#7fff00', 3020 'chocolate':'#d2691e', 3021 'coral':'#ff7f50', 3022 'cornflowerblue':'#6495ed', 3023 'cornsilk':'#fff8dc', 3024 'crimson':'#dc143c', 3025 'cyan':'#00ffff', 3026 'darkblue':'#00008b', 3027 'darkcyan':'#008b8b', 3028 'darkgoldenrod':'#b8860b', 3029 'darkgray':'#a9a9a9', 3030 'darkgrey':'#a9a9a9', 3031 'darkgreen':'#006400', 3032 'darkkhaki':'#bdb76b', 3033 'darkmagenta':'#8b008b', 3034 'darkolivegreen':'#556b2f', 3035 'darkorange':'#ff8c00', 3036 'darkorchid':'#9932cc', 3037 'darkred':'#8b0000', 3038 'darksalmon':'#e9967a', 3039 'darkseagreen':'#8fbc8f', 3040 'darkslateblue':'#483d8b', 3041 'darkslategray':'#2f4f4f', 3042 'darkslategrey':'#2f4f4f', 3043 'darkturquoise':'#00ced1', 3044 'darkviolet':'#9400d3', 3045 'deeppink':'#ff1493', 3046 'deepskyblue':'#00bfff', 3047 'dimgray':'#696969', 3048 'dimgrey':'#696969', 3049 'dodgerblue':'#1e90ff', 3050 'firebrick':'#b22222', 3051 'floralwhite':'#fffaf0', 3052 'forestgreen':'#228b22', 3053 'fuchsia':'#ff00ff', 3054 'gainsboro':'#dcdcdc', 3055 'ghostwhite':'#f8f8ff', 3056 'gold':'#ffd700', 3057 'goldenrod':'#daa520', 3058 'gray':'#808080', 3059 'grey':'#808080', 3060 'green':'#008000', 3061 'greenyellow':'#adff2f', 3062 'honeydew':'#f0fff0', 3063 'hotpink':'#ff69b4', 3064 'indianred':'#cd5c5c', 3065 'indigo':'#4b0082', 3066 'ivory':'#fffff0', 3067 'khaki':'#f0e68c', 3068 'lavender':'#e6e6fa', 3069 'lavenderblush':'#fff0f5', 3070 'lawngreen':'#7cfc00', 3071 'lemonchiffon':'#fffacd', 3072 'lightblue':'#add8e6', 3073 'lightcoral':'#f08080', 3074 'lightcyan':'#e0ffff', 3075 'lightgoldenrodyellow':'#fafad2', 3076 'lightgray':'#d3d3d3', 3077 'lightgrey':'#d3d3d3', 3078 'lightgreen':'#90ee90', 3079 'lightpink':'#ffb6c1', 3080 'lightsalmon':'#ffa07a', 3081 'lightseagreen':'#20b2aa', 3082 'lightskyblue':'#87cefa', 3083 'lightslategray':'#778899', 3084 'lightslategrey':'#778899', 3085 'lightsteelblue':'#b0c4de', 3086 'lightyellow':'#ffffe0', 3087 'lime':'#00ff00', 3088 'limegreen':'#32cd32', 3089 'linen':'#faf0e6', 3090 'magenta':'#ff00ff', 3091 'maroon':'#800000', 3092 'mediumaquamarine':'#66cdaa', 3093 'mediumblue':'#0000cd', 3094 'mediumorchid':'#ba55d3', 3095 'mediumpurple':'#9370d8', 3096 'mediumseagreen':'#3cb371', 3097 'mediumslateblue':'#7b68ee', 3098 'mediumspringgreen':'#00fa9a', 3099 'mediumturquoise':'#48d1cc', 3100 'mediumvioletred':'#c71585', 3101 'midnightblue':'#191970', 3102 'mintcream':'#f5fffa', 3103 'mistyrose':'#ffe4e1', 3104 'moccasin':'#ffe4b5', 3105 'navajowhite':'#ffdead', 3106 'navy':'#000080', 3107 'oldlace':'#fdf5e6', 3108 'olive':'#808000', 3109 'olivedrab':'#6b8e23', 3110 'orange':'#ffa500', 3111 'orangered':'#ff4500', 3112 'orchid':'#da70d6', 3113 'palegoldenrod':'#eee8aa', 3114 'palegreen':'#98fb98', 3115 'paleturquoise':'#afeeee', 3116 'palevioletred':'#d87093', 3117 'papayawhip':'#ffefd5', 3118 'peachpuff':'#ffdab9', 3119 'peru':'#cd853f', 3120 'pink':'#ffc0cb', 3121 'plum':'#dda0dd', 3122 'powderblue':'#b0e0e6', 3123 'purple':'#800080', 3124 'red':'#ff0000', 3125 'rosybrown':'#bc8f8f', 3126 'royalblue':'#4169e1', 3127 'saddlebrown':'#8b4513', 3128 'salmon':'#fa8072', 3129 'sandybrown':'#f4a460', 3130 'seagreen':'#2e8b57', 3131 'seashell':'#fff5ee', 3132 'sienna':'#a0522d', 3133 'silver':'#c0c0c0', 3134 'skyblue':'#87ceeb', 3135 'slateblue':'#6a5acd', 3136 'slategray':'#708090', 3137 'slategrey':'#708090', 3138 'snow':'#fffafa', 3139 'springgreen':'#00ff7f', 3140 'steelblue':'#4682b4', 3141 'tan':'#d2b48c', 3142 'teal':'#008080', 3143 'thistle':'#d8bfd8', 3144 'tomato':'#ff6347', 3145 'turquoise':'#40e0d0', 3146 'violet':'#ee82ee', 3147 'wheat':'#f5deb3', 3148 'white':'#ffffff', 3149 'whitesmoke':'#f5f5f5', 3150 'yellow':'#ffff00', 3151 'yellowgreen':'#9acd32' 3152 }; 3153 })(require('./tree')); 3154 var name; 3155 3156 function loadStyleSheet(sheet, callback, reload, remaining) { 3157 var sheetName = name.slice(0, name.lastIndexOf('/') + 1) + sheet.href; 3158 var input = readFile(sheetName); 3159 var parser = new less.Parser({ 3160 paths: [sheet.href.replace(/[\w\.-]+$/, '')] 3161 }); 3162 parser.parse(input, function (e, root) { 3163 if (e) { 3164 print("Error: " + e); 3165 quit(1); 3166 } 3167 callback(root, sheet, { local: false, lastModified: 0, remaining: remaining }); 3168 }); 3169 3170 // callback({}, sheet, { local: true, remaining: remaining }); 3171 } 3172 3173 function writeFile(filename, content) { 3174 var fstream = new java.io.FileWriter(filename); 3175 var out = new java.io.BufferedWriter(fstream); 3176 out.write(content); 3177 out.close(); 3178 } 3179 3180 // Prevent command line integration with Helma 3181 if (typeof HopObject === 'undefined') { 3182 // Command line integration via Rhino 3183 (function (args) { 3184 name = args[0]; 3185 var output = args[1]; 3186 3187 if (!name) { 3188 print('No files present in the fileset; Check your pattern match in build.xml'); 3189 quit(1); 3190 } 3191 path = name.split("/");path.pop();path=path.join("/") 3192 3193 var input = readFile(name); 3194 3195 if (!input) { 3196 print('lesscss: couldn\'t open file ' + name); 3197 quit(1); 3198 } 3199 3200 var result; 3201 var parser = new less.Parser(); 3202 parser.parse(input, function (e, root) { 3203 if (e) { 3204 quit(1); 3205 } else { 3206 result = root.toCSS(); 3207 if (output) { 3208 writeFile(output, result); 3209 print("Written to " + output); 3210 } else { 3211 print(result); 3212 } 3213 quit(0); 3214 } 3215 }); 3216 print("done"); 3217 }(arguments)); 3218 }})(); 3219