less 2.3.3 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +5 -0
  3. data/less.gemspec +1 -1
  4. data/lib/less/js/.gitattributes +9 -0
  5. data/lib/less/js/.gitignore +1 -0
  6. data/lib/less/js/.npmignore +1 -1
  7. data/lib/less/js/CHANGELOG.md +68 -0
  8. data/lib/less/js/CONTRIBUTING.md +33 -34
  9. data/lib/less/js/Makefile +24 -9
  10. data/lib/less/js/README.md +2 -2
  11. data/lib/less/js/bin/lessc +102 -25
  12. data/lib/less/js/build/amd.js +1 -1
  13. data/lib/less/js/build/header.js +9 -7
  14. data/lib/less/js/dist/less-1.3.3.js +2 -2
  15. data/lib/less/js/dist/less-1.3.3.min.js +2 -2
  16. data/lib/less/js/dist/less-1.4.0-beta.js +5830 -0
  17. data/lib/less/js/dist/less-1.4.0-beta.min.js +11 -0
  18. data/lib/less/js/dist/less-1.4.0.js +5830 -0
  19. data/lib/less/js/dist/less-1.4.0.min.js +11 -0
  20. data/lib/less/js/dist/less-1.4.1.js +5837 -0
  21. data/lib/less/js/dist/less-1.4.1.min.js +11 -0
  22. data/lib/less/js/dist/less-1.4.2.js +5837 -0
  23. data/lib/less/js/dist/less-1.4.2.min.js +11 -0
  24. data/lib/less/js/dist/less-rhino-1.4.0.js +4273 -0
  25. data/lib/less/js/lib/less/browser.js +131 -101
  26. data/lib/less/js/lib/less/env.js +105 -0
  27. data/lib/less/js/lib/less/extend-visitor.js +391 -0
  28. data/lib/less/js/lib/less/functions.js +174 -19
  29. data/lib/less/js/lib/less/import-visitor.js +107 -0
  30. data/lib/less/js/lib/less/index.js +70 -63
  31. data/lib/less/js/lib/less/join-selector-visitor.js +37 -0
  32. data/lib/less/js/lib/less/lessc_helper.js +13 -4
  33. data/lib/less/js/lib/less/parser.js +353 -264
  34. data/lib/less/js/lib/less/rhino.js +5 -2
  35. data/lib/less/js/lib/less/tree.js +1 -1
  36. data/lib/less/js/lib/less/tree/alpha.js +7 -3
  37. data/lib/less/js/lib/less/tree/anonymous.js +1 -0
  38. data/lib/less/js/lib/less/tree/assignment.js +4 -0
  39. data/lib/less/js/lib/less/tree/call.js +14 -8
  40. data/lib/less/js/lib/less/tree/color.js +50 -5
  41. data/lib/less/js/lib/less/tree/comment.js +1 -0
  42. data/lib/less/js/lib/less/tree/condition.js +35 -28
  43. data/lib/less/js/lib/less/tree/dimension.js +270 -16
  44. data/lib/less/js/lib/less/tree/directive.js +7 -2
  45. data/lib/less/js/lib/less/tree/element.js +57 -21
  46. data/lib/less/js/lib/less/tree/expression.js +29 -4
  47. data/lib/less/js/lib/less/tree/extend.js +43 -0
  48. data/lib/less/js/lib/less/tree/import.js +49 -28
  49. data/lib/less/js/lib/less/tree/javascript.js +1 -0
  50. data/lib/less/js/lib/less/tree/keyword.js +3 -2
  51. data/lib/less/js/lib/less/tree/media.js +20 -4
  52. data/lib/less/js/lib/less/tree/mixin.js +38 -18
  53. data/lib/less/js/lib/less/tree/negative.js +22 -0
  54. data/lib/less/js/lib/less/tree/operation.js +32 -17
  55. data/lib/less/js/lib/less/tree/paren.js +5 -1
  56. data/lib/less/js/lib/less/tree/quoted.js +5 -3
  57. data/lib/less/js/lib/less/tree/rule.js +44 -31
  58. data/lib/less/js/lib/less/tree/ruleset.js +50 -23
  59. data/lib/less/js/lib/less/tree/selector.js +49 -39
  60. data/lib/less/js/lib/less/tree/unicode-descriptor.js +1 -0
  61. data/lib/less/js/lib/less/tree/url.js +9 -5
  62. data/lib/less/js/lib/less/tree/value.js +4 -1
  63. data/lib/less/js/lib/less/tree/variable.js +4 -3
  64. data/lib/less/js/lib/less/visitor.js +54 -0
  65. data/lib/less/js/package.json +69 -19
  66. data/lib/less/js/test/browser-test-prepare.js +23 -6
  67. data/lib/less/js/test/browser/common.js +55 -3
  68. data/lib/less/js/test/browser/css/urls.css +13 -0
  69. data/lib/less/js/test/browser/less/relative-urls/urls.less +1 -1
  70. data/lib/less/js/test/browser/less/urls.less +16 -0
  71. data/lib/less/js/test/browser/phantom-runner.js +7 -5
  72. data/lib/less/js/test/browser/runner-browser.js +5 -1
  73. data/lib/less/js/test/browser/runner-errors.js +5 -0
  74. data/lib/less/js/test/browser/runner-legacy.js +6 -0
  75. data/lib/less/js/test/browser/runner-production.js +7 -0
  76. data/lib/less/js/test/browser/template.htm +6 -6
  77. data/lib/less/js/test/css/comments.css +1 -0
  78. data/lib/less/js/test/css/compression/compression.css +2 -0
  79. data/lib/less/js/test/css/css-3.css +4 -0
  80. data/lib/less/js/test/css/css.css +9 -3
  81. data/lib/less/js/test/css/extend-chaining.css +72 -0
  82. data/lib/less/js/test/css/extend-clearfix.css +19 -0
  83. data/lib/less/js/test/css/extend-exact.css +37 -0
  84. data/lib/less/js/test/css/extend-media.css +24 -0
  85. data/lib/less/js/test/css/extend-nest.css +57 -0
  86. data/lib/less/js/test/css/extend-selector.css +72 -0
  87. data/lib/less/js/test/css/extend.css +76 -0
  88. data/lib/less/js/test/css/functions.css +28 -0
  89. data/lib/less/js/test/css/import-interpolation.css +6 -0
  90. data/lib/less/js/test/css/import.css +18 -1
  91. data/lib/less/js/test/css/legacy/legacy.css +7 -0
  92. data/lib/less/js/test/css/media.css +9 -1
  93. data/lib/less/js/test/css/mixins-args.css +18 -0
  94. data/lib/less/js/test/css/mixins-guards.css +5 -0
  95. data/lib/less/js/test/css/parens.css +18 -5
  96. data/lib/less/js/test/css/selectors.css +14 -6
  97. data/lib/less/js/test/css/urls.css +17 -0
  98. data/lib/less/js/test/css/variables.css +22 -3
  99. data/lib/less/js/test/data/data-uri-fail.png +0 -0
  100. data/lib/less/js/test/data/image.jpg +0 -0
  101. data/lib/less/js/test/data/page.html +1 -0
  102. data/lib/less/js/test/less-test.js +41 -9
  103. data/lib/less/js/test/less/colors.less +4 -4
  104. data/lib/less/js/test/less/comments.less +2 -2
  105. data/lib/less/js/test/less/compression/compression.less +16 -0
  106. data/lib/less/js/test/less/css-3.less +5 -1
  107. data/lib/less/js/test/less/css.less +9 -3
  108. data/lib/less/js/test/less/errors/add-mixed-units.less +3 -0
  109. data/lib/less/js/test/less/errors/add-mixed-units.txt +2 -0
  110. data/lib/less/js/test/less/errors/add-mixed-units2.less +3 -0
  111. data/lib/less/js/test/less/errors/add-mixed-units2.txt +2 -0
  112. data/lib/less/js/test/less/errors/bad-variable-declaration1.txt +1 -1
  113. data/lib/less/js/test/less/errors/color-operation-error.less +3 -0
  114. data/lib/less/js/test/less/errors/color-operation-error.txt +2 -0
  115. data/lib/less/js/test/less/errors/comment-in-selector.txt +1 -1
  116. data/lib/less/js/test/less/errors/divide-mixed-units.less +3 -0
  117. data/lib/less/js/test/less/errors/divide-mixed-units.txt +4 -0
  118. data/lib/less/js/test/less/errors/extend-no-selector.less +3 -0
  119. data/lib/less/js/test/less/errors/extend-no-selector.txt +3 -0
  120. data/lib/less/js/test/less/errors/extend-not-at-end.less +3 -0
  121. data/lib/less/js/test/less/errors/extend-not-at-end.txt +3 -0
  122. data/lib/less/js/test/less/errors/import-missing.less +5 -0
  123. data/lib/less/js/test/less/errors/import-missing.txt +3 -3
  124. data/lib/less/js/test/less/errors/import-no-semi.txt +1 -1
  125. data/lib/less/js/test/less/errors/import-subfolder1.txt +1 -1
  126. data/lib/less/js/test/less/errors/import-subfolder2.txt +1 -1
  127. data/lib/less/js/test/less/errors/javascript-error.txt +1 -1
  128. data/lib/less/js/test/less/errors/mixed-mixin-definition-args-1.txt +1 -1
  129. data/lib/less/js/test/less/errors/mixed-mixin-definition-args-2.txt +1 -1
  130. data/lib/less/js/test/less/errors/mixin-not-defined.txt +1 -1
  131. data/lib/less/js/test/less/errors/mixin-not-matched.txt +1 -1
  132. data/lib/less/js/test/less/errors/mixin-not-matched2.txt +1 -1
  133. data/lib/less/js/test/less/errors/multiply-mixed-units.less +7 -0
  134. data/lib/less/js/test/less/errors/multiply-mixed-units.txt +4 -0
  135. data/lib/less/js/test/less/errors/parens-error-1.less +3 -0
  136. data/lib/less/js/test/less/errors/parens-error-1.txt +4 -0
  137. data/lib/less/js/test/less/errors/parens-error-2.less +3 -0
  138. data/lib/less/js/test/less/errors/parens-error-2.txt +4 -0
  139. data/lib/less/js/test/less/errors/parens-error-3.less +3 -0
  140. data/lib/less/js/test/less/errors/parens-error-3.txt +4 -0
  141. data/lib/less/js/test/less/errors/parse-error-curly-bracket.txt +1 -1
  142. data/lib/less/js/test/less/errors/parse-error-missing-bracket.txt +2 -1
  143. data/lib/less/js/test/less/errors/parse-error-with-import.txt +1 -1
  144. data/lib/less/js/test/less/errors/property-ie5-hack.txt +1 -1
  145. data/lib/less/js/test/less/errors/property-in-root.less +4 -0
  146. data/lib/less/js/test/less/errors/property-in-root.txt +4 -0
  147. data/lib/less/js/test/less/errors/property-in-root2.less +1 -0
  148. data/lib/less/js/test/less/errors/property-in-root2.txt +4 -0
  149. data/lib/less/js/test/less/errors/property-in-root3.less +4 -0
  150. data/lib/less/js/test/less/errors/property-in-root3.txt +3 -0
  151. data/lib/less/js/test/less/errors/recursive-variable.txt +1 -1
  152. data/lib/less/js/test/less/extend-chaining.less +79 -0
  153. data/lib/less/js/test/less/extend-clearfix.less +19 -0
  154. data/lib/less/js/test/less/extend-exact.less +46 -0
  155. data/lib/less/js/test/less/extend-media.less +24 -0
  156. data/lib/less/js/test/less/extend-nest.less +65 -0
  157. data/lib/less/js/test/less/extend-selector.less +84 -0
  158. data/lib/less/js/test/less/extend.less +81 -0
  159. data/lib/less/js/test/less/functions.less +37 -6
  160. data/lib/less/js/test/less/import-interpolation.less +8 -0
  161. data/lib/less/js/test/less/import-once.less +4 -4
  162. data/lib/less/js/test/less/import.less +11 -2
  163. data/lib/less/js/test/less/import/deeper/import-once-test-a.less +1 -1
  164. data/lib/less/js/test/less/import/import-interpolation.less +1 -0
  165. data/lib/less/js/test/less/import/import-interpolation2.less +5 -0
  166. data/lib/less/js/test/less/javascript.less +1 -1
  167. data/lib/less/js/test/less/legacy/legacy.less +7 -0
  168. data/lib/less/js/test/less/media.less +14 -3
  169. data/lib/less/js/test/less/mixins-args.less +43 -5
  170. data/lib/less/js/test/less/mixins-guards.less +13 -0
  171. data/lib/less/js/test/less/mixins-named-args.less +5 -5
  172. data/lib/less/js/test/less/mixins-nested.less +2 -2
  173. data/lib/less/js/test/less/mixins-pattern.less +1 -1
  174. data/lib/less/js/test/less/mixins.less +1 -1
  175. data/lib/less/js/test/less/operations.less +27 -27
  176. data/lib/less/js/test/less/parens.less +20 -5
  177. data/lib/less/js/test/less/selectors.less +14 -7
  178. data/lib/less/js/test/less/urls.less +24 -0
  179. data/lib/less/js/test/less/variables.less +42 -12
  180. data/lib/less/loader.rb +33 -0
  181. data/lib/less/version.rb +1 -1
  182. data/spec/less/parser_spec.rb +5 -5
  183. metadata +76 -6
  184. data/lib/less/js/build/ecma-5.js +0 -120
  185. data/lib/less/js/lib/less/tree/ratio.js +0 -13
@@ -0,0 +1,391 @@
1
+ (function (tree) {
2
+ tree.extendFinderVisitor = function() {
3
+ this._visitor = new tree.visitor(this);
4
+ this.contexts = [];
5
+ this.allExtendsStack = [[]];
6
+ };
7
+
8
+ tree.extendFinderVisitor.prototype = {
9
+ run: function (root) {
10
+ root = this._visitor.visit(root);
11
+ root.allExtends = this.allExtendsStack[0];
12
+ return root;
13
+ },
14
+ visitRule: function (ruleNode, visitArgs) {
15
+ visitArgs.visitDeeper = false;
16
+ },
17
+ visitMixinDefinition: function (mixinDefinitionNode, visitArgs) {
18
+ visitArgs.visitDeeper = false;
19
+ },
20
+ visitRuleset: function (rulesetNode, visitArgs) {
21
+
22
+ if (rulesetNode.root) {
23
+ return;
24
+ }
25
+
26
+ var i, j, extend, allSelectorsExtendList = [], extendList;
27
+
28
+ // get &:extend(.a); rules which apply to all selectors in this ruleset
29
+ for(i = 0; i < rulesetNode.rules.length; i++) {
30
+ if (rulesetNode.rules[i] instanceof tree.Extend) {
31
+ allSelectorsExtendList.push(rulesetNode.rules[i]);
32
+ }
33
+ }
34
+
35
+ // now find every selector and apply the extends that apply to all extends
36
+ // and the ones which apply to an individual extend
37
+ for(i = 0; i < rulesetNode.paths.length; i++) {
38
+ var selectorPath = rulesetNode.paths[i],
39
+ selector = selectorPath[selectorPath.length-1];
40
+ extendList = selector.extendList.slice(0).concat(allSelectorsExtendList).map(function(allSelectorsExtend) {
41
+ return allSelectorsExtend.clone();
42
+ });
43
+ for(j = 0; j < extendList.length; j++) {
44
+ this.foundExtends = true;
45
+ extend = extendList[j];
46
+ extend.findSelfSelectors(selectorPath);
47
+ extend.ruleset = rulesetNode;
48
+ if (j === 0) { extend.firstExtendOnThisSelectorPath = true; }
49
+ this.allExtendsStack[this.allExtendsStack.length-1].push(extend);
50
+ }
51
+ }
52
+
53
+ this.contexts.push(rulesetNode.selectors);
54
+ },
55
+ visitRulesetOut: function (rulesetNode) {
56
+ if (!rulesetNode.root) {
57
+ this.contexts.length = this.contexts.length - 1;
58
+ }
59
+ },
60
+ visitMedia: function (mediaNode, visitArgs) {
61
+ mediaNode.allExtends = [];
62
+ this.allExtendsStack.push(mediaNode.allExtends);
63
+ },
64
+ visitMediaOut: function (mediaNode) {
65
+ this.allExtendsStack.length = this.allExtendsStack.length - 1;
66
+ },
67
+ visitDirective: function (directiveNode, visitArgs) {
68
+ directiveNode.allExtends = [];
69
+ this.allExtendsStack.push(directiveNode.allExtends);
70
+ },
71
+ visitDirectiveOut: function (directiveNode) {
72
+ this.allExtendsStack.length = this.allExtendsStack.length - 1;
73
+ }
74
+ };
75
+
76
+ tree.processExtendsVisitor = function() {
77
+ this._visitor = new tree.visitor(this);
78
+ };
79
+
80
+ tree.processExtendsVisitor.prototype = {
81
+ run: function(root) {
82
+ var extendFinder = new tree.extendFinderVisitor();
83
+ extendFinder.run(root);
84
+ if (!extendFinder.foundExtends) { return root; }
85
+ root.allExtends = root.allExtends.concat(this.doExtendChaining(root.allExtends, root.allExtends));
86
+ this.allExtendsStack = [root.allExtends];
87
+ return this._visitor.visit(root);
88
+ },
89
+ doExtendChaining: function (extendsList, extendsListTarget, iterationCount) {
90
+ //
91
+ // chaining is different from normal extension.. if we extend an extend then we are not just copying, altering and pasting
92
+ // the selector we would do normally, but we are also adding an extend with the same target selector
93
+ // this means this new extend can then go and alter other extends
94
+ //
95
+ // this method deals with all the chaining work - without it, extend is flat and doesn't work on other extend selectors
96
+ // this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already processed if
97
+ // we look at each selector at a time, as is done in visitRuleset
98
+
99
+ var extendIndex, targetExtendIndex, matches, extendsToAdd = [], newSelector, extendVisitor = this, selectorPath, extend, targetExtend, newExtend;
100
+
101
+ iterationCount = iterationCount || 0;
102
+
103
+ //loop through comparing every extend with every target extend.
104
+ // a target extend is the one on the ruleset we are looking at copy/edit/pasting in place
105
+ // e.g. .a:extend(.b) {} and .b:extend(.c) {} then the first extend extends the second one
106
+ // and the second is the target.
107
+ // the seperation into two lists allows us to process a subset of chains with a bigger set, as is the
108
+ // case when processing media queries
109
+ for(extendIndex = 0; extendIndex < extendsList.length; extendIndex++){
110
+ for(targetExtendIndex = 0; targetExtendIndex < extendsListTarget.length; targetExtendIndex++){
111
+
112
+ extend = extendsList[extendIndex];
113
+ targetExtend = extendsListTarget[targetExtendIndex];
114
+
115
+ // look for circular references
116
+ if (this.inInheritanceChain(targetExtend, extend)) { continue; }
117
+
118
+ // find a match in the target extends self selector (the bit before :extend)
119
+ selectorPath = [targetExtend.selfSelectors[0]];
120
+ matches = extendVisitor.findMatch(extend, selectorPath);
121
+
122
+ if (matches.length) {
123
+
124
+ // we found a match, so for each self selector..
125
+ extend.selfSelectors.forEach(function(selfSelector) {
126
+
127
+ // process the extend as usual
128
+ newSelector = extendVisitor.extendSelector(matches, selectorPath, selfSelector);
129
+
130
+ // but now we create a new extend from it
131
+ newExtend = new(tree.Extend)(targetExtend.selector, targetExtend.option, 0);
132
+ newExtend.selfSelectors = newSelector;
133
+
134
+ // add the extend onto the list of extends for that selector
135
+ newSelector[newSelector.length-1].extendList = [newExtend];
136
+
137
+ // record that we need to add it.
138
+ extendsToAdd.push(newExtend);
139
+ newExtend.ruleset = targetExtend.ruleset;
140
+
141
+ //remember its parents for circular references
142
+ newExtend.parents = [targetExtend, extend];
143
+
144
+ // only process the selector once.. if we have :extend(.a,.b) then multiple
145
+ // extends will look at the same selector path, so when extending
146
+ // we know that any others will be duplicates in terms of what is added to the css
147
+ if (targetExtend.firstExtendOnThisSelectorPath) {
148
+ newExtend.firstExtendOnThisSelectorPath = true;
149
+ targetExtend.ruleset.paths.push(newSelector);
150
+ }
151
+ });
152
+ }
153
+ }
154
+ }
155
+
156
+ if (extendsToAdd.length) {
157
+ // try to detect circular references to stop a stack overflow.
158
+ // may no longer be needed.
159
+ this.extendChainCount++;
160
+ if (iterationCount > 100) {
161
+ var selectorOne = "{unable to calculate}";
162
+ var selectorTwo = "{unable to calculate}";
163
+ try
164
+ {
165
+ selectorOne = extendsToAdd[0].selfSelectors[0].toCSS();
166
+ selectorTwo = extendsToAdd[0].selector.toCSS();
167
+ }
168
+ catch(e) {}
169
+ throw {message: "extend circular reference detected. One of the circular extends is currently:"+selectorOne+":extend(" + selectorTwo+")"};
170
+ }
171
+
172
+ // now process the new extends on the existing rules so that we can handle a extending b extending c ectending d extending e...
173
+ return extendsToAdd.concat(extendVisitor.doExtendChaining(extendsToAdd, extendsListTarget, iterationCount+1));
174
+ } else {
175
+ return extendsToAdd;
176
+ }
177
+ },
178
+ inInheritanceChain: function (possibleParent, possibleChild) {
179
+ if (possibleParent === possibleChild) {
180
+ return true;
181
+ }
182
+ if (possibleChild.parents) {
183
+ if (this.inInheritanceChain(possibleParent, possibleChild.parents[0])) {
184
+ return true;
185
+ }
186
+ if (this.inInheritanceChain(possibleParent, possibleChild.parents[1])) {
187
+ return true;
188
+ }
189
+ }
190
+ return false;
191
+ },
192
+ visitRule: function (ruleNode, visitArgs) {
193
+ visitArgs.visitDeeper = false;
194
+ },
195
+ visitMixinDefinition: function (mixinDefinitionNode, visitArgs) {
196
+ visitArgs.visitDeeper = false;
197
+ },
198
+ visitSelector: function (selectorNode, visitArgs) {
199
+ visitArgs.visitDeeper = false;
200
+ },
201
+ visitRuleset: function (rulesetNode, visitArgs) {
202
+ if (rulesetNode.root) {
203
+ return;
204
+ }
205
+ var matches, pathIndex, extendIndex, allExtends = this.allExtendsStack[this.allExtendsStack.length-1], selectorsToAdd = [], extendVisitor = this, selectorPath;
206
+
207
+ // look at each selector path in the ruleset, find any extend matches and then copy, find and replace
208
+
209
+ for(extendIndex = 0; extendIndex < allExtends.length; extendIndex++) {
210
+ for(pathIndex = 0; pathIndex < rulesetNode.paths.length; pathIndex++) {
211
+
212
+ selectorPath = rulesetNode.paths[pathIndex];
213
+
214
+ // extending extends happens initially, before the main pass
215
+ if (selectorPath[selectorPath.length-1].extendList.length) { continue; }
216
+
217
+ matches = this.findMatch(allExtends[extendIndex], selectorPath);
218
+
219
+ if (matches.length) {
220
+
221
+ allExtends[extendIndex].selfSelectors.forEach(function(selfSelector) {
222
+ selectorsToAdd.push(extendVisitor.extendSelector(matches, selectorPath, selfSelector));
223
+ });
224
+ }
225
+ }
226
+ }
227
+ rulesetNode.paths = rulesetNode.paths.concat(selectorsToAdd);
228
+ },
229
+ findMatch: function (extend, haystackSelectorPath) {
230
+ //
231
+ // look through the haystack selector path to try and find the needle - extend.selector
232
+ // returns an array of selector matches that can then be replaced
233
+ //
234
+ var haystackSelectorIndex, hackstackSelector, hackstackElementIndex, haystackElement,
235
+ targetCombinator, i,
236
+ extendVisitor = this,
237
+ needleElements = extend.selector.elements,
238
+ potentialMatches = [], potentialMatch, matches = [];
239
+
240
+ // loop through the haystack elements
241
+ for(haystackSelectorIndex = 0; haystackSelectorIndex < haystackSelectorPath.length; haystackSelectorIndex++) {
242
+ hackstackSelector = haystackSelectorPath[haystackSelectorIndex];
243
+
244
+ for(hackstackElementIndex = 0; hackstackElementIndex < hackstackSelector.elements.length; hackstackElementIndex++) {
245
+
246
+ haystackElement = hackstackSelector.elements[hackstackElementIndex];
247
+
248
+ // if we allow elements before our match we can add a potential match every time. otherwise only at the first element.
249
+ if (extend.allowBefore || (haystackSelectorIndex == 0 && hackstackElementIndex == 0)) {
250
+ potentialMatches.push({pathIndex: haystackSelectorIndex, index: hackstackElementIndex, matched: 0, initialCombinator: haystackElement.combinator});
251
+ }
252
+
253
+ for(i = 0; i < potentialMatches.length; i++) {
254
+ potentialMatch = potentialMatches[i];
255
+
256
+ // selectors add " " onto the first element. When we use & it joins the selectors together, but if we don't
257
+ // then each selector in haystackSelectorPath has a space before it added in the toCSS phase. so we need to work out
258
+ // what the resulting combinator will be
259
+ targetCombinator = haystackElement.combinator.value;
260
+ if (targetCombinator == '' && hackstackElementIndex === 0) {
261
+ targetCombinator = ' ';
262
+ }
263
+
264
+ // if we don't match, null our match to indicate failure
265
+ if (!extendVisitor.isElementValuesEqual(needleElements[potentialMatch.matched].value, haystackElement.value) ||
266
+ (potentialMatch.matched > 0 && needleElements[potentialMatch.matched].combinator.value !== targetCombinator)) {
267
+ potentialMatch = null;
268
+ } else {
269
+ potentialMatch.matched++;
270
+ }
271
+
272
+ // if we are still valid and have finished, test whether we have elements after and whether these are allowed
273
+ if (potentialMatch) {
274
+ potentialMatch.finished = potentialMatch.matched === needleElements.length;
275
+ if (potentialMatch.finished &&
276
+ (!extend.allowAfter && (hackstackElementIndex+1 < hackstackSelector.elements.length || haystackSelectorIndex+1 < haystackSelectorPath.length))) {
277
+ potentialMatch = null;
278
+ }
279
+ }
280
+ // if null we remove, if not, we are still valid, so either push as a valid match or continue
281
+ if (potentialMatch) {
282
+ if (potentialMatch.finished) {
283
+ potentialMatch.length = needleElements.length;
284
+ potentialMatch.endPathIndex = haystackSelectorIndex;
285
+ potentialMatch.endPathElementIndex = hackstackElementIndex + 1; // index after end of match
286
+ potentialMatches.length = 0; // we don't allow matches to overlap, so start matching again
287
+ matches.push(potentialMatch);
288
+ }
289
+ } else {
290
+ potentialMatches.splice(i, 1);
291
+ i--;
292
+ }
293
+ }
294
+ }
295
+ }
296
+ return matches;
297
+ },
298
+ isElementValuesEqual: function(elementValue1, elementValue2) {
299
+ if (typeof elementValue1 === "string" || typeof elementValue2 === "string") {
300
+ return elementValue1 === elementValue2;
301
+ }
302
+ if (elementValue1 instanceof tree.Attribute) {
303
+ if (elementValue1.op !== elementValue2.op || elementValue1.key !== elementValue2.key) {
304
+ return false;
305
+ }
306
+ if (!elementValue1.value || !elementValue2.value) {
307
+ if (elementValue1.value || elementValue2.value) {
308
+ return false;
309
+ }
310
+ return true;
311
+ }
312
+ elementValue1 = elementValue1.value.value || elementValue1.value;
313
+ elementValue2 = elementValue2.value.value || elementValue2.value;
314
+ return elementValue1 === elementValue2;
315
+ }
316
+ return false;
317
+ },
318
+ extendSelector:function (matches, selectorPath, replacementSelector) {
319
+
320
+ //for a set of matches, replace each match with the replacement selector
321
+
322
+ var currentSelectorPathIndex = 0,
323
+ currentSelectorPathElementIndex = 0,
324
+ path = [],
325
+ matchIndex,
326
+ selector,
327
+ firstElement,
328
+ match;
329
+
330
+ for (matchIndex = 0; matchIndex < matches.length; matchIndex++) {
331
+ match = matches[matchIndex];
332
+ selector = selectorPath[match.pathIndex];
333
+ firstElement = new tree.Element(
334
+ match.initialCombinator,
335
+ replacementSelector.elements[0].value,
336
+ replacementSelector.elements[0].index
337
+ );
338
+
339
+ if (match.pathIndex > currentSelectorPathIndex && currentSelectorPathElementIndex > 0) {
340
+ path[path.length - 1].elements = path[path.length - 1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
341
+ currentSelectorPathElementIndex = 0;
342
+ currentSelectorPathIndex++;
343
+ }
344
+
345
+ path = path.concat(selectorPath.slice(currentSelectorPathIndex, match.pathIndex));
346
+
347
+ path.push(new tree.Selector(
348
+ selector.elements
349
+ .slice(currentSelectorPathElementIndex, match.index)
350
+ .concat([firstElement])
351
+ .concat(replacementSelector.elements.slice(1))
352
+ ));
353
+ currentSelectorPathIndex = match.endPathIndex;
354
+ currentSelectorPathElementIndex = match.endPathElementIndex;
355
+ if (currentSelectorPathElementIndex >= selector.elements.length) {
356
+ currentSelectorPathElementIndex = 0;
357
+ currentSelectorPathIndex++;
358
+ }
359
+ }
360
+
361
+ if (currentSelectorPathIndex < selectorPath.length && currentSelectorPathElementIndex > 0) {
362
+ path[path.length - 1].elements = path[path.length - 1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
363
+ currentSelectorPathElementIndex = 0;
364
+ currentSelectorPathIndex++;
365
+ }
366
+
367
+ path = path.concat(selectorPath.slice(currentSelectorPathIndex, selectorPath.length));
368
+
369
+ return path;
370
+ },
371
+ visitRulesetOut: function (rulesetNode) {
372
+ },
373
+ visitMedia: function (mediaNode, visitArgs) {
374
+ var newAllExtends = mediaNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);
375
+ newAllExtends = newAllExtends.concat(this.doExtendChaining(newAllExtends, mediaNode.allExtends));
376
+ this.allExtendsStack.push(newAllExtends);
377
+ },
378
+ visitMediaOut: function (mediaNode) {
379
+ this.allExtendsStack.length = this.allExtendsStack.length - 1;
380
+ },
381
+ visitDirective: function (directiveNode, visitArgs) {
382
+ var newAllExtends = directiveNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);
383
+ newAllExtends = newAllExtends.concat(this.doExtendChaining(newAllExtends, directiveNode.allExtends));
384
+ this.allExtendsStack.push(newAllExtends);
385
+ },
386
+ visitDirectiveOut: function (directiveNode) {
387
+ this.allExtendsStack.length = this.allExtendsStack.length - 1;
388
+ }
389
+ };
390
+
391
+ })(require('./tree'));
@@ -14,7 +14,7 @@ tree.functions = {
14
14
  },
15
15
  hsla: function (h, s, l, a) {
16
16
  h = (number(h) % 360) / 360;
17
- s = number(s); l = number(l); a = number(a);
17
+ s = clamp(number(s)); l = clamp(number(l)); a = clamp(number(a));
18
18
 
19
19
  var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s;
20
20
  var m1 = l * 2 - m2;
@@ -71,6 +71,15 @@ tree.functions = {
71
71
  lightness: function (color) {
72
72
  return new(tree.Dimension)(Math.round(color.toHSL().l * 100), '%');
73
73
  },
74
+ hsvhue: function(color) {
75
+ return new(tree.Dimension)(Math.round(color.toHSV().h));
76
+ },
77
+ hsvsaturation: function (color) {
78
+ return new(tree.Dimension)(Math.round(color.toHSV().s * 100), '%');
79
+ },
80
+ hsvvalue: function (color) {
81
+ return new(tree.Dimension)(Math.round(color.toHSV().v * 100), '%');
82
+ },
74
83
  red: function (color) {
75
84
  return new(tree.Dimension)(color.rgb[0]);
76
85
  },
@@ -84,10 +93,7 @@ tree.functions = {
84
93
  return new(tree.Dimension)(color.toHSL().a);
85
94
  },
86
95
  luma: function (color) {
87
- return new(tree.Dimension)(Math.round((0.2126 * (color.rgb[0]/255) +
88
- 0.7152 * (color.rgb[1]/255) +
89
- 0.0722 * (color.rgb[2]/255)) *
90
- color.alpha * 100), '%');
96
+ return new(tree.Dimension)(Math.round(color.luma() * color.alpha * 100), '%');
91
97
  },
92
98
  saturate: function (color, amount) {
93
99
  var hsl = color.toHSL();
@@ -184,12 +190,18 @@ tree.functions = {
184
190
  if (typeof dark === 'undefined') {
185
191
  dark = this.rgba(0, 0, 0, 1.0);
186
192
  }
193
+ //Figure out which is actually light and dark!
194
+ if (dark.luma() > light.luma()) {
195
+ var t = light;
196
+ light = dark;
197
+ dark = t;
198
+ }
187
199
  if (typeof threshold === 'undefined') {
188
200
  threshold = 0.43;
189
201
  } else {
190
- threshold = threshold.value;
202
+ threshold = number(threshold);
191
203
  }
192
- if (((0.2126 * (color.rgb[0]/255) + 0.7152 * (color.rgb[1]/255) + 0.0722 * (color.rgb[2]/255)) * color.alpha) < threshold) {
204
+ if ((color.luma() * color.alpha) < threshold) {
193
205
  return light;
194
206
  } else {
195
207
  return dark;
@@ -217,19 +229,32 @@ tree.functions = {
217
229
  unit: function (val, unit) {
218
230
  return new(tree.Dimension)(val.value, unit ? unit.toCSS() : "");
219
231
  },
232
+ convert: function (val, unit) {
233
+ return val.convertTo(unit.value);
234
+ },
220
235
  round: function (n, f) {
221
236
  var fraction = typeof(f) === "undefined" ? 0 : f.value;
222
- return this._math(function(num) { return num.toFixed(fraction); }, n);
237
+ return this._math(function(num) { return num.toFixed(fraction); }, null, n);
238
+ },
239
+ pi: function () {
240
+ return new(tree.Dimension)(Math.PI);
223
241
  },
224
- ceil: function (n) {
225
- return this._math(Math.ceil, n);
242
+ mod: function(a, b) {
243
+ return new(tree.Dimension)(a.value % b.value, a.unit);
226
244
  },
227
- floor: function (n) {
228
- return this._math(Math.floor, n);
245
+ pow: function(x, y) {
246
+ if (typeof x === "number" && typeof y === "number") {
247
+ x = new(tree.Dimension)(x);
248
+ y = new(tree.Dimension)(y);
249
+ } else if (!(x instanceof tree.Dimension) || !(y instanceof tree.Dimension)) {
250
+ throw { type: "Argument", message: "arguments must be numbers" };
251
+ }
252
+
253
+ return new(tree.Dimension)(Math.pow(x.value, y.value), x.unit);
229
254
  },
230
- _math: function (fn, n) {
255
+ _math: function (fn, unit, n) {
231
256
  if (n instanceof tree.Dimension) {
232
- return new(tree.Dimension)(fn(parseFloat(n.value)), n.unit);
257
+ return new(tree.Dimension)(fn(parseFloat(n.value)), unit == null ? n.unit : unit);
233
258
  } else if (typeof(n) === 'number') {
234
259
  return fn(n);
235
260
  } else {
@@ -266,13 +291,16 @@ tree.functions = {
266
291
  return this._isa(n, tree.URL);
267
292
  },
268
293
  ispixel: function (n) {
269
- return (n instanceof tree.Dimension) && n.unit === 'px' ? tree.True : tree.False;
294
+ return this.isunit(n, 'px');
270
295
  },
271
296
  ispercentage: function (n) {
272
- return (n instanceof tree.Dimension) && n.unit === '%' ? tree.True : tree.False;
297
+ return this.isunit(n, '%');
273
298
  },
274
299
  isem: function (n) {
275
- return (n instanceof tree.Dimension) && n.unit === 'em' ? tree.True : tree.False;
300
+ return this.isunit(n, 'em');
301
+ },
302
+ isunit: function (n, unit) {
303
+ return (n instanceof tree.Dimension) && n.unit.is(unit.value || unit) ? tree.True : tree.False;
276
304
  },
277
305
  _isa: function (n, Type) {
278
306
  return (n instanceof Type) ? tree.True : tree.False;
@@ -342,15 +370,135 @@ tree.functions = {
342
370
  },
343
371
  shade: function(color, amount) {
344
372
  return this.mix(this.rgb(0, 0, 0), color, amount);
373
+ },
374
+ extract: function(values, index) {
375
+ index = index.value - 1; // (1-based index)
376
+ return values.value[index];
377
+ },
378
+
379
+ "data-uri": function(mimetypeNode, filePathNode) {
380
+
381
+ if (typeof window !== 'undefined') {
382
+ return new tree.URL(filePathNode || mimetypeNode, this.currentFileInfo).eval(this.env);
383
+ }
384
+
385
+ var mimetype = mimetypeNode.value;
386
+ var filePath = (filePathNode && filePathNode.value);
387
+
388
+ var fs = require("fs"),
389
+ path = require("path"),
390
+ useBase64 = false;
391
+
392
+ if (arguments.length < 2) {
393
+ filePath = mimetype;
394
+ }
395
+
396
+ if (this.env.isPathRelative(filePath)) {
397
+ if (this.currentFileInfo.relativeUrls) {
398
+ filePath = path.join(this.currentFileInfo.currentDirectory, filePath);
399
+ } else {
400
+ filePath = path.join(this.currentFileInfo.entryPath, filePath);
401
+ }
402
+ }
403
+
404
+ // detect the mimetype if not given
405
+ if (arguments.length < 2) {
406
+ var mime;
407
+ try {
408
+ mime = require('mime');
409
+ } catch (ex) {
410
+ mime = tree._mime;
411
+ }
412
+
413
+ mimetype = mime.lookup(filePath);
414
+
415
+ // use base 64 unless it's an ASCII or UTF-8 format
416
+ var charset = mime.charsets.lookup(mimetype);
417
+ useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0;
418
+ if (useBase64) mimetype += ';base64';
419
+ }
420
+ else {
421
+ useBase64 = /;base64$/.test(mimetype)
422
+ }
423
+
424
+ var buf = fs.readFileSync(filePath);
425
+
426
+ // IE8 cannot handle a data-uri larger than 32KB. If this is exceeded
427
+ // and the --ieCompat flag is enabled, return a normal url() instead.
428
+ var DATA_URI_MAX_KB = 32,
429
+ fileSizeInKB = parseInt((buf.length / 1024), 10);
430
+ if (fileSizeInKB >= DATA_URI_MAX_KB) {
431
+
432
+ if (this.env.ieCompat !== false) {
433
+ if (!this.env.silent) {
434
+ console.warn("Skipped data-uri embedding of %s because its size (%dKB) exceeds IE8-safe %dKB!", filePath, fileSizeInKB, DATA_URI_MAX_KB);
435
+ }
436
+
437
+ return new tree.URL(filePathNode || mimetypeNode, this.currentFileInfo).eval(this.env);
438
+ } else if (!this.env.silent) {
439
+ // if explicitly disabled (via --no-ie-compat on CLI, or env.ieCompat === false), merely warn
440
+ console.warn("WARNING: Embedding %s (%dKB) exceeds IE8's data-uri size limit of %dKB!", filePath, fileSizeInKB, DATA_URI_MAX_KB);
441
+ }
442
+ }
443
+
444
+ buf = useBase64 ? buf.toString('base64')
445
+ : encodeURIComponent(buf);
446
+
447
+ var uri = "'data:" + mimetype + ',' + buf + "'";
448
+ return new(tree.URL)(new(tree.Anonymous)(uri));
449
+ }
450
+ };
451
+
452
+ // these static methods are used as a fallback when the optional 'mime' dependency is missing
453
+ tree._mime = {
454
+ // this map is intentionally incomplete
455
+ // if you want more, install 'mime' dep
456
+ _types: {
457
+ '.htm' : 'text/html',
458
+ '.html': 'text/html',
459
+ '.gif' : 'image/gif',
460
+ '.jpg' : 'image/jpeg',
461
+ '.jpeg': 'image/jpeg',
462
+ '.png' : 'image/png'
463
+ },
464
+ lookup: function (filepath) {
465
+ var ext = require('path').extname(filepath),
466
+ type = tree._mime._types[ext];
467
+ if (type === undefined) {
468
+ throw new Error('Optional dependency "mime" is required for ' + ext);
469
+ }
470
+ return type;
471
+ },
472
+ charsets: {
473
+ lookup: function (type) {
474
+ // assumes all text types are UTF-8
475
+ return type && (/^text\//).test(type) ? 'UTF-8' : '';
476
+ }
345
477
  }
346
478
  };
347
479
 
480
+ var mathFunctions = [{name:"ceil"}, {name:"floor"}, {name: "sqrt"}, {name:"abs"},
481
+ {name:"tan", unit: ""}, {name:"sin", unit: ""}, {name:"cos", unit: ""},
482
+ {name:"atan", unit: "rad"}, {name:"asin", unit: "rad"}, {name:"acos", unit: "rad"}],
483
+ createMathFunction = function(name, unit) {
484
+ return function(n) {
485
+ if (unit != null) {
486
+ n = n.unify();
487
+ }
488
+ return this._math(Math[name], unit, n);
489
+ };
490
+ };
491
+
492
+ for(var i = 0; i < mathFunctions.length; i++) {
493
+ tree.functions[mathFunctions[i].name] = createMathFunction(mathFunctions[i].name, mathFunctions[i].unit);
494
+ }
495
+
348
496
  function hsla(color) {
349
497
  return tree.functions.hsla(color.h, color.s, color.l, color.a);
350
498
  }
351
499
 
352
500
  function scaled(n, size) {
353
- if (n instanceof tree.Dimension && n.unit == '%') {
501
+ if (n instanceof tree.Dimension && n.unit.is('%')) {
354
502
  return parseFloat(n.value * size / 100);
355
503
  } else {
356
504
  return number(n);
@@ -359,7 +507,7 @@ function scaled(n, size) {
359
507
 
360
508
  function number(n) {
361
509
  if (n instanceof tree.Dimension) {
362
- return parseFloat(n.unit == '%' ? n.value / 100 : n.value);
510
+ return parseFloat(n.unit.is('%') ? n.value / 100 : n.value);
363
511
  } else if (typeof(n) === 'number') {
364
512
  return n;
365
513
  } else {
@@ -374,4 +522,11 @@ function clamp(val) {
374
522
  return Math.min(1, Math.max(0, val));
375
523
  }
376
524
 
525
+ tree.functionCall = function(env, currentFileInfo) {
526
+ this.env = env;
527
+ this.currentFileInfo = currentFileInfo;
528
+ };
529
+
530
+ tree.functionCall.prototype = tree.functions;
531
+
377
532
  })(require('./tree'));