less-1.0.35.js (84928B)
1 // 2 // LESS - Leaner CSS v1.0.35 3 // http://lesscss.org 4 // 5 // Copyright (c) 2010, Alexis Sellier 6 // Licensed under the Apache 2.0 License. 7 // 8 (function (window, undefined) { 9 // 10 // Stub out `require` in the browser 11 // 12 function require(arg) { 13 return window.less[arg.split('/')[1]]; 14 }; 15 16 17 // ecma-5.js 18 // 19 // -- kriskowal Kris Kowal Copyright (C) 2009-2010 MIT License 20 // -- tlrobinson Tom Robinson 21 // dantman Daniel Friesen 22 23 // 24 // Array 25 // 26 if (!Array.isArray) { 27 Array.isArray = function(obj) { 28 return Object.prototype.toString.call(obj) === "[object Array]" || 29 (obj instanceof Array); 30 }; 31 } 32 if (!Array.prototype.forEach) { 33 Array.prototype.forEach = function(block, thisObject) { 34 var len = this.length >>> 0; 35 for (var i = 0; i < len; i++) { 36 if (i in this) { 37 block.call(thisObject, this[i], i, this); 38 } 39 } 40 }; 41 } 42 if (!Array.prototype.map) { 43 Array.prototype.map = function(fun /*, thisp*/) { 44 var len = this.length >>> 0; 45 var res = new Array(len); 46 var thisp = arguments[1]; 47 48 for (var i = 0; i < len; i++) { 49 if (i in this) { 50 res[i] = fun.call(thisp, this[i], i, this); 51 } 52 } 53 return res; 54 }; 55 } 56 if (!Array.prototype.filter) { 57 Array.prototype.filter = function (block /*, thisp */) { 58 var values = []; 59 var thisp = arguments[1]; 60 for (var i = 0; i < this.length; i++) { 61 if (block.call(thisp, this[i])) { 62 values.push(this[i]); 63 } 64 } 65 return values; 66 }; 67 } 68 if (!Array.prototype.reduce) { 69 Array.prototype.reduce = function(fun /*, initial*/) { 70 var len = this.length >>> 0; 71 var i = 0; 72 73 // no value to return if no initial value and an empty array 74 if (len === 0 && arguments.length === 1) throw new TypeError(); 75 76 if (arguments.length >= 2) { 77 var rv = arguments[1]; 78 } else { 79 do { 80 if (i in this) { 81 rv = this[i++]; 82 break; 83 } 84 // if array contains no values, no initial value to return 85 if (++i >= len) throw new TypeError(); 86 } while (true); 87 } 88 for (; i < len; i++) { 89 if (i in this) { 90 rv = fun.call(null, rv, this[i], i, this); 91 } 92 } 93 return rv; 94 }; 95 } 96 if (!Array.prototype.indexOf) { 97 Array.prototype.indexOf = function (value /*, fromIndex */ ) { 98 var length = this.length; 99 var i = arguments[1] || 0; 100 101 if (!length) return -1; 102 if (i >= length) return -1; 103 if (i < 0) i += length; 104 105 for (; i < length; i++) { 106 if (!Object.prototype.hasOwnProperty.call(this, i)) { continue } 107 if (value === this[i]) return i; 108 } 109 return -1; 110 }; 111 } 112 113 // 114 // Object 115 // 116 if (!Object.keys) { 117 Object.keys = function (object) { 118 var keys = []; 119 for (var name in object) { 120 if (Object.prototype.hasOwnProperty.call(object, name)) { 121 keys.push(name); 122 } 123 } 124 return keys; 125 }; 126 } 127 128 // 129 // String 130 // 131 if (!String.prototype.trim) { 132 String.prototype.trim = function () { 133 return String(this).replace(/^\s\s*/, '').replace(/\s\s*$/, ''); 134 }; 135 } 136 var less, tree; 137 138 if (typeof(window) === 'undefined') { 139 less = exports, 140 tree = require('less/tree'); 141 } else { 142 if (typeof(window.less) === 'undefined') { window.less = {} } 143 less = window.less, 144 tree = window.less.tree = {}; 145 } 146 // 147 // less.js - parser 148 // 149 // A relatively straight-forward predictive parser. 150 // There is no tokenization/lexing stage, the input is parsed 151 // in one sweep. 152 // 153 // To make the parser fast enough to run in the browser, several 154 // optimization had to be made: 155 // 156 // - Matching and slicing on a huge input is often cause of slowdowns. 157 // The solution is to chunkify the input into smaller strings. 158 // The chunks are stored in the `chunks` var, 159 // `j` holds the current chunk index, and `current` holds 160 // the index of the current chunk in relation to `input`. 161 // This gives us an almost 4x speed-up. 162 // 163 // - In many cases, we don't need to match individual tokens; 164 // for example, if a value doesn't hold any variables, operations 165 // or dynamic references, the parser can effectively 'skip' it, 166 // treating it as a literal. 167 // An example would be '1px solid #000' - which evaluates to itself, 168 // we don't need to know what the individual components are. 169 // The drawback, of course is that you don't get the benefits of 170 // syntax-checking on the CSS. This gives us a 50% speed-up in the parser, 171 // and a smaller speed-up in the code-gen. 172 // 173 // 174 // Token matching is done with the `$` function, which either takes 175 // a terminal string or regexp, or a non-terminal function to call. 176 // It also takes care of moving all the indices forwards. 177 // 178 // 179 less.Parser = function Parser(env) { 180 var input, // LeSS input string 181 i, // current index in `input` 182 j, // current chunk 183 temp, // temporarily holds a chunk's state, for backtracking 184 memo, // temporarily holds `i`, when backtracking 185 furthest, // furthest index the parser has gone to 186 chunks, // chunkified input 187 current, // index of current chunk, in `input` 188 parser; 189 190 var that = this; 191 192 // This function is called after all files 193 // have been imported through `@import`. 194 var finish = function () {}; 195 196 var imports = this.imports = { 197 paths: env && env.paths || [], // Search paths, when importing 198 queue: [], // Files which haven't been imported yet 199 files: {}, // Holds the imported parse trees 200 push: function (path, callback) { 201 var that = this; 202 this.queue.push(path); 203 204 // 205 // Import a file asynchronously 206 // 207 less.Parser.importer(path, this.paths, function (root) { 208 that.queue.splice(that.queue.indexOf(path), 1); // Remove the path from the queue 209 that.files[path] = root; // Store the root 210 211 callback(root); 212 213 if (that.queue.length === 0) { finish() } // Call `finish` if we're done importing 214 }); 215 } 216 }; 217 218 function save() { temp = chunks[j], memo = i, current = i } 219 function restore() { chunks[j] = temp, i = memo, current = i } 220 221 function sync() { 222 if (i > current) { 223 chunks[j] = chunks[j].slice(i - current); 224 current = i; 225 } 226 } 227 // 228 // Parse from a token, regexp or string, and move forward if match 229 // 230 function $(tok) { 231 var match, args, length, c, index, endIndex, k; 232 233 // 234 // Non-terminal 235 // 236 if (tok instanceof Function) { 237 return tok.call(parser.parsers); 238 // 239 // Terminal 240 // 241 // Either match a single character in the input, 242 // or match a regexp in the current chunk (chunk[j]). 243 // 244 } else if (typeof(tok) === 'string') { 245 match = input.charAt(i) === tok ? tok : null; 246 length = 1; 247 sync (); 248 249 // 1. We move to the next chunk, if necessary. 250 // 2. Set the `lastIndex` to be relative 251 // to the current chunk, and try to match in it. 252 // 3. Make sure we matched at `index`. Because we use 253 // the /g flag, the match could be anywhere in the 254 // chunk. We have to make sure it's at our previous 255 // index, which we stored in [2]. 256 // 257 } else { 258 sync (); 259 260 if (match = tok.exec(chunks[j])) { // 3. 261 length = match[0].length; 262 } else { 263 return null; 264 } 265 } 266 267 // The match is confirmed, add the match length to `i`, 268 // and consume any extra white-space characters (' ' || '\n') 269 // which come after that. The reason for this is that LeSS's 270 // grammar is mostly white-space insensitive. 271 // 272 if (match) { 273 mem = i += length; 274 endIndex = i + chunks[j].length - length; 275 276 while (i < endIndex) { 277 c = input.charCodeAt(i); 278 if (! (c === 32 || c === 10 || c === 9)) { break } 279 i++; 280 } 281 chunks[j] = chunks[j].slice(length + (i - mem)); 282 current = i; 283 284 if (chunks[j].length === 0 && j < chunks.length - 1) { j++ } 285 286 if(typeof(match) === 'string') { 287 return match; 288 } else { 289 return match.length === 1 ? match[0] : match; 290 } 291 } 292 } 293 294 // Same as $(), but don't change the state of the parser, 295 // just return the match. 296 function peek(tok) { 297 if (typeof(tok) === 'string') { 298 return input.charAt(i) === tok; 299 } else { 300 if (tok.test(chunks[j])) { 301 return true; 302 } else { 303 return false; 304 } 305 } 306 } 307 308 this.env = env = env || {}; 309 310 // The optimization level dictates the thoroughness of the parser, 311 // the lower the number, the less nodes it will create in the tree. 312 // This could matter for debugging, or if you want to access 313 // the individual nodes in the tree. 314 this.optimization = ('optimization' in this.env) ? this.env.optimization : 1; 315 316 this.env.filename = this.env.filename || null; 317 318 // 319 // The Parser 320 // 321 return parser = { 322 323 imports: imports, 324 // 325 // Parse an input string into an abstract syntax tree, 326 // call `callback` when done. 327 // 328 parse: function (str, callback) { 329 var root, start, end, zone, line, lines, buff = [], c, error = null; 330 331 i = j = current = furthest = 0; 332 chunks = []; 333 input = str.replace(/\r\n/g, '\n'); 334 335 // Split the input into chunks. 336 chunks = (function (chunks) { 337 var j = 0, 338 skip = /[^"'`\{\}\/]+/g, 339 comment = /\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g, 340 level = 0, 341 match, 342 chunk = chunks[0], 343 inString; 344 345 for (var i = 0, c, cc; i < input.length; i++) { 346 skip.lastIndex = i; 347 if (match = skip.exec(input)) { 348 if (match.index === i) { 349 i += match[0].length; 350 chunk.push(match[0]); 351 } 352 } 353 c = input.charAt(i); 354 comment.lastIndex = i; 355 356 if (!inString && c === '/') { 357 cc = input.charAt(i + 1); 358 if (cc === '/' || cc === '*') { 359 if (match = comment.exec(input)) { 360 if (match.index === i) { 361 i += match[0].length; 362 chunk.push(match[0]); 363 c = input.charAt(i); 364 } 365 } 366 } 367 } 368 369 if (c === '{' && !inString) { level ++; 370 chunk.push(c); 371 } else if (c === '}' && !inString) { level --; 372 chunk.push(c); 373 chunks[++j] = chunk = []; 374 } else { 375 if (c === '"' || c === "'" || c === '`') { 376 if (! inString) { 377 inString = c; 378 } else { 379 inString = inString === c ? false : inString; 380 } 381 } 382 chunk.push(c); 383 } 384 } 385 if (level > 0) { 386 throw { 387 type: 'Syntax', 388 message: "Missing closing `}`", 389 filename: env.filename 390 }; 391 } 392 393 return chunks.map(function (c) { return c.join('') });; 394 })([[]]); 395 396 // Start with the primary rule. 397 // The whole syntax tree is held under a Ruleset node, 398 // with the `root` property set to true, so no `{}` are 399 // output. The callback is called when the input is parsed. 400 root = new(tree.Ruleset)([], $(this.parsers.primary)); 401 root.root = true; 402 403 root.toCSS = (function (evaluate) { 404 var line, lines, column; 405 406 return function (options, variables) { 407 var frames = []; 408 409 options = options || {}; 410 // 411 // Allows setting variables with a hash, so: 412 // 413 // `{ color: new(tree.Color)('#f01') }` will become: 414 // 415 // new(tree.Rule)('@color', 416 // new(tree.Value)([ 417 // new(tree.Expression)([ 418 // new(tree.Color)('#f01') 419 // ]) 420 // ]) 421 // ) 422 // 423 if (typeof(variables) === 'object' && !Array.isArray(variables)) { 424 variables = Object.keys(variables).map(function (k) { 425 var value = variables[k]; 426 427 if (! (value instanceof tree.Value)) { 428 if (! (value instanceof tree.Expression)) { 429 value = new(tree.Expression)([value]); 430 } 431 value = new(tree.Value)([value]); 432 } 433 return new(tree.Rule)('@' + k, value, false, 0); 434 }); 435 frames = [new(tree.Ruleset)(null, variables)]; 436 } 437 438 try { 439 var css = evaluate.call(this, { frames: frames }) 440 .toCSS([], { compress: options.compress || false }); 441 } catch (e) { 442 lines = input.split('\n'); 443 line = getLine(e.index); 444 445 for (var n = e.index, column = -1; 446 n >= 0 && input.charAt(n) !== '\n'; 447 n--) { column++ } 448 449 throw { 450 type: e.type, 451 message: e.message, 452 filename: env.filename, 453 index: e.index, 454 line: typeof(line) === 'number' ? line + 1 : null, 455 callLine: e.call && (getLine(e.call) + 1), 456 callExtract: lines[getLine(e.call)], 457 stack: e.stack, 458 column: column, 459 extract: [ 460 lines[line - 1], 461 lines[line], 462 lines[line + 1] 463 ] 464 }; 465 } 466 if (options.compress) { 467 return css.replace(/(\s)+/g, "$1"); 468 } else { 469 return css; 470 } 471 472 function getLine(index) { 473 return index ? (input.slice(0, index).match(/\n/g) || "").length : null; 474 } 475 }; 476 })(root.eval); 477 478 // If `i` is smaller than the `input.length - 1`, 479 // it means the parser wasn't able to parse the whole 480 // string, so we've got a parsing error. 481 // 482 // We try to extract a \n delimited string, 483 // showing the line where the parse error occured. 484 // We split it up into two parts (the part which parsed, 485 // and the part which didn't), so we can color them differently. 486 if (i < input.length - 1) { 487 i = furthest; 488 lines = input.split('\n'); 489 line = (input.slice(0, i).match(/\n/g) || "").length + 1; 490 491 for (var n = i, column = -1; n >= 0 && input.charAt(n) !== '\n'; n--) { column++ } 492 493 error = { 494 name: "ParseError", 495 message: "Syntax Error on line " + line, 496 filename: env.filename, 497 line: line, 498 column: column, 499 extract: [ 500 lines[line - 2], 501 lines[line - 1], 502 lines[line] 503 ] 504 }; 505 } 506 507 if (this.imports.queue.length > 0) { 508 finish = function () { callback(error, root) }; 509 } else { 510 callback(error, root); 511 } 512 }, 513 514 // 515 // Here in, the parsing rules/functions 516 // 517 // The basic structure of the syntax tree generated is as follows: 518 // 519 // Ruleset -> Rule -> Value -> Expression -> Entity 520 // 521 // Here's some LESS code: 522 // 523 // .class { 524 // color: #fff; 525 // border: 1px solid #000; 526 // width: @w + 4px; 527 // > .child {...} 528 // } 529 // 530 // And here's what the parse tree might look like: 531 // 532 // Ruleset (Selector '.class', [ 533 // Rule ("color", Value ([Expression [Color #fff]])) 534 // Rule ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]])) 535 // Rule ("width", Value ([Expression [Operation "+" [Variable "@w"][Dimension 4px]]])) 536 // Ruleset (Selector [Element '>', '.child'], [...]) 537 // ]) 538 // 539 // In general, most rules will try to parse a token with the `$()` function, and if the return 540 // value is truly, will return a new node, of the relevant type. Sometimes, we need to check 541 // first, before parsing, that's when we use `peek()`. 542 // 543 parsers: { 544 // 545 // The `primary` rule is the *entry* and *exit* point of the parser. 546 // The rules here can appear at any level of the parse tree. 547 // 548 // The recursive nature of the grammar is an interplay between the `block` 549 // rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule, 550 // as represented by this simplified grammar: 551 // 552 // primary → (ruleset | rule)+ 553 // ruleset → selector+ block 554 // block → '{' primary '}' 555 // 556 // Only at one point is the primary rule not called from the 557 // block rule: at the root level. 558 // 559 primary: function () { 560 var node, root = []; 561 562 while ((node = $(this.mixin.definition) || $(this.rule) || $(this.ruleset) || 563 $(this.mixin.call) || $(this.comment) || $(this.directive)) 564 || $(/^[\s\n]+/)) { 565 node && root.push(node); 566 } 567 return root; 568 }, 569 570 // We create a Comment node for CSS comments `/* */`, 571 // but keep the LeSS comments `//` silent, by just skipping 572 // over them. 573 comment: function () { 574 var comment; 575 576 if (input.charAt(i) !== '/') return; 577 578 if (input.charAt(i + 1) === '/') { 579 return new(tree.Comment)($(/^\/\/.*/), true); 580 } else if (comment = $(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/)) { 581 return new(tree.Comment)(comment); 582 } 583 }, 584 585 // 586 // Entities are tokens which can be found inside an Expression 587 // 588 entities: { 589 // 590 // A string, which supports escaping " and ' 591 // 592 // "milky way" 'he\'s the one!' 593 // 594 quoted: function () { 595 var str; 596 if (input.charAt(i) !== '"' && input.charAt(i) !== "'") return; 597 598 if (str = $(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/)) { 599 return new(tree.Quoted)(str[0], str[1] || str[2]); 600 } 601 }, 602 603 // 604 // A catch-all word, such as: 605 // 606 // black border-collapse 607 // 608 keyword: function () { 609 var k; 610 if (k = $(/^[A-Za-z-]+/)) { return new(tree.Keyword)(k) } 611 }, 612 613 // 614 // A function call 615 // 616 // rgb(255, 0, 255) 617 // 618 // We also try to catch IE's `alpha()`, but let the `alpha` parser 619 // deal with the details. 620 // 621 // The arguments are parsed with the `entities.arguments` parser. 622 // 623 call: function () { 624 var name, args; 625 626 if (! (name = /^([\w-]+|%)\(/.exec(chunks[j]))) return; 627 628 name = name[1].toLowerCase(); 629 630 if (name === 'url') { return null } 631 else { i += name.length + 1 } 632 633 if (name === 'alpha') { return $(this.alpha) } 634 635 args = $(this.entities.arguments); 636 637 if (! $(')')) return; 638 639 if (name) { return new(tree.Call)(name, args) } 640 }, 641 arguments: function () { 642 var args = [], arg; 643 644 while (arg = $(this.expression)) { 645 args.push(arg); 646 if (! $(',')) { break } 647 } 648 return args; 649 }, 650 literal: function () { 651 return $(this.entities.dimension) || 652 $(this.entities.color) || 653 $(this.entities.quoted); 654 }, 655 656 // 657 // Parse url() tokens 658 // 659 // We use a specific rule for urls, because they don't really behave like 660 // standard function calls. The difference is that the argument doesn't have 661 // to be enclosed within a string, so it can't be parsed as an Expression. 662 // 663 url: function () { 664 var value; 665 666 if (input.charAt(i) !== 'u' || !$(/^url\(/)) return; 667 value = $(this.entities.quoted) || $(this.entities.variable) || $(/^[-\w%@$\/.&=:;#+?]+/) || ""; 668 if (! $(')')) throw new(Error)("missing closing ) for url()"); 669 670 return new(tree.URL)((value.value || value instanceof tree.Variable) 671 ? value : new(tree.Anonymous)(value), imports.paths); 672 }, 673 674 // 675 // A Variable entity, such as `@fink`, in 676 // 677 // width: @fink + 2px 678 // 679 // We use a different parser for variable definitions, 680 // see `parsers.variable`. 681 // 682 variable: function () { 683 var name, index = i; 684 685 if (input.charAt(i) === '@' && (name = $(/^@[\w-]+/))) { 686 return new(tree.Variable)(name, index); 687 } 688 }, 689 690 // 691 // A Hexadecimal color 692 // 693 // #4F3C2F 694 // 695 // `rgb` and `hsl` colors are parsed through the `entities.call` parser. 696 // 697 color: function () { 698 var rgb; 699 700 if (input.charAt(i) === '#' && (rgb = $(/^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})/))) { 701 return new(tree.Color)(rgb[1]); 702 } 703 }, 704 705 // 706 // A Dimension, that is, a number and a unit 707 // 708 // 0.5em 95% 709 // 710 dimension: function () { 711 var value, c = input.charCodeAt(i); 712 if ((c > 57 || c < 45) || c === 47) return; 713 714 if (value = $(/^(-?\d*\.?\d+)(px|%|em|pc|ex|in|deg|s|ms|pt|cm|mm|rad|grad|turn)?/)) { 715 return new(tree.Dimension)(value[1], value[2]); 716 } 717 }, 718 719 // 720 // JavaScript code to be evaluated 721 // 722 // `window.location.href` 723 // 724 javascript: function () { 725 var str; 726 727 if (input.charAt(i) !== '`') { return } 728 729 if (str = $(/^`([^`]*)`/)) { 730 return new(tree.JavaScript)(str[1], i); 731 } 732 } 733 }, 734 735 // 736 // The variable part of a variable definition. Used in the `rule` parser 737 // 738 // @fink: 739 // 740 variable: function () { 741 var name; 742 743 if (input.charAt(i) === '@' && (name = $(/^(@[\w-]+)\s*:/))) { return name[1] } 744 }, 745 746 // 747 // A font size/line-height shorthand 748 // 749 // small/12px 750 // 751 // We need to peek first, or we'll match on keywords and dimensions 752 // 753 shorthand: function () { 754 var a, b; 755 756 if (! peek(/^[@\w.%-]+\/[@\w.-]+/)) return; 757 758 if ((a = $(this.entity)) && $('/') && (b = $(this.entity))) { 759 return new(tree.Shorthand)(a, b); 760 } 761 }, 762 763 // 764 // Mixins 765 // 766 mixin: { 767 // 768 // A Mixin call, with an optional argument list 769 // 770 // #mixins > .square(#fff); 771 // .rounded(4px, black); 772 // .button; 773 // 774 // The `while` loop is there because mixins can be 775 // namespaced, but we only support the child and descendant 776 // selector for now. 777 // 778 call: function () { 779 var elements = [], e, c, args, index = i, s = input.charAt(i); 780 781 if (s !== '.' && s !== '#') { return } 782 783 while (e = $(/^[#.][\w-]+/)) { 784 elements.push(new(tree.Element)(c, e)); 785 c = $('>'); 786 } 787 $('(') && (args = $(this.entities.arguments)) && $(')'); 788 789 if (elements.length > 0 && ($(';') || peek('}'))) { 790 return new(tree.mixin.Call)(elements, args, index); 791 } 792 }, 793 794 // 795 // A Mixin definition, with a list of parameters 796 // 797 // .rounded (@radius: 2px, @color) { 798 // ... 799 // } 800 // 801 // Until we have a finer grained state-machine, we have to 802 // do a look-ahead, to make sure we don't have a mixin call. 803 // See the `rule` function for more information. 804 // 805 // We start by matching `.rounded (`, and then proceed on to 806 // the argument list, which has optional default values. 807 // We store the parameters in `params`, with a `value` key, 808 // if there is a value, such as in the case of `@radius`. 809 // 810 // Once we've got our params list, and a closing `)`, we parse 811 // the `{...}` block. 812 // 813 definition: function () { 814 var name, params = [], match, ruleset, param, value; 815 816 if ((input.charAt(i) !== '.' && input.charAt(i) !== '#') || 817 peek(/^[^{]*(;|})/)) return; 818 819 if (match = $(/^([#.][\w-]+)\s*\(/)) { 820 name = match[1]; 821 822 while (param = $(this.entities.variable) || $(this.entities.literal) 823 || $(this.entities.keyword)) { 824 // Variable 825 if (param instanceof tree.Variable) { 826 if ($(':')) { 827 if (value = $(this.expression)) { 828 params.push({ name: param.name, value: value }); 829 } else { 830 throw new(Error)("Expected value"); 831 } 832 } else { 833 params.push({ name: param.name }); 834 } 835 } else { 836 params.push({ value: param }); 837 } 838 if (! $(',')) { break } 839 } 840 if (! $(')')) throw new(Error)("Expected )"); 841 842 ruleset = $(this.block); 843 844 if (ruleset) { 845 return new(tree.mixin.Definition)(name, params, ruleset); 846 } 847 } 848 } 849 }, 850 851 // 852 // Entities are the smallest recognized token, 853 // and can be found inside a rule's value. 854 // 855 entity: function () { 856 return $(this.entities.literal) || $(this.entities.variable) || $(this.entities.url) || 857 $(this.entities.call) || $(this.entities.keyword) || $(this.entities.javascript); 858 }, 859 860 // 861 // A Rule terminator. Note that we use `peek()` to check for '}', 862 // because the `block` rule will be expecting it, but we still need to make sure 863 // it's there, if ';' was ommitted. 864 // 865 end: function () { 866 return $(';') || peek('}'); 867 }, 868 869 // 870 // IE's alpha function 871 // 872 // alpha(opacity=88) 873 // 874 alpha: function () { 875 var value; 876 877 if (! $(/^opacity=/i)) return; 878 if (value = $(/^\d+/) || $(this.entities.variable)) { 879 if (! $(')')) throw new(Error)("missing closing ) for alpha()"); 880 return new(tree.Alpha)(value); 881 } 882 }, 883 884 // 885 // A Selector Element 886 // 887 // div 888 // + h1 889 // #socks 890 // input[type="text"] 891 // 892 // Elements are the building blocks for Selectors, 893 // they are made out of a `Combinator` (see combinator rule), 894 // and an element name, such as a tag a class, or `*`. 895 // 896 element: function () { 897 var e, t; 898 899 c = $(this.combinator); 900 e = $(/^[.#:]?[\w-]+/) || $('*') || $(this.attribute) || $(/^\([^)@]+\)/); 901 902 if (e) { return new(tree.Element)(c, e) } 903 }, 904 905 // 906 // Combinators combine elements together, in a Selector. 907 // 908 // Because our parser isn't white-space sensitive, special care 909 // has to be taken, when parsing the descendant combinator, ` `, 910 // as it's an empty space. We have to check the previous character 911 // in the input, to see if it's a ` ` character. More info on how 912 // we deal with this in *combinator.js*. 913 // 914 combinator: function () { 915 var match, c = input.charAt(i); 916 917 if (c === '>' || c === '&' || c === '+' || c === '~') { 918 i++; 919 while (input.charAt(i) === ' ') { i++ } 920 return new(tree.Combinator)(c); 921 } else if (c === ':' && input.charAt(i + 1) === ':') { 922 i += 2; 923 while (input.charAt(i) === ' ') { i++ } 924 return new(tree.Combinator)('::'); 925 } else if (input.charAt(i - 1) === ' ') { 926 return new(tree.Combinator)(" "); 927 } else { 928 return new(tree.Combinator)(null); 929 } 930 }, 931 932 // 933 // A CSS Selector 934 // 935 // .class > div + h1 936 // li a:hover 937 // 938 // Selectors are made out of one or more Elements, see above. 939 // 940 selector: function () { 941 var sel, e, elements = [], c, match; 942 943 while (e = $(this.element)) { 944 c = input.charAt(i); 945 elements.push(e) 946 if (c === '{' || c === '}' || c === ';' || c === ',') { break } 947 } 948 949 if (elements.length > 0) { return new(tree.Selector)(elements) } 950 }, 951 tag: function () { 952 return $(/^[a-zA-Z][a-zA-Z-]*[0-9]?/) || $('*'); 953 }, 954 attribute: function () { 955 var attr = '', key, val, op; 956 957 if (! $('[')) return; 958 959 if (key = $(/^[a-zA-Z-]+/) || $(this.entities.quoted)) { 960 if ((op = $(/^[|~*$^]?=/)) && 961 (val = $(this.entities.quoted) || $(/^[\w-]+/))) { 962 attr = [key, op, val.toCSS ? val.toCSS() : val].join(''); 963 } else { attr = key } 964 } 965 966 if (! $(']')) return; 967 968 if (attr) { return "[" + attr + "]" } 969 }, 970 971 // 972 // The `block` rule is used by `ruleset` and `mixin.definition`. 973 // It's a wrapper around the `primary` rule, with added `{}`. 974 // 975 block: function () { 976 var content; 977 978 if ($('{') && (content = $(this.primary)) && $('}')) { 979 return content; 980 } 981 }, 982 983 // 984 // div, .class, body > p {...} 985 // 986 ruleset: function () { 987 var selectors = [], s, rules, match; 988 save(); 989 990 if (match = /^([.#: \w-]+)[\s\n]*\{/.exec(chunks[j])) { 991 i += match[0].length - 1; 992 selectors = [new(tree.Selector)([new(tree.Element)(null, match[1])])]; 993 } else { 994 while (s = $(this.selector)) { 995 selectors.push(s); 996 if (! $(',')) { break } 997 } 998 if (s) $(this.comment); 999 } 1000 1001 if (selectors.length > 0 && (rules = $(this.block))) { 1002 return new(tree.Ruleset)(selectors, rules); 1003 } else { 1004 // Backtrack 1005 furthest = i; 1006 restore(); 1007 } 1008 }, 1009 rule: function () { 1010 var value, c = input.charAt(i), important; 1011 save(); 1012 1013 if (c === '.' || c === '#' || c === '&') { return } 1014 1015 if (name = $(this.variable) || $(this.property)) { 1016 if ((name.charAt(0) != '@') && (match = /^([^@+\/'"*`(;{}-]*);/.exec(chunks[j]))) { 1017 i += match[0].length - 1; 1018 value = new(tree.Anonymous)(match[1]); 1019 } else if (name === "font") { 1020 value = $(this.font); 1021 } else { 1022 value = $(this.value); 1023 } 1024 important = $(this.important); 1025 1026 if (value && $(this.end)) { 1027 return new(tree.Rule)(name, value, important, memo); 1028 } else { 1029 furthest = i; 1030 restore(); 1031 } 1032 } 1033 }, 1034 1035 // 1036 // An @import directive 1037 // 1038 // @import "lib"; 1039 // 1040 // Depending on our environemnt, importing is done differently: 1041 // In the browser, it's an XHR request, in Node, it would be a 1042 // file-system operation. The function used for importing is 1043 // stored in `import`, which we pass to the Import constructor. 1044 // 1045 "import": function () { 1046 var path; 1047 if ($(/^@import\s+/) && 1048 (path = $(this.entities.quoted) || $(this.entities.url)) && 1049 $(';')) { 1050 return new(tree.Import)(path, imports); 1051 } 1052 }, 1053 1054 // 1055 // A CSS Directive 1056 // 1057 // @charset "utf-8"; 1058 // 1059 directive: function () { 1060 var name, value, rules, types; 1061 1062 if (input.charAt(i) !== '@') return; 1063 1064 if (value = $(this['import'])) { 1065 return value; 1066 } else if (name = $(/^@media|@page/)) { 1067 types = $(/^[^{]+/).trim(); 1068 if (rules = $(this.block)) { 1069 return new(tree.Directive)(name + " " + types, rules); 1070 } 1071 } else if (name = $(/^@[-a-z]+/)) { 1072 if (name === '@font-face') { 1073 if (rules = $(this.block)) { 1074 return new(tree.Directive)(name, rules); 1075 } 1076 } else if ((value = $(this.entity)) && $(';')) { 1077 return new(tree.Directive)(name, value); 1078 } 1079 } 1080 }, 1081 font: function () { 1082 var value = [], expression = [], weight, shorthand, font, e; 1083 1084 while (e = $(this.shorthand) || $(this.entity)) { 1085 expression.push(e); 1086 } 1087 value.push(new(tree.Expression)(expression)); 1088 1089 if ($(',')) { 1090 while (e = $(this.expression)) { 1091 value.push(e); 1092 if (! $(',')) { break } 1093 } 1094 } 1095 return new(tree.Value)(value); 1096 }, 1097 1098 // 1099 // A Value is a comma-delimited list of Expressions 1100 // 1101 // font-family: Baskerville, Georgia, serif; 1102 // 1103 // In a Rule, a Value represents everything after the `:`, 1104 // and before the `;`. 1105 // 1106 value: function () { 1107 var e, expressions = [], important; 1108 1109 while (e = $(this.expression)) { 1110 expressions.push(e); 1111 if (! $(',')) { break } 1112 } 1113 1114 if (expressions.length > 0) { 1115 return new(tree.Value)(expressions); 1116 } 1117 }, 1118 important: function () { 1119 if (input.charAt(i) === '!') { 1120 return $(/^! *important/); 1121 } 1122 }, 1123 sub: function () { 1124 var e; 1125 1126 if ($('(') && (e = $(this.expression)) && $(')')) { 1127 return e; 1128 } 1129 }, 1130 multiplication: function () { 1131 var m, a, op, operation; 1132 if (m = $(this.operand)) { 1133 while ((op = ($('/') || $('*'))) && (a = $(this.operand))) { 1134 operation = new(tree.Operation)(op, [operation || m, a]); 1135 } 1136 return operation || m; 1137 } 1138 }, 1139 addition: function () { 1140 var m, a, op, operation; 1141 if (m = $(this.multiplication)) { 1142 while ((op = $(/^[-+]\s+/) || (input.charAt(i - 1) != ' ' && ($('+') || $('-')))) && 1143 (a = $(this.multiplication))) { 1144 operation = new(tree.Operation)(op, [operation || m, a]); 1145 } 1146 return operation || m; 1147 } 1148 }, 1149 1150 // 1151 // An operand is anything that can be part of an operation, 1152 // such as a Color, or a Variable 1153 // 1154 operand: function () { 1155 return $(this.sub) || $(this.entities.dimension) || 1156 $(this.entities.color) || $(this.entities.variable) || 1157 $(this.entities.call); 1158 }, 1159 1160 // 1161 // Expressions either represent mathematical operations, 1162 // or white-space delimited Entities. 1163 // 1164 // 1px solid black 1165 // @var * 2 1166 // 1167 expression: function () { 1168 var e, delim, entities = [], d; 1169 1170 while (e = $(this.addition) || $(this.entity)) { 1171 entities.push(e); 1172 } 1173 if (entities.length > 0) { 1174 return new(tree.Expression)(entities); 1175 } 1176 }, 1177 property: function () { 1178 var name; 1179 1180 if (name = $(/^(\*?-?[-a-z_0-9]+)\s*:/)) { 1181 return name[1]; 1182 } 1183 } 1184 } 1185 }; 1186 }; 1187 1188 if (typeof(window) !== 'undefined') { 1189 // 1190 // Used by `@import` directives 1191 // 1192 less.Parser.importer = function (path, paths, callback) { 1193 if (path.charAt(0) !== '/' && paths.length > 0) { 1194 path = paths[0] + path; 1195 } 1196 // We pass `true` as 3rd argument, to force the reload of the import. 1197 // This is so we can get the syntax tree as opposed to just the CSS output, 1198 // as we need this to evaluate the current stylesheet. 1199 loadStyleSheet({ href: path, title: path }, callback, true); 1200 }; 1201 } 1202 1203 (function (tree) { 1204 1205 tree.functions = { 1206 rgb: function (r, g, b) { 1207 return this.rgba(r, g, b, 1.0); 1208 }, 1209 rgba: function (r, g, b, a) { 1210 var rgb = [r, g, b].map(function (c) { return number(c) }), 1211 a = number(a); 1212 return new(tree.Color)(rgb, a); 1213 }, 1214 hsl: function (h, s, l) { 1215 return this.hsla(h, s, l, 1.0); 1216 }, 1217 hsla: function (h, s, l, a) { 1218 h = (number(h) % 360) / 360; 1219 s = number(s); l = number(l); a = number(a); 1220 1221 var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s; 1222 var m1 = l * 2 - m2; 1223 1224 return this.rgba(hue(h + 1/3) * 255, 1225 hue(h) * 255, 1226 hue(h - 1/3) * 255, 1227 a); 1228 1229 function hue(h) { 1230 h = h < 0 ? h + 1 : (h > 1 ? h - 1 : h); 1231 if (h * 6 < 1) return m1 + (m2 - m1) * h * 6; 1232 else if (h * 2 < 1) return m2; 1233 else if (h * 3 < 2) return m1 + (m2 - m1) * (2/3 - h) * 6; 1234 else return m1; 1235 } 1236 }, 1237 hue: function (color) { 1238 return new(tree.Dimension)(Math.round(color.toHSL().h)); 1239 }, 1240 saturation: function (color) { 1241 return new(tree.Dimension)(Math.round(color.toHSL().s * 100), '%'); 1242 }, 1243 lightness: function (color) { 1244 return new(tree.Dimension)(Math.round(color.toHSL().l * 100), '%'); 1245 }, 1246 alpha: function (color) { 1247 return new(tree.Dimension)(color.toHSL().a); 1248 }, 1249 saturate: function (color, amount) { 1250 var hsl = color.toHSL(); 1251 1252 hsl.s += amount.value / 100; 1253 hsl.s = clamp(hsl.s); 1254 return hsla(hsl); 1255 }, 1256 desaturate: function (color, amount) { 1257 var hsl = color.toHSL(); 1258 1259 hsl.s -= amount.value / 100; 1260 hsl.s = clamp(hsl.s); 1261 return hsla(hsl); 1262 }, 1263 lighten: function (color, amount) { 1264 var hsl = color.toHSL(); 1265 1266 hsl.l += amount.value / 100; 1267 hsl.l = clamp(hsl.l); 1268 return hsla(hsl); 1269 }, 1270 darken: function (color, amount) { 1271 var hsl = color.toHSL(); 1272 1273 hsl.l -= amount.value / 100; 1274 hsl.l = clamp(hsl.l); 1275 return hsla(hsl); 1276 }, 1277 spin: function (color, amount) { 1278 var hsl = color.toHSL(); 1279 var hue = (hsl.h + amount.value) % 360; 1280 1281 hsl.h = hue < 0 ? 360 + hue : hue; 1282 1283 return hsla(hsl); 1284 }, 1285 greyscale: function (color) { 1286 return this.desaturate(color, new(tree.Dimension)(100)); 1287 }, 1288 e: function (str) { 1289 return new(tree.Anonymous)(str instanceof tree.JavaScript ? str.evaluated : str); 1290 }, 1291 '%': function (quoted /* arg, arg, ...*/) { 1292 var args = Array.prototype.slice.call(arguments, 1), 1293 str = quoted.value; 1294 1295 for (var i = 0; i < args.length; i++) { 1296 str = str.replace(/%s/, args[i].value) 1297 .replace(/%[da]/, args[i].toCSS()); 1298 } 1299 str = str.replace(/%%/g, '%'); 1300 return new(tree.Quoted)('"' + str + '"', str); 1301 } 1302 }; 1303 1304 function hsla(hsla) { 1305 return tree.functions.hsla(hsla.h, hsla.s, hsla.l, hsla.a); 1306 } 1307 1308 function number(n) { 1309 if (n instanceof tree.Dimension) { 1310 return parseFloat(n.unit == '%' ? n.value / 100 : n.value); 1311 } else if (typeof(n) === 'number') { 1312 return n; 1313 } else { 1314 throw { 1315 error: "RuntimeError", 1316 message: "color functions take numbers as parameters" 1317 }; 1318 } 1319 } 1320 1321 function clamp(val) { 1322 return Math.min(1, Math.max(0, val)); 1323 } 1324 1325 })(require('less/tree')); 1326 (function (tree) { 1327 1328 tree.Alpha = function (val) { 1329 this.value = val; 1330 }; 1331 tree.Alpha.prototype = { 1332 toCSS: function () { 1333 return "alpha(opacity=" + 1334 (this.value.toCSS ? this.value.toCSS() : this.value) + ")"; 1335 }, 1336 eval: function () { return this } 1337 }; 1338 1339 })(require('less/tree')); 1340 (function (tree) { 1341 1342 tree.Anonymous = function (string) { 1343 this.value = string.value || string; 1344 }; 1345 tree.Anonymous.prototype = { 1346 toCSS: function () { 1347 return this.value; 1348 }, 1349 eval: function () { return this } 1350 }; 1351 1352 })(require('less/tree')); 1353 (function (tree) { 1354 1355 // 1356 // A function call node. 1357 // 1358 tree.Call = function (name, args) { 1359 this.name = name; 1360 this.args = args; 1361 }; 1362 tree.Call.prototype = { 1363 // 1364 // When evaluating a function call, 1365 // we either find the function in `tree.functions` [1], 1366 // in which case we call it, passing the evaluated arguments, 1367 // or we simply print it out as it appeared originally [2]. 1368 // 1369 // The *functions.js* file contains the built-in functions. 1370 // 1371 // The reason why we evaluate the arguments, is in the case where 1372 // we try to pass a variable to a function, like: `saturate(@color)`. 1373 // The function should receive the value, not the variable. 1374 // 1375 eval: function (env) { 1376 var args = this.args.map(function (a) { return a.eval(env) }); 1377 1378 if (this.name in tree.functions) { // 1. 1379 return tree.functions[this.name].apply(tree.functions, args); 1380 } else { // 2. 1381 return new(tree.Anonymous)(this.name + 1382 "(" + args.map(function (a) { return a.toCSS() }).join(', ') + ")"); 1383 } 1384 }, 1385 1386 toCSS: function (env) { 1387 return this.eval(env).toCSS(); 1388 } 1389 }; 1390 1391 })(require('less/tree')); 1392 (function (tree) { 1393 // 1394 // RGB Colors - #ff0014, #eee 1395 // 1396 tree.Color = function (rgb, a) { 1397 // 1398 // The end goal here, is to parse the arguments 1399 // into an integer triplet, such as `128, 255, 0` 1400 // 1401 // This facilitates operations and conversions. 1402 // 1403 if (Array.isArray(rgb)) { 1404 this.rgb = rgb; 1405 } else if (rgb.length == 6) { 1406 this.rgb = rgb.match(/.{2}/g).map(function (c) { 1407 return parseInt(c, 16); 1408 }); 1409 } else { 1410 this.rgb = rgb.split('').map(function (c) { 1411 return parseInt(c + c, 16); 1412 }); 1413 } 1414 this.alpha = typeof(a) === 'number' ? a : 1; 1415 }; 1416 tree.Color.prototype = { 1417 eval: function () { return this }, 1418 1419 // 1420 // If we have some transparency, the only way to represent it 1421 // is via `rgba`. Otherwise, we use the hex representation, 1422 // which has better compatibility with older browsers. 1423 // Values are capped between `0` and `255`, rounded and zero-padded. 1424 // 1425 toCSS: function () { 1426 if (this.alpha < 1.0) { 1427 return "rgba(" + this.rgb.map(function (c) { 1428 return Math.round(c); 1429 }).concat(this.alpha).join(', ') + ")"; 1430 } else { 1431 return '#' + this.rgb.map(function (i) { 1432 i = Math.round(i); 1433 i = (i > 255 ? 255 : (i < 0 ? 0 : i)).toString(16); 1434 return i.length === 1 ? '0' + i : i; 1435 }).join(''); 1436 } 1437 }, 1438 1439 // 1440 // Operations have to be done per-channel, if not, 1441 // channels will spill onto each other. Once we have 1442 // our result, in the form of an integer triplet, 1443 // we create a new Color node to hold the result. 1444 // 1445 operate: function (op, other) { 1446 var result = []; 1447 1448 if (! (other instanceof tree.Color)) { 1449 other = other.toColor(); 1450 } 1451 1452 for (var c = 0; c < 3; c++) { 1453 result[c] = tree.operate(op, this.rgb[c], other.rgb[c]); 1454 } 1455 return new(tree.Color)(result); 1456 }, 1457 1458 toHSL: function () { 1459 var r = this.rgb[0] / 255, 1460 g = this.rgb[1] / 255, 1461 b = this.rgb[2] / 255, 1462 a = this.alpha; 1463 1464 var max = Math.max(r, g, b), min = Math.min(r, g, b); 1465 var h, s, l = (max + min) / 2, d = max - min; 1466 1467 if (max === min) { 1468 h = s = 0; 1469 } else { 1470 s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 1471 1472 switch (max) { 1473 case r: h = (g - b) / d + (g < b ? 6 : 0); break; 1474 case g: h = (b - r) / d + 2; break; 1475 case b: h = (r - g) / d + 4; break; 1476 } 1477 h /= 6; 1478 } 1479 return { h: h * 360, s: s, l: l, a: a }; 1480 } 1481 }; 1482 1483 1484 })(require('less/tree')); 1485 (function (tree) { 1486 1487 tree.Comment = function (value, silent) { 1488 this.value = value; 1489 this.silent = !!silent; 1490 }; 1491 tree.Comment.prototype = { 1492 toCSS: function (env) { 1493 return env.compress ? '' : this.value; 1494 }, 1495 eval: function () { return this } 1496 }; 1497 1498 })(require('less/tree')); 1499 (function (tree) { 1500 1501 // 1502 // A number with a unit 1503 // 1504 tree.Dimension = function (value, unit) { 1505 this.value = parseFloat(value); 1506 this.unit = unit || null; 1507 }; 1508 1509 tree.Dimension.prototype = { 1510 eval: function () { return this }, 1511 toColor: function () { 1512 return new(tree.Color)([this.value, this.value, this.value]); 1513 }, 1514 toCSS: function () { 1515 var css = this.value + this.unit; 1516 return css; 1517 }, 1518 1519 // In an operation between two Dimensions, 1520 // we default to the first Dimension's unit, 1521 // so `1px + 2em` will yield `3px`. 1522 // In the future, we could implement some unit 1523 // conversions such that `100cm + 10mm` would yield 1524 // `101cm`. 1525 operate: function (op, other) { 1526 return new(tree.Dimension) 1527 (tree.operate(op, this.value, other.value), 1528 this.unit || other.unit); 1529 } 1530 }; 1531 1532 })(require('less/tree')); 1533 (function (tree) { 1534 1535 tree.Directive = function (name, value) { 1536 this.name = name; 1537 if (Array.isArray(value)) { 1538 this.ruleset = new(tree.Ruleset)([], value); 1539 } else { 1540 this.value = value; 1541 } 1542 }; 1543 tree.Directive.prototype = { 1544 toCSS: function (ctx, env) { 1545 if (this.ruleset) { 1546 this.ruleset.root = true; 1547 return this.name + (env.compress ? '{' : ' {\n ') + 1548 this.ruleset.toCSS(ctx, env).trim().replace(/\n/g, '\n ') + 1549 (env.compress ? '}': '\n}\n'); 1550 } else { 1551 return this.name + ' ' + this.value.toCSS() + ';\n'; 1552 } 1553 }, 1554 eval: function (env) { 1555 env.frames.unshift(this); 1556 this.ruleset = this.ruleset && this.ruleset.eval(env); 1557 env.frames.shift(); 1558 return this; 1559 }, 1560 variable: function (name) { return tree.Ruleset.prototype.variable.call(this.ruleset, name) }, 1561 find: function () { return tree.Ruleset.prototype.find.apply(this.ruleset, arguments) }, 1562 rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.ruleset) } 1563 }; 1564 1565 })(require('less/tree')); 1566 (function (tree) { 1567 1568 tree.Element = function (combinator, value) { 1569 this.combinator = combinator instanceof tree.Combinator ? 1570 combinator : new(tree.Combinator)(combinator); 1571 this.value = value.trim(); 1572 }; 1573 tree.Element.prototype.toCSS = function (env) { 1574 return this.combinator.toCSS(env || {}) + this.value; 1575 }; 1576 1577 tree.Combinator = function (value) { 1578 if (value === ' ') { 1579 this.value = ' '; 1580 } else { 1581 this.value = value ? value.trim() : ""; 1582 } 1583 }; 1584 tree.Combinator.prototype.toCSS = function (env) { 1585 return { 1586 '' : '', 1587 ' ' : ' ', 1588 '&' : '', 1589 ':' : ' :', 1590 '::': '::', 1591 '+' : env.compress ? '+' : ' + ', 1592 '~' : env.compress ? '~' : ' ~ ', 1593 '>' : env.compress ? '>' : ' > ' 1594 }[this.value]; 1595 }; 1596 1597 })(require('less/tree')); 1598 (function (tree) { 1599 1600 tree.Expression = function (value) { this.value = value }; 1601 tree.Expression.prototype = { 1602 eval: function (env) { 1603 if (this.value.length > 1) { 1604 return new(tree.Expression)(this.value.map(function (e) { 1605 return e.eval(env); 1606 })); 1607 } else { 1608 return this.value[0].eval(env); 1609 } 1610 }, 1611 toCSS: function (env) { 1612 return this.value.map(function (e) { 1613 return e.toCSS(env); 1614 }).join(' '); 1615 } 1616 }; 1617 1618 })(require('less/tree')); 1619 (function (tree) { 1620 // 1621 // CSS @import node 1622 // 1623 // The general strategy here is that we don't want to wait 1624 // for the parsing to be completed, before we start importing 1625 // the file. That's because in the context of a browser, 1626 // most of the time will be spent waiting for the server to respond. 1627 // 1628 // On creation, we push the import path to our import queue, though 1629 // `import,push`, we also pass it a callback, which it'll call once 1630 // the file has been fetched, and parsed. 1631 // 1632 tree.Import = function (path, imports) { 1633 var that = this; 1634 1635 this._path = path; 1636 1637 // The '.less' extension is optional 1638 if (path instanceof tree.Quoted) { 1639 this.path = /\.(le?|c)ss$/.test(path.value) ? path.value : path.value + '.less'; 1640 } else { 1641 this.path = path.value.value || path.value; 1642 } 1643 1644 this.css = /css$/.test(this.path); 1645 1646 // Only pre-compile .less files 1647 if (! this.css) { 1648 imports.push(this.path, function (root) { 1649 if (! root) { 1650 throw new(Error)("Error parsing " + that.path); 1651 } 1652 that.root = root; 1653 }); 1654 } 1655 }; 1656 1657 // 1658 // The actual import node doesn't return anything, when converted to CSS. 1659 // The reason is that it's used at the evaluation stage, so that the rules 1660 // it imports can be treated like any other rules. 1661 // 1662 // In `eval`, we make sure all Import nodes get evaluated, recursively, so 1663 // we end up with a flat structure, which can easily be imported in the parent 1664 // ruleset. 1665 // 1666 tree.Import.prototype = { 1667 toCSS: function () { 1668 if (this.css) { 1669 return "@import " + this._path.toCSS() + ';\n'; 1670 } else { 1671 return ""; 1672 } 1673 }, 1674 eval: function (env) { 1675 var ruleset; 1676 1677 if (this.css) { 1678 return this; 1679 } else { 1680 ruleset = new(tree.Ruleset)(null, this.root.rules.slice(0)); 1681 1682 for (var i = 0; i < ruleset.rules.length; i++) { 1683 if (ruleset.rules[i] instanceof tree.Import) { 1684 Array.prototype 1685 .splice 1686 .apply(ruleset.rules, 1687 [i, 1].concat(ruleset.rules[i].eval(env))); 1688 } 1689 } 1690 return ruleset.rules; 1691 } 1692 } 1693 }; 1694 1695 })(require('less/tree')); 1696 (function (tree) { 1697 1698 tree.JavaScript = function (string, index) { 1699 this.expression = string; 1700 this.index = index; 1701 }; 1702 tree.JavaScript.prototype = { 1703 toCSS: function () { 1704 return JSON.stringify(this.evaluated); 1705 }, 1706 eval: function (env) { 1707 var result, 1708 expression = new(Function)('return (' + this.expression + ')'), 1709 context = {}; 1710 1711 for (var k in env.frames[0].variables()) { 1712 context[k.slice(1)] = { 1713 value: env.frames[0].variables()[k].value, 1714 toJS: function () { 1715 return this.value.eval(env).toCSS(); 1716 } 1717 }; 1718 } 1719 1720 try { 1721 this.evaluated = expression.call(context); 1722 } catch (e) { 1723 throw { message: "JavaScript evaluation error: '" + e.name + ': ' + e.message + "'" , 1724 index: this.index }; 1725 } 1726 return this; 1727 } 1728 }; 1729 1730 })(require('less/tree')); 1731 1732 (function (tree) { 1733 1734 tree.Keyword = function (value) { this.value = value }; 1735 tree.Keyword.prototype = { 1736 eval: function () { return this }, 1737 toCSS: function () { return this.value } 1738 }; 1739 1740 })(require('less/tree')); 1741 (function (tree) { 1742 1743 tree.mixin = {}; 1744 tree.mixin.Call = function (elements, args, index) { 1745 this.selector = new(tree.Selector)(elements); 1746 this.arguments = args; 1747 this.index = index; 1748 }; 1749 tree.mixin.Call.prototype = { 1750 eval: function (env) { 1751 var mixins, rules = [], match = false; 1752 1753 for (var i = 0; i < env.frames.length; i++) { 1754 if ((mixins = env.frames[i].find(this.selector)).length > 0) { 1755 for (var m = 0; m < mixins.length; m++) { 1756 if (mixins[m].match(this.arguments, env)) { 1757 try { 1758 Array.prototype.push.apply( 1759 rules, mixins[m].eval(env, this.arguments).rules); 1760 match = true; 1761 } catch (e) { 1762 throw { message: e.message, index: e.index, stack: e.stack, call: this.index }; 1763 } 1764 } 1765 } 1766 if (match) { 1767 return rules; 1768 } else { 1769 throw { message: 'No matching definition was found for `' + 1770 this.selector.toCSS().trim() + '(' + 1771 this.arguments.map(function (a) { 1772 return a.toCSS(); 1773 }).join(', ') + ")`", 1774 index: this.index }; 1775 } 1776 } 1777 } 1778 throw { message: this.selector.toCSS().trim() + " is undefined", 1779 index: this.index }; 1780 } 1781 }; 1782 1783 tree.mixin.Definition = function (name, params, rules) { 1784 this.name = name; 1785 this.selectors = [new(tree.Selector)([new(tree.Element)(null, name)])]; 1786 this.params = params; 1787 this.arity = params.length; 1788 this.rules = rules; 1789 this._lookups = {}; 1790 this.required = params.reduce(function (count, p) { 1791 if (p.name && !p.value) { return count + 1 } 1792 else { return count } 1793 }, 0); 1794 this.parent = tree.Ruleset.prototype; 1795 this.frames = []; 1796 }; 1797 tree.mixin.Definition.prototype = { 1798 toCSS: function () { return "" }, 1799 variable: function (name) { return this.parent.variable.call(this, name) }, 1800 variables: function () { return this.parent.variables.call(this) }, 1801 find: function () { return this.parent.find.apply(this, arguments) }, 1802 rulesets: function () { return this.parent.rulesets.apply(this) }, 1803 1804 eval: function (env, args) { 1805 var frame = new(tree.Ruleset)(null, []), context; 1806 1807 for (var i = 0, val; i < this.params.length; i++) { 1808 if (this.params[i].name) { 1809 if (val = (args && args[i]) || this.params[i].value) { 1810 frame.rules.unshift(new(tree.Rule)(this.params[i].name, val.eval(env))); 1811 } else { 1812 throw { message: "wrong number of arguments for " + this.name + 1813 ' (' + args.length + ' for ' + this.arity + ')' }; 1814 } 1815 } 1816 } 1817 return new(tree.Ruleset)(null, this.rules.slice(0)).eval({ 1818 frames: [this, frame].concat(this.frames, env.frames) 1819 }); 1820 }, 1821 match: function (args, env) { 1822 var argsLength = (args && args.length) || 0, len; 1823 1824 if (argsLength < this.required) { return false } 1825 1826 len = Math.min(argsLength, this.arity); 1827 1828 for (var i = 0; i < len; i++) { 1829 if (!this.params[i].name) { 1830 if (args[i].eval(env).toCSS() != this.params[i].value.eval(env).toCSS()) { 1831 return false; 1832 } 1833 } 1834 } 1835 return true; 1836 } 1837 }; 1838 1839 })(require('less/tree')); 1840 (function (tree) { 1841 1842 tree.Operation = function (op, operands) { 1843 this.op = op.trim(); 1844 this.operands = operands; 1845 }; 1846 tree.Operation.prototype.eval = function (env) { 1847 var a = this.operands[0].eval(env), 1848 b = this.operands[1].eval(env), 1849 temp; 1850 1851 if (a instanceof tree.Dimension && b instanceof tree.Color) { 1852 if (this.op === '*' || this.op === '+') { 1853 temp = b, b = a, a = temp; 1854 } else { 1855 throw { name: "OperationError", 1856 message: "Can't substract or divide a color from a number" }; 1857 } 1858 } 1859 return a.operate(this.op, b); 1860 }; 1861 1862 tree.operate = function (op, a, b) { 1863 switch (op) { 1864 case '+': return a + b; 1865 case '-': return a - b; 1866 case '*': return a * b; 1867 case '/': return a / b; 1868 } 1869 }; 1870 1871 })(require('less/tree')); 1872 (function (tree) { 1873 1874 tree.Quoted = function (str, content) { 1875 this.value = content || ''; 1876 this.quote = str.charAt(0); 1877 }; 1878 tree.Quoted.prototype = { 1879 toCSS: function () { 1880 return this.quote + this.value + this.quote; 1881 }, 1882 eval: function () { 1883 return this; 1884 } 1885 }; 1886 1887 })(require('less/tree')); 1888 (function (tree) { 1889 1890 tree.Rule = function (name, value, important, index) { 1891 this.name = name; 1892 this.value = (value instanceof tree.Value) ? value : new(tree.Value)([value]); 1893 this.important = important ? ' ' + important.trim() : ''; 1894 this.index = index; 1895 1896 if (name.charAt(0) === '@') { 1897 this.variable = true; 1898 } else { this.variable = false } 1899 }; 1900 tree.Rule.prototype.toCSS = function (env) { 1901 if (this.variable) { return "" } 1902 else { 1903 return this.name + (env.compress ? ':' : ': ') + 1904 this.value.toCSS(env) + 1905 this.important + ";"; 1906 } 1907 }; 1908 1909 tree.Rule.prototype.eval = function (context) { 1910 return new(tree.Rule)(this.name, this.value.eval(context), this.important, this.index); 1911 }; 1912 1913 tree.Shorthand = function (a, b) { 1914 this.a = a; 1915 this.b = b; 1916 }; 1917 1918 tree.Shorthand.prototype = { 1919 toCSS: function (env) { 1920 return this.a.toCSS(env) + "/" + this.b.toCSS(env); 1921 }, 1922 eval: function () { return this } 1923 }; 1924 1925 })(require('less/tree')); 1926 (function (tree) { 1927 1928 tree.Ruleset = function (selectors, rules) { 1929 this.selectors = selectors; 1930 this.rules = rules; 1931 this._lookups = {}; 1932 }; 1933 tree.Ruleset.prototype = { 1934 eval: function (env) { 1935 var ruleset = new(tree.Ruleset)(this.selectors, this.rules.slice(0)); 1936 1937 ruleset.root = this.root; 1938 1939 // push the current ruleset to the frames stack 1940 env.frames.unshift(ruleset); 1941 1942 // Evaluate imports 1943 if (ruleset.root) { 1944 for (var i = 0; i < ruleset.rules.length; i++) { 1945 if (ruleset.rules[i] instanceof tree.Import) { 1946 Array.prototype.splice 1947 .apply(ruleset.rules, [i, 1].concat(ruleset.rules[i].eval(env))); 1948 } 1949 } 1950 } 1951 1952 // Store the frames around mixin definitions, 1953 // so they can be evaluated like closures when the time comes. 1954 for (var i = 0; i < ruleset.rules.length; i++) { 1955 if (ruleset.rules[i] instanceof tree.mixin.Definition) { 1956 ruleset.rules[i].frames = env.frames.slice(0); 1957 } 1958 } 1959 1960 // Evaluate mixin calls. 1961 for (var i = 0; i < ruleset.rules.length; i++) { 1962 if (ruleset.rules[i] instanceof tree.mixin.Call) { 1963 Array.prototype.splice 1964 .apply(ruleset.rules, [i, 1].concat(ruleset.rules[i].eval(env))); 1965 } 1966 } 1967 1968 // Evaluate everything else 1969 for (var i = 0, rule; i < ruleset.rules.length; i++) { 1970 rule = ruleset.rules[i]; 1971 1972 if (! (rule instanceof tree.mixin.Definition)) { 1973 ruleset.rules[i] = rule.eval ? rule.eval(env) : rule; 1974 } 1975 } 1976 1977 // Pop the stack 1978 env.frames.shift(); 1979 1980 return ruleset; 1981 }, 1982 match: function (args) { 1983 return !args || args.length === 0; 1984 }, 1985 variables: function () { 1986 if (this._variables) { return this._variables } 1987 else { 1988 return this._variables = this.rules.reduce(function (hash, r) { 1989 if (r instanceof tree.Rule && r.variable === true) { 1990 hash[r.name] = r; 1991 } 1992 return hash; 1993 }, {}); 1994 } 1995 }, 1996 variable: function (name) { 1997 return this.variables()[name]; 1998 }, 1999 rulesets: function () { 2000 if (this._rulesets) { return this._rulesets } 2001 else { 2002 return this._rulesets = this.rules.filter(function (r) { 2003 return (r instanceof tree.Ruleset) || (r instanceof tree.mixin.Definition); 2004 }); 2005 } 2006 }, 2007 find: function (selector, self) { 2008 self = self || this; 2009 var rules = [], rule, match, 2010 key = selector.toCSS(); 2011 2012 if (key in this._lookups) { return this._lookups[key] } 2013 2014 this.rulesets().forEach(function (rule) { 2015 if (rule !== self) { 2016 for (var j = 0; j < rule.selectors.length; j++) { 2017 if (match = selector.match(rule.selectors[j])) { 2018 if (selector.elements.length > 1) { 2019 Array.prototype.push.apply(rules, rule.find( 2020 new(tree.Selector)(selector.elements.slice(1)), self)); 2021 } else { 2022 rules.push(rule); 2023 } 2024 break; 2025 } 2026 } 2027 } 2028 }); 2029 return this._lookups[key] = rules; 2030 }, 2031 // 2032 // Entry point for code generation 2033 // 2034 // `context` holds an array of arrays. 2035 // 2036 toCSS: function (context, env) { 2037 var css = [], // The CSS output 2038 rules = [], // node.Rule instances 2039 rulesets = [], // node.Ruleset instances 2040 paths = [], // Current selectors 2041 selector, // The fully rendered selector 2042 rule; 2043 2044 if (! this.root) { 2045 if (context.length === 0) { 2046 paths = this.selectors.map(function (s) { return [s] }); 2047 } else { 2048 for (var s = 0; s < this.selectors.length; s++) { 2049 for (var c = 0; c < context.length; c++) { 2050 paths.push(context[c].concat([this.selectors[s]])); 2051 } 2052 } 2053 } 2054 } 2055 2056 // Compile rules and rulesets 2057 for (var i = 0; i < this.rules.length; i++) { 2058 rule = this.rules[i]; 2059 2060 if (rule.rules || (rule instanceof tree.Directive)) { 2061 rulesets.push(rule.toCSS(paths, env)); 2062 } else if (rule instanceof tree.Comment) { 2063 if (!rule.silent) { 2064 if (this.root) { 2065 rulesets.push(rule.toCSS(env)); 2066 } else { 2067 rules.push(rule.toCSS(env)); 2068 } 2069 } 2070 } else { 2071 if (rule.toCSS && !rule.variable) { 2072 rules.push(rule.toCSS(env)); 2073 } else if (rule.value && !rule.variable) { 2074 rules.push(rule.value.toString()); 2075 } 2076 } 2077 } 2078 2079 rulesets = rulesets.join(''); 2080 2081 // If this is the root node, we don't render 2082 // a selector, or {}. 2083 // Otherwise, only output if this ruleset has rules. 2084 if (this.root) { 2085 css.push(rules.join(env.compress ? '' : '\n')); 2086 } else { 2087 if (rules.length > 0) { 2088 selector = paths.map(function (p) { 2089 return p.map(function (s) { 2090 return s.toCSS(env); 2091 }).join('').trim(); 2092 }).join(env.compress ? ',' : (paths.length > 3 ? ',\n' : ', ')); 2093 css.push(selector, 2094 (env.compress ? '{' : ' {\n ') + 2095 rules.join(env.compress ? '' : '\n ') + 2096 (env.compress ? '}' : '\n}\n')); 2097 } 2098 } 2099 css.push(rulesets); 2100 2101 return css.join('') + (env.compress ? '\n' : ''); 2102 } 2103 }; 2104 })(require('less/tree')); 2105 (function (tree) { 2106 2107 tree.Selector = function (elements) { 2108 this.elements = elements; 2109 if (this.elements[0].combinator.value === "") { 2110 this.elements[0].combinator.value = ' '; 2111 } 2112 }; 2113 tree.Selector.prototype.match = function (other) { 2114 if (this.elements[0].value === other.elements[0].value) { 2115 return true; 2116 } else { 2117 return false; 2118 } 2119 }; 2120 tree.Selector.prototype.toCSS = function (env) { 2121 if (this._css) { return this._css } 2122 2123 return this._css = this.elements.map(function (e) { 2124 if (typeof(e) === 'string') { 2125 return ' ' + e.trim(); 2126 } else { 2127 return e.toCSS(env); 2128 } 2129 }).join(''); 2130 }; 2131 2132 })(require('less/tree')); 2133 (function (tree) { 2134 2135 tree.URL = function (val, paths) { 2136 // Add the base path if the URL is relative and we are in the browser 2137 if (!/^(?:https?:\/|file:\/)?\//.test(val.value) && paths.length > 0 && typeof(window) !== 'undefined') { 2138 val.value = paths[0] + (val.value.charAt(0) === '/' ? val.value.slice(1) : val.value); 2139 } 2140 this.value = val; 2141 this.paths = paths; 2142 }; 2143 tree.URL.prototype = { 2144 toCSS: function () { 2145 return "url(" + this.value.toCSS() + ")"; 2146 }, 2147 eval: function (ctx) { 2148 return new(tree.URL)(this.value.eval(ctx), this.paths); 2149 } 2150 }; 2151 2152 })(require('less/tree')); 2153 (function (tree) { 2154 2155 tree.Value = function (value) { 2156 this.value = value; 2157 this.is = 'value'; 2158 }; 2159 tree.Value.prototype = { 2160 eval: function (env) { 2161 if (this.value.length === 1) { 2162 return this.value[0].eval(env); 2163 } else { 2164 return new(tree.Value)(this.value.map(function (v) { 2165 return v.eval(env); 2166 })); 2167 } 2168 }, 2169 toCSS: function (env) { 2170 return this.value.map(function (e) { 2171 return e.toCSS(env); 2172 }).join(env.compress ? ',' : ', '); 2173 } 2174 }; 2175 2176 })(require('less/tree')); 2177 (function (tree) { 2178 2179 tree.Variable = function (name, index) { this.name = name, this.index = index }; 2180 tree.Variable.prototype = { 2181 eval: function (env) { 2182 var variable, v, name = this.name; 2183 2184 if (variable = tree.find(env.frames, function (frame) { 2185 if (v = frame.variable(name)) { 2186 return v.value.eval(env); 2187 } 2188 })) { return variable } 2189 else { 2190 throw { message: "variable " + this.name + " is undefined", 2191 index: this.index }; 2192 } 2193 } 2194 }; 2195 2196 })(require('less/tree')); 2197 require('less/tree').find = function (obj, fun) { 2198 for (var i = 0, r; i < obj.length; i++) { 2199 if (r = fun.call(obj, obj[i])) { return r } 2200 } 2201 return null; 2202 }; 2203 // 2204 // browser.js - client-side engine 2205 // 2206 2207 var isFileProtocol = (location.protocol === 'file:' || 2208 location.protocol === 'chrome:' || 2209 location.protocol === 'resource:'); 2210 2211 less.env = less.env || 2212 location.hostname == '127.0.0.1' || 2213 location.hostname == '0.0.0.0' || 2214 location.hostname == 'localhost' || 2215 location.port.length > 0 || 2216 isFileProtocol ? 'development' 2217 : 'production'; 2218 2219 // Load styles asynchronously (default: false) 2220 // 2221 // This is set to `false` by default, so that the body 2222 // doesn't start loading before the stylesheets are parsed. 2223 // Setting this to `true` can result in flickering. 2224 // 2225 less.async = false; 2226 2227 // Interval between watch polls 2228 less.poll = less.poll || (isFileProtocol ? 1000 : 1500); 2229 2230 // 2231 // Watch mode 2232 // 2233 less.watch = function () { return this.watchMode = true }; 2234 less.unwatch = function () { return this.watchMode = false }; 2235 2236 if (less.env === 'development') { 2237 less.optimization = 0; 2238 2239 if (/!watch/.test(location.hash)) { 2240 less.watch(); 2241 } 2242 less.watchTimer = setInterval(function () { 2243 if (less.watchMode) { 2244 loadStyleSheets(function (root, sheet, env) { 2245 if (root) { 2246 createCSS(root.toCSS(), sheet, env.lastModified); 2247 } 2248 }); 2249 } 2250 }, less.poll); 2251 } else { 2252 less.optimization = 3; 2253 } 2254 2255 var cache; 2256 2257 try { 2258 cache = (typeof(window.localStorage) === 'undefined') ? null : window.localStorage; 2259 } catch (_) { 2260 cache = null; 2261 } 2262 2263 // 2264 // Get all <link> tags with the 'rel' attribute set to "stylesheet/less" 2265 // 2266 var links = document.getElementsByTagName('link'); 2267 var typePattern = /^text\/(x-)?less$/; 2268 2269 less.sheets = []; 2270 2271 for (var i = 0; i < links.length; i++) { 2272 if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) && 2273 (links[i].type.match(typePattern)))) { 2274 less.sheets.push(links[i]); 2275 } 2276 } 2277 2278 2279 less.refresh = function (reload) { 2280 var startTime = endTime = new(Date); 2281 2282 loadStyleSheets(function (root, sheet, env) { 2283 if (env.local) { 2284 log("loading " + sheet.href + " from cache."); 2285 } else { 2286 log("parsed " + sheet.href + " successfully."); 2287 createCSS(root.toCSS(), sheet, env.lastModified); 2288 } 2289 log("css for " + sheet.href + " generated in " + (new(Date) - endTime) + 'ms'); 2290 (env.remaining === 0) && log("css generated in " + (new(Date) - startTime) + 'ms'); 2291 endTime = new(Date); 2292 }, reload); 2293 2294 loadStyles(); 2295 }; 2296 less.refreshStyles = loadStyles; 2297 2298 less.refresh(less.env === 'development'); 2299 2300 function loadStyles() { 2301 var styles = document.getElementsByTagName('style'); 2302 for (var i = 0; i < styles.length; i++) { 2303 if (styles[i].type.match(typePattern)) { 2304 new(less.Parser)().parse(styles[i].innerHTML || '', function (e, tree) { 2305 styles[i].type = 'text/css'; 2306 styles[i].innerHTML = tree.toCSS(); 2307 }); 2308 } 2309 } 2310 } 2311 2312 function loadStyleSheets(callback, reload) { 2313 for (var i = 0; i < less.sheets.length; i++) { 2314 loadStyleSheet(less.sheets[i], callback, reload, less.sheets.length - (i + 1)); 2315 } 2316 } 2317 2318 function loadStyleSheet(sheet, callback, reload, remaining) { 2319 var url = window.location.href; 2320 var href = sheet.href.replace(/\?.*$/, ''); 2321 var css = cache && cache.getItem(href); 2322 var timestamp = cache && cache.getItem(href + ':timestamp'); 2323 var styles = { css: css, timestamp: timestamp }; 2324 2325 // Stylesheets in IE don't always return the full path 2326 if (! /^(https?|file):/.test(href)) { 2327 href = url.slice(0, url.lastIndexOf('/') + 1) + href; 2328 } 2329 2330 xhr(sheet.href, function (data, lastModified) { 2331 if (!reload && styles && 2332 (new(Date)(lastModified).valueOf() === 2333 new(Date)(styles.timestamp).valueOf())) { 2334 // Use local copy 2335 createCSS(styles.css, sheet); 2336 callback(null, sheet, { local: true, remaining: remaining }); 2337 } else { 2338 // Use remote copy (re-parse) 2339 try { 2340 new(less.Parser)({ 2341 optimization: less.optimization, 2342 paths: [href.replace(/[\w\.-]+$/, '')] 2343 }).parse(data, function (e, root) { 2344 if (e) { return error(e, href) } 2345 try { 2346 callback(root, sheet, { local: false, lastModified: lastModified, remaining: remaining }); 2347 removeNode(document.getElementById('less-error-message:' + extractId(href))); 2348 } catch (e) { 2349 error(e, href); 2350 } 2351 }); 2352 } catch (e) { 2353 error(e, href); 2354 } 2355 } 2356 }, function (status, url) { 2357 throw new(Error)("Couldn't load " + url+ " (" + status + ")"); 2358 }); 2359 } 2360 2361 function extractId(href) { 2362 return href.replace(/^[a-z]+:\/\/?[^\/]+/, '' ) // Remove protocol & domain 2363 .replace(/^\//, '' ) // Remove root / 2364 .replace(/\?.*$/, '' ) // Remove query 2365 .replace(/\.[^\.\/]+$/, '' ) // Remove file extension 2366 .replace(/[^\.\w-]+/g, '-') // Replace illegal characters 2367 .replace(/\./g, ':'); // Replace dots with colons(for valid id) 2368 } 2369 2370 function createCSS(styles, sheet, lastModified) { 2371 var css; 2372 2373 // Strip the query-string 2374 var href = sheet.href ? sheet.href.replace(/\?.*$/, '') : ''; 2375 2376 // If there is no title set, use the filename, minus the extension 2377 var id = 'less:' + (sheet.title || extractId(href)); 2378 2379 // If the stylesheet doesn't exist, create a new node 2380 if ((css = document.getElementById(id)) === null) { 2381 css = document.createElement('style'); 2382 css.type = 'text/css'; 2383 css.media = sheet.media || 'screen'; 2384 css.id = id; 2385 document.getElementsByTagName('head')[0].appendChild(css); 2386 } 2387 2388 if (css.styleSheet) { // IE 2389 try { 2390 css.styleSheet.cssText = styles; 2391 } catch (e) { 2392 throw new(Error)("Couldn't reassign styleSheet.cssText."); 2393 } 2394 } else { 2395 (function (node) { 2396 if (css.childNodes.length > 0) { 2397 if (css.firstChild.nodeValue !== node.nodeValue) { 2398 css.replaceChild(node, css.firstChild); 2399 } 2400 } else { 2401 css.appendChild(node); 2402 } 2403 })(document.createTextNode(styles)); 2404 } 2405 2406 // Don't update the local store if the file wasn't modified 2407 if (lastModified && cache) { 2408 log('saving ' + href + ' to cache.'); 2409 cache.setItem(href, styles); 2410 cache.setItem(href + ':timestamp', lastModified); 2411 } 2412 } 2413 2414 function xhr(url, callback, errback) { 2415 var xhr = getXMLHttpRequest(); 2416 var async = isFileProtocol ? false : less.async; 2417 2418 if (typeof(xhr.overrideMimeType) === 'function') { 2419 xhr.overrideMimeType('text/css'); 2420 } 2421 xhr.open('GET', url, async); 2422 xhr.send(null); 2423 2424 if (isFileProtocol) { 2425 if (xhr.status === 0) { 2426 callback(xhr.responseText); 2427 } else { 2428 errback(xhr.status); 2429 } 2430 } else if (async) { 2431 xhr.onreadystatechange = function () { 2432 if (xhr.readyState == 4) { 2433 handleResponse(xhr, callback, errback); 2434 } 2435 }; 2436 } else { 2437 handleResponse(xhr, callback, errback); 2438 } 2439 2440 function handleResponse(xhr, callback, errback) { 2441 if (xhr.status >= 200 && xhr.status < 300) { 2442 callback(xhr.responseText, 2443 xhr.getResponseHeader("Last-Modified")); 2444 } else if (typeof(errback) === 'function') { 2445 errback(xhr.status, url); 2446 } 2447 } 2448 } 2449 2450 function getXMLHttpRequest() { 2451 if (window.XMLHttpRequest) { 2452 return new(XMLHttpRequest); 2453 } else { 2454 try { 2455 return new(ActiveXObject)("MSXML2.XMLHTTP.3.0"); 2456 } catch (e) { 2457 log("browser doesn't support AJAX."); 2458 return null; 2459 } 2460 } 2461 } 2462 2463 function removeNode(node) { 2464 return node && node.parentNode.removeChild(node); 2465 } 2466 2467 function log(str) { 2468 if (less.env == 'development' && typeof(console) !== "undefined") { console.log('less: ' + str) } 2469 } 2470 2471 function error(e, href) { 2472 var id = 'less-error-message:' + extractId(href); 2473 2474 var template = ['<ul>', 2475 '<li><label>[-1]</label><pre class="ctx">{0}</pre></li>', 2476 '<li><label>[0]</label><pre>{current}</pre></li>', 2477 '<li><label>[1]</label><pre class="ctx">{2}</pre></li>', 2478 '</ul>'].join('\n'); 2479 2480 var elem = document.createElement('div'), timer, content; 2481 2482 elem.id = id; 2483 elem.className = "less-error-message"; 2484 2485 content = '<h3>' + (e.message || 'There is an error in your .less file') + 2486 '</h3>' + '<p><a href="' + href + '">' + href + "</a> "; 2487 2488 if (e.extract) { 2489 content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':</p>' + 2490 template.replace(/\[(-?\d)\]/g, function (_, i) { 2491 return (parseInt(e.line) + parseInt(i)) || ''; 2492 }).replace(/\{(\d)\}/g, function (_, i) { 2493 return e.extract[parseInt(i)] || ''; 2494 }).replace(/\{current\}/, e.extract[1].slice(0, e.column) + '<span class="error">' + 2495 e.extract[1].slice(e.column) + '</span>'); 2496 } 2497 elem.innerHTML = content; 2498 2499 // CSS for error messages 2500 createCSS([ 2501 '.less-error-message ul, .less-error-message li {', 2502 'list-style-type: none;', 2503 'margin-right: 15px;', 2504 'padding: 4px 0;', 2505 'margin: 0;', 2506 '}', 2507 '.less-error-message label {', 2508 'font-size: 12px;', 2509 'margin-right: 15px;', 2510 'padding: 4px 0;', 2511 'color: #cc7777;', 2512 '}', 2513 '.less-error-message pre {', 2514 'color: #ee4444;', 2515 'padding: 4px 0;', 2516 'margin: 0;', 2517 'display: inline-block;', 2518 '}', 2519 '.less-error-message pre.ctx {', 2520 'color: #dd4444;', 2521 '}', 2522 '.less-error-message h3 {', 2523 'font-size: 20px;', 2524 'font-weight: bold;', 2525 'padding: 15px 0 5px 0;', 2526 'margin: 0;', 2527 '}', 2528 '.less-error-message a {', 2529 'color: #10a', 2530 '}', 2531 '.less-error-message .error {', 2532 'color: red;', 2533 'font-weight: bold;', 2534 'padding-bottom: 2px;', 2535 'border-bottom: 1px dashed red;', 2536 '}' 2537 ].join('\n'), { title: 'error-message' }); 2538 2539 elem.style.cssText = [ 2540 "font-family: Arial, sans-serif", 2541 "border: 1px solid #e00", 2542 "background-color: #eee", 2543 "border-radius: 5px", 2544 "-webkit-border-radius: 5px", 2545 "-moz-border-radius: 5px", 2546 "color: #e00", 2547 "padding: 15px", 2548 "margin-bottom: 15px" 2549 ].join(';'); 2550 2551 if (less.env == 'development') { 2552 timer = setInterval(function () { 2553 if (document.body) { 2554 if (document.getElementById(id)) { 2555 document.body.replaceChild(elem, document.getElementById(id)); 2556 } else { 2557 document.body.insertBefore(elem, document.body.firstChild); 2558 } 2559 clearInterval(timer); 2560 } 2561 }, 10); 2562 } 2563 } 2564 2565 })(window);