less 2.3.3 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
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'));