Magemaven_Lesscss - Version 1.0.0

Version Notes

Release 1.0.0

Download this release

Release Info

Developer Sergey Storchay
Extension Magemaven_Lesscss
Version 1.0.0
Comparing to
See all releases


Version 1.0.0

Files changed (67) hide show
  1. app/code/community/Magemaven/Lesscss/Helper/Data.php +75 -0
  2. app/code/community/Magemaven/Lesscss/Model/Design/Package.php +53 -0
  3. app/code/community/Magemaven/Lesscss/etc/config.xml +38 -0
  4. app/etc/modules/Magemaven_Lesscss.xml +25 -0
  5. lib/lessphp/.gitignore +6 -0
  6. lib/lessphp/LICENSE +660 -0
  7. lib/lessphp/README.md +64 -0
  8. lib/lessphp/docs/docs.md +992 -0
  9. lib/lessphp/lessc.inc.php +2622 -0
  10. lib/lessphp/lessify +23 -0
  11. lib/lessphp/lessify.inc.php +447 -0
  12. lib/lessphp/package.sh +22 -0
  13. lib/lessphp/plessc +193 -0
  14. lib/lessphp/tests/README.md +24 -0
  15. lib/lessphp/tests/bootstrap.sh +49 -0
  16. lib/lessphp/tests/inputs/accessors.less.disable +36 -0
  17. lib/lessphp/tests/inputs/arity.less +77 -0
  18. lib/lessphp/tests/inputs/attributes.less +41 -0
  19. lib/lessphp/tests/inputs/builtins.less +34 -0
  20. lib/lessphp/tests/inputs/colors.less +122 -0
  21. lib/lessphp/tests/inputs/compile_on_mixin.less +39 -0
  22. lib/lessphp/tests/inputs/escape.less +19 -0
  23. lib/lessphp/tests/inputs/font_family.less +28 -0
  24. lib/lessphp/tests/inputs/guards.less +88 -0
  25. lib/lessphp/tests/inputs/hacks.less +6 -0
  26. lib/lessphp/tests/inputs/import.less +21 -0
  27. lib/lessphp/tests/inputs/keyframes.less +48 -0
  28. lib/lessphp/tests/inputs/math.less +116 -0
  29. lib/lessphp/tests/inputs/media.less +38 -0
  30. lib/lessphp/tests/inputs/misc.less +78 -0
  31. lib/lessphp/tests/inputs/mixin_functions.less +40 -0
  32. lib/lessphp/tests/inputs/mixin_merging.less.disable +100 -0
  33. lib/lessphp/tests/inputs/mixins.less +122 -0
  34. lib/lessphp/tests/inputs/nested.less +60 -0
  35. lib/lessphp/tests/inputs/pattern_matching.less +113 -0
  36. lib/lessphp/tests/inputs/scopes.less +40 -0
  37. lib/lessphp/tests/inputs/site_demos.less +120 -0
  38. lib/lessphp/tests/inputs/test-imports/file1.less +16 -0
  39. lib/lessphp/tests/inputs/test-imports/file2.less +6 -0
  40. lib/lessphp/tests/inputs/variables.less +45 -0
  41. lib/lessphp/tests/outputs/accessors.css +14 -0
  42. lib/lessphp/tests/outputs/arity.css +25 -0
  43. lib/lessphp/tests/outputs/attributes.css +35 -0
  44. lib/lessphp/tests/outputs/builtins.css +18 -0
  45. lib/lessphp/tests/outputs/colors.css +69 -0
  46. lib/lessphp/tests/outputs/compile_on_mixin.css +11 -0
  47. lib/lessphp/tests/outputs/escape.css +13 -0
  48. lib/lessphp/tests/outputs/font_family.css +17 -0
  49. lib/lessphp/tests/outputs/guards.css +23 -0
  50. lib/lessphp/tests/outputs/hacks.css +1 -0
  51. lib/lessphp/tests/outputs/import.css +14 -0
  52. lib/lessphp/tests/outputs/keyframes.css +36 -0
  53. lib/lessphp/tests/outputs/math.css +61 -0
  54. lib/lessphp/tests/outputs/media.css +28 -0
  55. lib/lessphp/tests/outputs/misc.css +32 -0
  56. lib/lessphp/tests/outputs/mixin_functions.css +14 -0
  57. lib/lessphp/tests/outputs/mixin_merging.css +42 -0
  58. lib/lessphp/tests/outputs/mixins.css +49 -0
  59. lib/lessphp/tests/outputs/nested.css +16 -0
  60. lib/lessphp/tests/outputs/nesting.css +6 -0
  61. lib/lessphp/tests/outputs/pattern_matching.css +48 -0
  62. lib/lessphp/tests/outputs/scopes.css +7 -0
  63. lib/lessphp/tests/outputs/site_demos.css +54 -0
  64. lib/lessphp/tests/outputs/variables.css +20 -0
  65. lib/lessphp/tests/sort.php +57 -0
  66. lib/lessphp/tests/test.php +190 -0
  67. package.xml +18 -0
app/code/community/Magemaven/Lesscss/Helper/Data.php ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This source file is subject to the Academic Free License (AFL 3.0)
4
+ * that is bundled with this package in the file LICENSE_AFL.txt.
5
+ * It is also available through the world-wide-web at this URL:
6
+ * http://opensource.org/licenses/afl-3.0.php
7
+ *
8
+ * @category Magemaven
9
+ * @package Magemaven_Lesscss
10
+ * @copyright Copyright (c) 2012 Sergey Storchay <r8@r8.com.ua>
11
+ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
12
+ */
13
+ require_once(Mage::getBaseDir('lib') . DS . 'lessphp' . DS .'lessc.inc.php');
14
+
15
+ class Magemaven_Lesscss_Helper_Data extends Mage_Core_Helper_Abstract
16
+ {
17
+ /**
18
+ * Get file extension in lower case
19
+ *
20
+ * @param $file
21
+ * @return string
22
+ */
23
+ public function getFileExtension($file)
24
+ {
25
+ return strtolower(pathinfo($file, PATHINFO_EXTENSION));
26
+ }
27
+
28
+ /**
29
+ * Compile less file and return full path to created css
30
+ *
31
+ * @param $file
32
+ * @return string
33
+ */
34
+ public function compile($file)
35
+ {
36
+ if (!$file) {
37
+ return '';
38
+ }
39
+
40
+ try {
41
+ $targetFilename = Mage::getBaseDir('media')
42
+ . DS . 'lesscss' . DS . md5($file) . '.css';
43
+ $cacheKey = 'less_' . $file;
44
+
45
+ /** @var $cacheModel Mage_Core_Model_Cache */
46
+ $cacheModel = $cache = Mage::getSingleton('core/cache');
47
+ $cache = $cacheModel->load($cacheKey);
48
+ if ($cache) {
49
+ $cache = @unserialize($cache);
50
+ }
51
+
52
+ if (!file_exists($targetFilename)) {
53
+ $cache = false;
54
+ }
55
+
56
+ $lastUpdated = (isset($cache['updated'])) ? $cache['updated'] : 0;
57
+ $cache = lessc::cexecute(($cache) ? $cache : $file);
58
+
59
+ if ($cache['updated'] > $lastUpdated) {
60
+ if (!file_exists(dirname($targetFilename))) {
61
+ mkdir(dirname($targetFilename), 0777, true);
62
+ }
63
+
64
+ file_put_contents($targetFilename, $cache['compiled']);
65
+ $cacheModel->save(serialize($cache), $cacheKey);
66
+ }
67
+
68
+ } catch (Exception $e) {
69
+ Mage::logException($e);
70
+ $targetFilename = '';
71
+ }
72
+
73
+ return $targetFilename;
74
+ }
75
+ }
app/code/community/Magemaven/Lesscss/Model/Design/Package.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This source file is subject to the Academic Free License (AFL 3.0)
4
+ * that is bundled with this package in the file LICENSE_AFL.txt.
5
+ * It is also available through the world-wide-web at this URL:
6
+ * http://opensource.org/licenses/afl-3.0.php
7
+ *
8
+ * @category Magemaven
9
+ * @package Magemaven_Lesscss
10
+ * @copyright Copyright (c) 2012 Sergey Storchay <r8@r8.com.ua>
11
+ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
12
+ */
13
+ class Magemaven_Lesscss_Model_Design_Package extends Mage_Core_Model_Design_Package
14
+ {
15
+ public function getSkinUrl($file = null, array $params = array())
16
+ {
17
+ if (empty($params['_type'])) {
18
+ $params['_type'] = 'skin';
19
+ }
20
+
21
+ /** @var $helper Magemaven_Lesscss_Helper_Data */
22
+ $helper = Mage::helper('lesscss');
23
+
24
+ if ($helper->getFileExtension($file) == 'less') {
25
+ $file = $this->getFilename($file, $params);
26
+
27
+ if ($file) {
28
+ $file = str_replace(Mage::getBaseDir('media') . DS, '', $file);
29
+ $file = str_replace('\\', '/', $file);
30
+ $file = Mage::getBaseUrl('media',
31
+ isset($params['_secure']) ? (bool)$params['_secure'] : null
32
+ ) . $file;
33
+ }
34
+ } else {
35
+ $file = parent::getSkinUrl($file, $params);
36
+ }
37
+
38
+ return $file;
39
+ }
40
+
41
+ public function getFilename($file, array $params)
42
+ {
43
+ /** @var $helper Magemaven_Lesscss_Helper_Data */
44
+ $helper = Mage::helper('lesscss');
45
+
46
+ $file = parent::getFilename($file, $params);
47
+ if ($helper->getFileExtension($file) == 'less') {
48
+ $file = $helper->compile($file);
49
+ }
50
+
51
+ return $file;
52
+ }
53
+ }
app/code/community/Magemaven/Lesscss/etc/config.xml ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <!--
3
+ /**
4
+ * This source file is subject to the Academic Free License (AFL 3.0)
5
+ * that is bundled with this package in the file LICENSE_AFL.txt.
6
+ * It is also available through the world-wide-web at this URL:
7
+ * http://opensource.org/licenses/afl-3.0.php
8
+ *
9
+ * @category Magemaven
10
+ * @package Magemaven_Lesscss
11
+ * @copyright Copyright (c) 2012 Sergey Storchay <r8@r8.com.ua>
12
+ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
13
+ */
14
+ -->
15
+ <config>
16
+ <modules>
17
+ <Magemaven_Lesscss>
18
+ <version>1.0.0.0</version>
19
+ </Magemaven_Lesscss>
20
+ </modules>
21
+ <global>
22
+ <models>
23
+ <lesscss>
24
+ <class>Magemaven_Lesscss_Model</class>
25
+ </lesscss>
26
+ <core>
27
+ <rewrite>
28
+ <design_package>Magemaven_Lesscss_Model_Design_Package</design_package>
29
+ </rewrite>
30
+ </core>
31
+ </models>
32
+ <helpers>
33
+ <lesscss>
34
+ <class>Magemaven_Lesscss_Helper</class>
35
+ </lesscss>
36
+ </helpers>
37
+ </global>
38
+ </config>
app/etc/modules/Magemaven_Lesscss.xml ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <!--
3
+ /**
4
+ * This source file is subject to the Academic Free License (AFL 3.0)
5
+ * that is bundled with this package in the file LICENSE_AFL.txt.
6
+ * It is also available through the world-wide-web at this URL:
7
+ * http://opensource.org/licenses/afl-3.0.php
8
+ *
9
+ * @category Magemaven
10
+ * @package Magemaven_Lesscss
11
+ * @copyright Copyright (c) 2012 Sergey Storchay <r8@r8.com.ua>
12
+ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
13
+ */
14
+ -->
15
+ <config>
16
+ <modules>
17
+ <Magemaven_Lesscss>
18
+ <active>true</active>
19
+ <codePool>community</codePool>
20
+ <depends>
21
+ <Mage_Core />
22
+ </depends>
23
+ </Magemaven_Lesscss>
24
+ </modules>
25
+ </config>
lib/lessphp/.gitignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ *.swp
2
+ *~
3
+ /*.less
4
+ /*.css
5
+ tests/bootstrap
6
+ tests/tmp
lib/lessphp/LICENSE ADDED
@@ -0,0 +1,660 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ For ease of distribution, lessphp 0.2.0 is under a dual license.
2
+ You are free to pick which one suits your needs.
3
+
4
+
5
+
6
+
7
+ MIT LICENSE
8
+
9
+
10
+
11
+
12
+ Copyright (c) 2010 Leaf Corcoran, http://leafo.net/lessphp
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining
15
+ a copy of this software and associated documentation files (the
16
+ "Software"), to deal in the Software without restriction, including
17
+ without limitation the rights to use, copy, modify, merge, publish,
18
+ distribute, sublicense, and/or sell copies of the Software, and to
19
+ permit persons to whom the Software is furnished to do so, subject to
20
+ the following conditions:
21
+
22
+ The above copyright notice and this permission notice shall be
23
+ included in all copies or substantial portions of the Software.
24
+
25
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32
+
33
+
34
+
35
+
36
+ GPL VERSION 3
37
+
38
+
39
+
40
+
41
+ GNU GENERAL PUBLIC LICENSE
42
+ Version 3, 29 June 2007
43
+
44
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
45
+ Everyone is permitted to copy and distribute verbatim copies
46
+ of this license document, but changing it is not allowed.
47
+
48
+ Preamble
49
+
50
+ The GNU General Public License is a free, copyleft license for
51
+ software and other kinds of works.
52
+
53
+ The licenses for most software and other practical works are designed
54
+ to take away your freedom to share and change the works. By contrast,
55
+ the GNU General Public License is intended to guarantee your freedom to
56
+ share and change all versions of a program--to make sure it remains free
57
+ software for all its users. We, the Free Software Foundation, use the
58
+ GNU General Public License for most of our software; it applies also to
59
+ any other work released this way by its authors. You can apply it to
60
+ your programs, too.
61
+
62
+ When we speak of free software, we are referring to freedom, not
63
+ price. Our General Public Licenses are designed to make sure that you
64
+ have the freedom to distribute copies of free software (and charge for
65
+ them if you wish), that you receive source code or can get it if you
66
+ want it, that you can change the software or use pieces of it in new
67
+ free programs, and that you know you can do these things.
68
+
69
+ To protect your rights, we need to prevent others from denying you
70
+ these rights or asking you to surrender the rights. Therefore, you have
71
+ certain responsibilities if you distribute copies of the software, or if
72
+ you modify it: responsibilities to respect the freedom of others.
73
+
74
+ For example, if you distribute copies of such a program, whether
75
+ gratis or for a fee, you must pass on to the recipients the same
76
+ freedoms that you received. You must make sure that they, too, receive
77
+ or can get the source code. And you must show them these terms so they
78
+ know their rights.
79
+
80
+ Developers that use the GNU GPL protect your rights with two steps:
81
+ (1) assert copyright on the software, and (2) offer you this License
82
+ giving you legal permission to copy, distribute and/or modify it.
83
+
84
+ For the developers' and authors' protection, the GPL clearly explains
85
+ that there is no warranty for this free software. For both users' and
86
+ authors' sake, the GPL requires that modified versions be marked as
87
+ changed, so that their problems will not be attributed erroneously to
88
+ authors of previous versions.
89
+
90
+ Some devices are designed to deny users access to install or run
91
+ modified versions of the software inside them, although the manufacturer
92
+ can do so. This is fundamentally incompatible with the aim of
93
+ protecting users' freedom to change the software. The systematic
94
+ pattern of such abuse occurs in the area of products for individuals to
95
+ use, which is precisely where it is most unacceptable. Therefore, we
96
+ have designed this version of the GPL to prohibit the practice for those
97
+ products. If such problems arise substantially in other domains, we
98
+ stand ready to extend this provision to those domains in future versions
99
+ of the GPL, as needed to protect the freedom of users.
100
+
101
+ Finally, every program is threatened constantly by software patents.
102
+ States should not allow patents to restrict development and use of
103
+ software on general-purpose computers, but in those that do, we wish to
104
+ avoid the special danger that patents applied to a free program could
105
+ make it effectively proprietary. To prevent this, the GPL assures that
106
+ patents cannot be used to render the program non-free.
107
+
108
+ The precise terms and conditions for copying, distribution and
109
+ modification follow.
110
+
111
+ TERMS AND CONDITIONS
112
+
113
+ 0. Definitions.
114
+
115
+ "This License" refers to version 3 of the GNU General Public License.
116
+
117
+ "Copyright" also means copyright-like laws that apply to other kinds of
118
+ works, such as semiconductor masks.
119
+
120
+ "The Program" refers to any copyrightable work licensed under this
121
+ License. Each licensee is addressed as "you". "Licensees" and
122
+ "recipients" may be individuals or organizations.
123
+
124
+ To "modify" a work means to copy from or adapt all or part of the work
125
+ in a fashion requiring copyright permission, other than the making of an
126
+ exact copy. The resulting work is called a "modified version" of the
127
+ earlier work or a work "based on" the earlier work.
128
+
129
+ A "covered work" means either the unmodified Program or a work based
130
+ on the Program.
131
+
132
+ To "propagate" a work means to do anything with it that, without
133
+ permission, would make you directly or secondarily liable for
134
+ infringement under applicable copyright law, except executing it on a
135
+ computer or modifying a private copy. Propagation includes copying,
136
+ distribution (with or without modification), making available to the
137
+ public, and in some countries other activities as well.
138
+
139
+ To "convey" a work means any kind of propagation that enables other
140
+ parties to make or receive copies. Mere interaction with a user through
141
+ a computer network, with no transfer of a copy, is not conveying.
142
+
143
+ An interactive user interface displays "Appropriate Legal Notices"
144
+ to the extent that it includes a convenient and prominently visible
145
+ feature that (1) displays an appropriate copyright notice, and (2)
146
+ tells the user that there is no warranty for the work (except to the
147
+ extent that warranties are provided), that licensees may convey the
148
+ work under this License, and how to view a copy of this License. If
149
+ the interface presents a list of user commands or options, such as a
150
+ menu, a prominent item in the list meets this criterion.
151
+
152
+ 1. Source Code.
153
+
154
+ The "source code" for a work means the preferred form of the work
155
+ for making modifications to it. "Object code" means any non-source
156
+ form of a work.
157
+
158
+ A "Standard Interface" means an interface that either is an official
159
+ standard defined by a recognized standards body, or, in the case of
160
+ interfaces specified for a particular programming language, one that
161
+ is widely used among developers working in that language.
162
+
163
+ The "System Libraries" of an executable work include anything, other
164
+ than the work as a whole, that (a) is included in the normal form of
165
+ packaging a Major Component, but which is not part of that Major
166
+ Component, and (b) serves only to enable use of the work with that
167
+ Major Component, or to implement a Standard Interface for which an
168
+ implementation is available to the public in source code form. A
169
+ "Major Component", in this context, means a major essential component
170
+ (kernel, window system, and so on) of the specific operating system
171
+ (if any) on which the executable work runs, or a compiler used to
172
+ produce the work, or an object code interpreter used to run it.
173
+
174
+ The "Corresponding Source" for a work in object code form means all
175
+ the source code needed to generate, install, and (for an executable
176
+ work) run the object code and to modify the work, including scripts to
177
+ control those activities. However, it does not include the work's
178
+ System Libraries, or general-purpose tools or generally available free
179
+ programs which are used unmodified in performing those activities but
180
+ which are not part of the work. For example, Corresponding Source
181
+ includes interface definition files associated with source files for
182
+ the work, and the source code for shared libraries and dynamically
183
+ linked subprograms that the work is specifically designed to require,
184
+ such as by intimate data communication or control flow between those
185
+ subprograms and other parts of the work.
186
+
187
+ The Corresponding Source need not include anything that users
188
+ can regenerate automatically from other parts of the Corresponding
189
+ Source.
190
+
191
+ The Corresponding Source for a work in source code form is that
192
+ same work.
193
+
194
+ 2. Basic Permissions.
195
+
196
+ All rights granted under this License are granted for the term of
197
+ copyright on the Program, and are irrevocable provided the stated
198
+ conditions are met. This License explicitly affirms your unlimited
199
+ permission to run the unmodified Program. The output from running a
200
+ covered work is covered by this License only if the output, given its
201
+ content, constitutes a covered work. This License acknowledges your
202
+ rights of fair use or other equivalent, as provided by copyright law.
203
+
204
+ You may make, run and propagate covered works that you do not
205
+ convey, without conditions so long as your license otherwise remains
206
+ in force. You may convey covered works to others for the sole purpose
207
+ of having them make modifications exclusively for you, or provide you
208
+ with facilities for running those works, provided that you comply with
209
+ the terms of this License in conveying all material for which you do
210
+ not control copyright. Those thus making or running the covered works
211
+ for you must do so exclusively on your behalf, under your direction
212
+ and control, on terms that prohibit them from making any copies of
213
+ your copyrighted material outside their relationship with you.
214
+
215
+ Conveying under any other circumstances is permitted solely under
216
+ the conditions stated below. Sublicensing is not allowed; section 10
217
+ makes it unnecessary.
218
+
219
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
220
+
221
+ No covered work shall be deemed part of an effective technological
222
+ measure under any applicable law fulfilling obligations under article
223
+ 11 of the WIPO copyright treaty adopted on 20 December 1996, or
224
+ similar laws prohibiting or restricting circumvention of such
225
+ measures.
226
+
227
+ When you convey a covered work, you waive any legal power to forbid
228
+ circumvention of technological measures to the extent such circumvention
229
+ is effected by exercising rights under this License with respect to
230
+ the covered work, and you disclaim any intention to limit operation or
231
+ modification of the work as a means of enforcing, against the work's
232
+ users, your or third parties' legal rights to forbid circumvention of
233
+ technological measures.
234
+
235
+ 4. Conveying Verbatim Copies.
236
+
237
+ You may convey verbatim copies of the Program's source code as you
238
+ receive it, in any medium, provided that you conspicuously and
239
+ appropriately publish on each copy an appropriate copyright notice;
240
+ keep intact all notices stating that this License and any
241
+ non-permissive terms added in accord with section 7 apply to the code;
242
+ keep intact all notices of the absence of any warranty; and give all
243
+ recipients a copy of this License along with the Program.
244
+
245
+ You may charge any price or no price for each copy that you convey,
246
+ and you may offer support or warranty protection for a fee.
247
+
248
+ 5. Conveying Modified Source Versions.
249
+
250
+ You may convey a work based on the Program, or the modifications to
251
+ produce it from the Program, in the form of source code under the
252
+ terms of section 4, provided that you also meet all of these conditions:
253
+
254
+ a) The work must carry prominent notices stating that you modified
255
+ it, and giving a relevant date.
256
+
257
+ b) The work must carry prominent notices stating that it is
258
+ released under this License and any conditions added under section
259
+ 7. This requirement modifies the requirement in section 4 to
260
+ "keep intact all notices".
261
+
262
+ c) You must license the entire work, as a whole, under this
263
+ License to anyone who comes into possession of a copy. This
264
+ License will therefore apply, along with any applicable section 7
265
+ additional terms, to the whole of the work, and all its parts,
266
+ regardless of how they are packaged. This License gives no
267
+ permission to license the work in any other way, but it does not
268
+ invalidate such permission if you have separately received it.
269
+
270
+ d) If the work has interactive user interfaces, each must display
271
+ Appropriate Legal Notices; however, if the Program has interactive
272
+ interfaces that do not display Appropriate Legal Notices, your
273
+ work need not make them do so.
274
+
275
+ A compilation of a covered work with other separate and independent
276
+ works, which are not by their nature extensions of the covered work,
277
+ and which are not combined with it such as to form a larger program,
278
+ in or on a volume of a storage or distribution medium, is called an
279
+ "aggregate" if the compilation and its resulting copyright are not
280
+ used to limit the access or legal rights of the compilation's users
281
+ beyond what the individual works permit. Inclusion of a covered work
282
+ in an aggregate does not cause this License to apply to the other
283
+ parts of the aggregate.
284
+
285
+ 6. Conveying Non-Source Forms.
286
+
287
+ You may convey a covered work in object code form under the terms
288
+ of sections 4 and 5, provided that you also convey the
289
+ machine-readable Corresponding Source under the terms of this License,
290
+ in one of these ways:
291
+
292
+ a) Convey the object code in, or embodied in, a physical product
293
+ (including a physical distribution medium), accompanied by the
294
+ Corresponding Source fixed on a durable physical medium
295
+ customarily used for software interchange.
296
+
297
+ b) Convey the object code in, or embodied in, a physical product
298
+ (including a physical distribution medium), accompanied by a
299
+ written offer, valid for at least three years and valid for as
300
+ long as you offer spare parts or customer support for that product
301
+ model, to give anyone who possesses the object code either (1) a
302
+ copy of the Corresponding Source for all the software in the
303
+ product that is covered by this License, on a durable physical
304
+ medium customarily used for software interchange, for a price no
305
+ more than your reasonable cost of physically performing this
306
+ conveying of source, or (2) access to copy the
307
+ Corresponding Source from a network server at no charge.
308
+
309
+ c) Convey individual copies of the object code with a copy of the
310
+ written offer to provide the Corresponding Source. This
311
+ alternative is allowed only occasionally and noncommercially, and
312
+ only if you received the object code with such an offer, in accord
313
+ with subsection 6b.
314
+
315
+ d) Convey the object code by offering access from a designated
316
+ place (gratis or for a charge), and offer equivalent access to the
317
+ Corresponding Source in the same way through the same place at no
318
+ further charge. You need not require recipients to copy the
319
+ Corresponding Source along with the object code. If the place to
320
+ copy the object code is a network server, the Corresponding Source
321
+ may be on a different server (operated by you or a third party)
322
+ that supports equivalent copying facilities, provided you maintain
323
+ clear directions next to the object code saying where to find the
324
+ Corresponding Source. Regardless of what server hosts the
325
+ Corresponding Source, you remain obligated to ensure that it is
326
+ available for as long as needed to satisfy these requirements.
327
+
328
+ e) Convey the object code using peer-to-peer transmission, provided
329
+ you inform other peers where the object code and Corresponding
330
+ Source of the work are being offered to the general public at no
331
+ charge under subsection 6d.
332
+
333
+ A separable portion of the object code, whose source code is excluded
334
+ from the Corresponding Source as a System Library, need not be
335
+ included in conveying the object code work.
336
+
337
+ A "User Product" is either (1) a "consumer product", which means any
338
+ tangible personal property which is normally used for personal, family,
339
+ or household purposes, or (2) anything designed or sold for incorporation
340
+ into a dwelling. In determining whether a product is a consumer product,
341
+ doubtful cases shall be resolved in favor of coverage. For a particular
342
+ product received by a particular user, "normally used" refers to a
343
+ typical or common use of that class of product, regardless of the status
344
+ of the particular user or of the way in which the particular user
345
+ actually uses, or expects or is expected to use, the product. A product
346
+ is a consumer product regardless of whether the product has substantial
347
+ commercial, industrial or non-consumer uses, unless such uses represent
348
+ the only significant mode of use of the product.
349
+
350
+ "Installation Information" for a User Product means any methods,
351
+ procedures, authorization keys, or other information required to install
352
+ and execute modified versions of a covered work in that User Product from
353
+ a modified version of its Corresponding Source. The information must
354
+ suffice to ensure that the continued functioning of the modified object
355
+ code is in no case prevented or interfered with solely because
356
+ modification has been made.
357
+
358
+ If you convey an object code work under this section in, or with, or
359
+ specifically for use in, a User Product, and the conveying occurs as
360
+ part of a transaction in which the right of possession and use of the
361
+ User Product is transferred to the recipient in perpetuity or for a
362
+ fixed term (regardless of how the transaction is characterized), the
363
+ Corresponding Source conveyed under this section must be accompanied
364
+ by the Installation Information. But this requirement does not apply
365
+ if neither you nor any third party retains the ability to install
366
+ modified object code on the User Product (for example, the work has
367
+ been installed in ROM).
368
+
369
+ The requirement to provide Installation Information does not include a
370
+ requirement to continue to provide support service, warranty, or updates
371
+ for a work that has been modified or installed by the recipient, or for
372
+ the User Product in which it has been modified or installed. Access to a
373
+ network may be denied when the modification itself materially and
374
+ adversely affects the operation of the network or violates the rules and
375
+ protocols for communication across the network.
376
+
377
+ Corresponding Source conveyed, and Installation Information provided,
378
+ in accord with this section must be in a format that is publicly
379
+ documented (and with an implementation available to the public in
380
+ source code form), and must require no special password or key for
381
+ unpacking, reading or copying.
382
+
383
+ 7. Additional Terms.
384
+
385
+ "Additional permissions" are terms that supplement the terms of this
386
+ License by making exceptions from one or more of its conditions.
387
+ Additional permissions that are applicable to the entire Program shall
388
+ be treated as though they were included in this License, to the extent
389
+ that they are valid under applicable law. If additional permissions
390
+ apply only to part of the Program, that part may be used separately
391
+ under those permissions, but the entire Program remains governed by
392
+ this License without regard to the additional permissions.
393
+
394
+ When you convey a copy of a covered work, you may at your option
395
+ remove any additional permissions from that copy, or from any part of
396
+ it. (Additional permissions may be written to require their own
397
+ removal in certain cases when you modify the work.) You may place
398
+ additional permissions on material, added by you to a covered work,
399
+ for which you have or can give appropriate copyright permission.
400
+
401
+ Notwithstanding any other provision of this License, for material you
402
+ add to a covered work, you may (if authorized by the copyright holders of
403
+ that material) supplement the terms of this License with terms:
404
+
405
+ a) Disclaiming warranty or limiting liability differently from the
406
+ terms of sections 15 and 16 of this License; or
407
+
408
+ b) Requiring preservation of specified reasonable legal notices or
409
+ author attributions in that material or in the Appropriate Legal
410
+ Notices displayed by works containing it; or
411
+
412
+ c) Prohibiting misrepresentation of the origin of that material, or
413
+ requiring that modified versions of such material be marked in
414
+ reasonable ways as different from the original version; or
415
+
416
+ d) Limiting the use for publicity purposes of names of licensors or
417
+ authors of the material; or
418
+
419
+ e) Declining to grant rights under trademark law for use of some
420
+ trade names, trademarks, or service marks; or
421
+
422
+ f) Requiring indemnification of licensors and authors of that
423
+ material by anyone who conveys the material (or modified versions of
424
+ it) with contractual assumptions of liability to the recipient, for
425
+ any liability that these contractual assumptions directly impose on
426
+ those licensors and authors.
427
+
428
+ All other non-permissive additional terms are considered "further
429
+ restrictions" within the meaning of section 10. If the Program as you
430
+ received it, or any part of it, contains a notice stating that it is
431
+ governed by this License along with a term that is a further
432
+ restriction, you may remove that term. If a license document contains
433
+ a further restriction but permits relicensing or conveying under this
434
+ License, you may add to a covered work material governed by the terms
435
+ of that license document, provided that the further restriction does
436
+ not survive such relicensing or conveying.
437
+
438
+ If you add terms to a covered work in accord with this section, you
439
+ must place, in the relevant source files, a statement of the
440
+ additional terms that apply to those files, or a notice indicating
441
+ where to find the applicable terms.
442
+
443
+ Additional terms, permissive or non-permissive, may be stated in the
444
+ form of a separately written license, or stated as exceptions;
445
+ the above requirements apply either way.
446
+
447
+ 8. Termination.
448
+
449
+ You may not propagate or modify a covered work except as expressly
450
+ provided under this License. Any attempt otherwise to propagate or
451
+ modify it is void, and will automatically terminate your rights under
452
+ this License (including any patent licenses granted under the third
453
+ paragraph of section 11).
454
+
455
+ However, if you cease all violation of this License, then your
456
+ license from a particular copyright holder is reinstated (a)
457
+ provisionally, unless and until the copyright holder explicitly and
458
+ finally terminates your license, and (b) permanently, if the copyright
459
+ holder fails to notify you of the violation by some reasonable means
460
+ prior to 60 days after the cessation.
461
+
462
+ Moreover, your license from a particular copyright holder is
463
+ reinstated permanently if the copyright holder notifies you of the
464
+ violation by some reasonable means, this is the first time you have
465
+ received notice of violation of this License (for any work) from that
466
+ copyright holder, and you cure the violation prior to 30 days after
467
+ your receipt of the notice.
468
+
469
+ Termination of your rights under this section does not terminate the
470
+ licenses of parties who have received copies or rights from you under
471
+ this License. If your rights have been terminated and not permanently
472
+ reinstated, you do not qualify to receive new licenses for the same
473
+ material under section 10.
474
+
475
+ 9. Acceptance Not Required for Having Copies.
476
+
477
+ You are not required to accept this License in order to receive or
478
+ run a copy of the Program. Ancillary propagation of a covered work
479
+ occurring solely as a consequence of using peer-to-peer transmission
480
+ to receive a copy likewise does not require acceptance. However,
481
+ nothing other than this License grants you permission to propagate or
482
+ modify any covered work. These actions infringe copyright if you do
483
+ not accept this License. Therefore, by modifying or propagating a
484
+ covered work, you indicate your acceptance of this License to do so.
485
+
486
+ 10. Automatic Licensing of Downstream Recipients.
487
+
488
+ Each time you convey a covered work, the recipient automatically
489
+ receives a license from the original licensors, to run, modify and
490
+ propagate that work, subject to this License. You are not responsible
491
+ for enforcing compliance by third parties with this License.
492
+
493
+ An "entity transaction" is a transaction transferring control of an
494
+ organization, or substantially all assets of one, or subdividing an
495
+ organization, or merging organizations. If propagation of a covered
496
+ work results from an entity transaction, each party to that
497
+ transaction who receives a copy of the work also receives whatever
498
+ licenses to the work the party's predecessor in interest had or could
499
+ give under the previous paragraph, plus a right to possession of the
500
+ Corresponding Source of the work from the predecessor in interest, if
501
+ the predecessor has it or can get it with reasonable efforts.
502
+
503
+ You may not impose any further restrictions on the exercise of the
504
+ rights granted or affirmed under this License. For example, you may
505
+ not impose a license fee, royalty, or other charge for exercise of
506
+ rights granted under this License, and you may not initiate litigation
507
+ (including a cross-claim or counterclaim in a lawsuit) alleging that
508
+ any patent claim is infringed by making, using, selling, offering for
509
+ sale, or importing the Program or any portion of it.
510
+
511
+ 11. Patents.
512
+
513
+ A "contributor" is a copyright holder who authorizes use under this
514
+ License of the Program or a work on which the Program is based. The
515
+ work thus licensed is called the contributor's "contributor version".
516
+
517
+ A contributor's "essential patent claims" are all patent claims
518
+ owned or controlled by the contributor, whether already acquired or
519
+ hereafter acquired, that would be infringed by some manner, permitted
520
+ by this License, of making, using, or selling its contributor version,
521
+ but do not include claims that would be infringed only as a
522
+ consequence of further modification of the contributor version. For
523
+ purposes of this definition, "control" includes the right to grant
524
+ patent sublicenses in a manner consistent with the requirements of
525
+ this License.
526
+
527
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
528
+ patent license under the contributor's essential patent claims, to
529
+ make, use, sell, offer for sale, import and otherwise run, modify and
530
+ propagate the contents of its contributor version.
531
+
532
+ In the following three paragraphs, a "patent license" is any express
533
+ agreement or commitment, however denominated, not to enforce a patent
534
+ (such as an express permission to practice a patent or covenant not to
535
+ sue for patent infringement). To "grant" such a patent license to a
536
+ party means to make such an agreement or commitment not to enforce a
537
+ patent against the party.
538
+
539
+ If you convey a covered work, knowingly relying on a patent license,
540
+ and the Corresponding Source of the work is not available for anyone
541
+ to copy, free of charge and under the terms of this License, through a
542
+ publicly available network server or other readily accessible means,
543
+ then you must either (1) cause the Corresponding Source to be so
544
+ available, or (2) arrange to deprive yourself of the benefit of the
545
+ patent license for this particular work, or (3) arrange, in a manner
546
+ consistent with the requirements of this License, to extend the patent
547
+ license to downstream recipients. "Knowingly relying" means you have
548
+ actual knowledge that, but for the patent license, your conveying the
549
+ covered work in a country, or your recipient's use of the covered work
550
+ in a country, would infringe one or more identifiable patents in that
551
+ country that you have reason to believe are valid.
552
+
553
+ If, pursuant to or in connection with a single transaction or
554
+ arrangement, you convey, or propagate by procuring conveyance of, a
555
+ covered work, and grant a patent license to some of the parties
556
+ receiving the covered work authorizing them to use, propagate, modify
557
+ or convey a specific copy of the covered work, then the patent license
558
+ you grant is automatically extended to all recipients of the covered
559
+ work and works based on it.
560
+
561
+ A patent license is "discriminatory" if it does not include within
562
+ the scope of its coverage, prohibits the exercise of, or is
563
+ conditioned on the non-exercise of one or more of the rights that are
564
+ specifically granted under this License. You may not convey a covered
565
+ work if you are a party to an arrangement with a third party that is
566
+ in the business of distributing software, under which you make payment
567
+ to the third party based on the extent of your activity of conveying
568
+ the work, and under which the third party grants, to any of the
569
+ parties who would receive the covered work from you, a discriminatory
570
+ patent license (a) in connection with copies of the covered work
571
+ conveyed by you (or copies made from those copies), or (b) primarily
572
+ for and in connection with specific products or compilations that
573
+ contain the covered work, unless you entered into that arrangement,
574
+ or that patent license was granted, prior to 28 March 2007.
575
+
576
+ Nothing in this License shall be construed as excluding or limiting
577
+ any implied license or other defenses to infringement that may
578
+ otherwise be available to you under applicable patent law.
579
+
580
+ 12. No Surrender of Others' Freedom.
581
+
582
+ If conditions are imposed on you (whether by court order, agreement or
583
+ otherwise) that contradict the conditions of this License, they do not
584
+ excuse you from the conditions of this License. If you cannot convey a
585
+ covered work so as to satisfy simultaneously your obligations under this
586
+ License and any other pertinent obligations, then as a consequence you may
587
+ not convey it at all. For example, if you agree to terms that obligate you
588
+ to collect a royalty for further conveying from those to whom you convey
589
+ the Program, the only way you could satisfy both those terms and this
590
+ License would be to refrain entirely from conveying the Program.
591
+
592
+ 13. Use with the GNU Affero General Public License.
593
+
594
+ Notwithstanding any other provision of this License, you have
595
+ permission to link or combine any covered work with a work licensed
596
+ under version 3 of the GNU Affero General Public License into a single
597
+ combined work, and to convey the resulting work. The terms of this
598
+ License will continue to apply to the part which is the covered work,
599
+ but the special requirements of the GNU Affero General Public License,
600
+ section 13, concerning interaction through a network will apply to the
601
+ combination as such.
602
+
603
+ 14. Revised Versions of this License.
604
+
605
+ The Free Software Foundation may publish revised and/or new versions of
606
+ the GNU General Public License from time to time. Such new versions will
607
+ be similar in spirit to the present version, but may differ in detail to
608
+ address new problems or concerns.
609
+
610
+ Each version is given a distinguishing version number. If the
611
+ Program specifies that a certain numbered version of the GNU General
612
+ Public License "or any later version" applies to it, you have the
613
+ option of following the terms and conditions either of that numbered
614
+ version or of any later version published by the Free Software
615
+ Foundation. If the Program does not specify a version number of the
616
+ GNU General Public License, you may choose any version ever published
617
+ by the Free Software Foundation.
618
+
619
+ If the Program specifies that a proxy can decide which future
620
+ versions of the GNU General Public License can be used, that proxy's
621
+ public statement of acceptance of a version permanently authorizes you
622
+ to choose that version for the Program.
623
+
624
+ Later license versions may give you additional or different
625
+ permissions. However, no additional obligations are imposed on any
626
+ author or copyright holder as a result of your choosing to follow a
627
+ later version.
628
+
629
+ 15. Disclaimer of Warranty.
630
+
631
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
632
+ APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
633
+ HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
634
+ OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
635
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
636
+ PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
637
+ IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
638
+ ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
639
+
640
+ 16. Limitation of Liability.
641
+
642
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
643
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
644
+ THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
645
+ GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
646
+ USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
647
+ DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
648
+ PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
649
+ EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
650
+ SUCH DAMAGES.
651
+
652
+ 17. Interpretation of Sections 15 and 16.
653
+
654
+ If the disclaimer of warranty and limitation of liability provided
655
+ above cannot be given local legal effect according to their terms,
656
+ reviewing courts shall apply local law that most closely approximates
657
+ an absolute waiver of all civil liability in connection with the
658
+ Program, unless a warranty or assumption of liability accompanies a
659
+ copy of the Program in return for a fee.
660
+
lib/lessphp/README.md ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # lessphp v0.3.3
2
+ ### <http://leafo.net/lessphp>
3
+
4
+ `lessphp` is a compiler for LESS written in PHP. The documentation is great,
5
+ so check it out: <http://leafo.net/lessphp/docs/>.
6
+
7
+ Here's a quick tutorial:
8
+
9
+ ### How to use in your PHP project
10
+
11
+ Copy `lessc.inc.php` to your include directory and include it into your project.
12
+
13
+ There are a few ways to interface with the compiler. The easiest is to have it
14
+ compile a LESS file when the page is requested. The static function
15
+ `lessc::ccompile`, checked compile, will compile the input LESS file only when it
16
+ is newer than the output file.
17
+
18
+ try {
19
+ lessc::ccompile('input.less', 'output.css');
20
+ } catch (exception $ex) {
21
+ exit($ex->getMessage());
22
+ }
23
+
24
+ `lessc::ccompile` is not aware of imported files that change. Read [about
25
+ `lessc::cexecute`](http://leafo.net/lessphp/docs/#compiling_automatically).
26
+
27
+ Note that all failures with lessc are reported through exceptions.
28
+ If you need more control you can make your own instance of lessc.
29
+
30
+ $input = 'mystyle.less';
31
+
32
+ $lc = new lessc($input);
33
+
34
+ try {
35
+ file_put_contents('mystyle.css', $lc->parse());
36
+ } catch (exception $ex) { ... }
37
+
38
+ In addition to loading from file, you can also parse from a string like so:
39
+
40
+ $lc = new lessc();
41
+ $lesscode = 'body { ... }';
42
+ $out = $lc->parse($lesscode);
43
+
44
+ ### How to use from the command line
45
+
46
+ An additional script has been included to use the compiler from the command
47
+ line. In the simplest invocation, you specify an input file and the compiled
48
+ css is written to standard out:
49
+
50
+ $ plessc input.less > output.css
51
+
52
+ Using the -r flag, you can specify LESS code directly as an argument or, if
53
+ the argument is left off, from standard in:
54
+
55
+ $ plessc -r "my less code here"
56
+
57
+ Finally, by using the -w flag you can watch a specified input file and have it
58
+ compile as needed to the output file
59
+
60
+ $ plessc -w input-file output-file
61
+
62
+ Errors from watch mode are written to standard out.
63
+
64
+
lib/lessphp/docs/docs.md ADDED
@@ -0,0 +1,992 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ title: v0.3.3 documentation
2
+ link_to_home: true
3
+ --
4
+
5
+ <h2 skip="true">Documentation v0.3.3</h2>
6
+
7
+ <div style="margin-bottom: 1em;">$index</div>
8
+
9
+ **lessphp** is a compiler that generates CSS from a superset language which
10
+ adds a collection of convenient features often seen in other languages. All CSS
11
+ is compatible with LESS, so you can start using new features with your existing CSS.
12
+
13
+ It is designed to be compatible with [less.js](http://lesscss.org), and suitable
14
+ as a drop in replacement for PHP projects.
15
+
16
+ ## Getting Started
17
+
18
+ The homepage for **lessphp** can be found at [http://leafo.net/lessphp/][1].
19
+
20
+ You can follow development at the project's [GitHub][2].
21
+
22
+ Including **lessphp** in your project is as simple as dropping the single
23
+ include file into your code base and running the appropriate compile method as
24
+ described in the [PHP Interface](#php_interface).
25
+
26
+ [1]: http://leafo.net/lessphp "lessphp homepage"
27
+ [2]: https://github.com/leafo/lessphp "lessphp GitHub page"
28
+
29
+ ## Installation
30
+
31
+ **lessphp** is distributed entirely in a single stand-alone file. Download the
32
+ latest version from either [the homepage][1] or [GitHub][2].
33
+
34
+ Development versions can also be downloading from GitHub.
35
+
36
+ Place `lessphp.inc.php` in a location available to your PHP scripts, and
37
+ include it. That's it! you're ready to begin.
38
+
39
+ ## The Language
40
+
41
+ **lessphp** is very easy to learn because it generally functions how you would
42
+ expect it to. If you feel something is challenging or missing, feel free to
43
+ open an issue on the [bug tracker](https://github.com/leafo/lessphp/issues).
44
+
45
+ It is also easy to learn because any standards-compliant CSS code is valid LESS
46
+ code. You are free to gradually enhance your existing CSS code base with LESS
47
+ features without having to worry about rewriting anything.
48
+
49
+ The following is a description of the new languages features provided by LESS.
50
+
51
+ ### Line Comments
52
+
53
+ Simple but very useful; line comments are started with `//`:
54
+
55
+ ```less
56
+ // this is a comment
57
+ body {
58
+ color: red; // as is this
59
+ /* block comments still work also */
60
+ }
61
+ ```
62
+
63
+ ### Variables
64
+ Variables are identified with a name that starts with `@`. To declare a
65
+ variable, you create an appropriately named CSS property and assign it a value:
66
+
67
+ ```less
68
+ @family: "verdana";
69
+ @color: red;
70
+ body {
71
+ @mycolor: red;
72
+ font-family: @family;
73
+ color: @color;
74
+ border-bottom: 1px solid @color;
75
+ }
76
+ ```
77
+
78
+ Variable declarations will not appear in the output. Variables can be declared
79
+ in the outer most scope of the file, or anywhere else a CSS property may
80
+ appear. They can hold any CSS property value.
81
+
82
+ Variables are only visible for use from their current scope, or any enclosed
83
+ scopes.
84
+
85
+ If you have a string or keyword in a variable, you can reference another
86
+ variable by that name by repeating the `@`:
87
+
88
+ ```less
89
+ @value: 20px;
90
+ @value_name: "value";
91
+
92
+ width: @@value_name;
93
+ ```
94
+
95
+ ### Expressions
96
+
97
+ Expressions let you combine values and variables in meaningful ways. For
98
+ example you can add to a color to make it a different shade. Or divide up the
99
+ width of your layout logically. You can even concatenate strings.
100
+
101
+ Use the mathematical operators to evaluate an expression:
102
+
103
+ ```less
104
+ @width: 960px;
105
+ .nav {
106
+ width: @width / 3;
107
+ color: #001 + #abc;
108
+ }
109
+ .body {
110
+ width: 2 * @width / 3;
111
+ font-family: "hel" + "vetica";
112
+ }
113
+ ```
114
+
115
+ Parentheses can be used to control the order of evaluation. They can also be
116
+ used to force an evaluation for cases where CSS's syntax makes the expression
117
+ ambiguous.
118
+
119
+ The following property will produce two numbers, instead of doing the
120
+ subtraction:
121
+
122
+ ```less
123
+ margin: 10px -5px;
124
+ ```
125
+
126
+ To force the subtraction:
127
+
128
+ ```less
129
+ margin: (10px -5px);
130
+ ```
131
+
132
+ It is also safe to surround mathematical operators by spaces to ensure that
133
+ they are evaluated:
134
+
135
+ ```less
136
+ margin: 10px - 5px;
137
+ ```
138
+
139
+ Division has a special quirk. There are certain CSS properties that use the `/`
140
+ operator as part of their value's syntax. Namely, the [font][4] shorthand and
141
+ [border-radius][3].
142
+
143
+ [3]: https://developer.mozilla.org/en/CSS/border-radius
144
+ [4]: https://developer.mozilla.org/en/CSS/font
145
+
146
+
147
+ Thus, **lessphp** will ignore any division in these properties unless it is
148
+ wrapped in parentheses. For example, no division will take place here:
149
+
150
+ ```less
151
+ .font {
152
+ font: 20px/80px "Times New Roman";
153
+ }
154
+ ```
155
+
156
+ In order to force division we must wrap the expression in parentheses:
157
+
158
+ ```less
159
+ .font {
160
+ font: (20px/80px) "Times New Roman";
161
+ }
162
+ ```
163
+
164
+ If you want to write a literal `/` expression without dividing in another
165
+ property (or a variable), you can use [string unquoting](#string_unquoting):
166
+
167
+ ```less
168
+ .var {
169
+ @size: ~"20px/80px";
170
+ font: @size sans-serif;
171
+ }
172
+ ```
173
+
174
+ ### Nested Blocks
175
+
176
+ By nesting blocks we can build up a chain of CSS selectors through scope
177
+ instead of repeating them. In addition to reducing repetition, this also helps
178
+ logically organize the structure of our CSS.
179
+
180
+ ```less
181
+ ol.list {
182
+ li.special {
183
+ border: 1px solid red;
184
+ }
185
+
186
+ li.plain {
187
+ font-weight: bold;
188
+ }
189
+ }
190
+ ```
191
+
192
+
193
+ This will produce two blocks, a `ol.list li.special` and `ol.list li.plain`.
194
+
195
+ Blocks can be nested as deep as required in order to build a hierarchy of
196
+ relationships.
197
+
198
+ The `&` operator can be used in a selector to represent its parent's selector.
199
+ If the `&` operator is used, then the default action of appending the parent to
200
+ the front of the child selector separated by space is not performed.
201
+
202
+ ```less
203
+ b {
204
+ a & {
205
+ color: red;
206
+ }
207
+
208
+ // the following have the same effect
209
+
210
+ & i {
211
+ color: blue;
212
+ }
213
+
214
+ i {
215
+ color: blue;
216
+ }
217
+ }
218
+ ```
219
+
220
+
221
+ Because the `&` operator respects the whitespace around it, we can use it to
222
+ control how the child blocks are joined. Consider the differences between the
223
+ following:
224
+
225
+ ```less
226
+ div {
227
+ .child-class { color: purple; }
228
+
229
+ &.isa-class { color: green; }
230
+
231
+ #child-id { height: 200px; }
232
+
233
+ &#div-id { height: 400px; }
234
+
235
+ &:hover { color: red; }
236
+
237
+ :link { color: blue; }
238
+ }
239
+ ```
240
+
241
+ The `&` operator also works with [mixins](#mixins), which produces interesting results:
242
+
243
+ ```less
244
+ .within_box_style() {
245
+ .box & {
246
+ color: blue;
247
+ }
248
+ }
249
+
250
+ #menu {
251
+ .within_box_style;
252
+ }
253
+ ```
254
+
255
+ ### Mixins
256
+
257
+ Any block can be mixed in just by naming it:
258
+
259
+ ```less
260
+ .mymixin {
261
+ color: blue;
262
+ border: 1px solid red;
263
+
264
+ .special {
265
+ font-weight: bold;
266
+ }
267
+ }
268
+
269
+
270
+ h1 {
271
+ font-size: 200px;
272
+ .mixin;
273
+ }
274
+ ```
275
+
276
+ All properties and child blocks are mixed in.
277
+
278
+ Mixins can be made parametric, meaning they can take arguments, in order to
279
+ enhance their utility. A parametric mixin all by itself is not outputted when
280
+ compiled. Its properties will only appear when mixed into another block.
281
+
282
+ The canonical example is to create a rounded corners mixin that works across
283
+ browsers:
284
+
285
+ ```less
286
+ .rounded-corners(@radius: 5px) {
287
+ border-radius: @radius;
288
+ -webkit-border-radius: @radius;
289
+ -moz-border-radius: @radius;
290
+ }
291
+
292
+ .header {
293
+ .rounded-corners();
294
+ }
295
+
296
+ .info {
297
+ background: red;
298
+ .rounded-corners(14px);
299
+ }
300
+ ```
301
+
302
+ If you have a mixin that doesn't have any arguments, but you don't want it to
303
+ show up in the output, give it a blank argument list:
304
+
305
+ ```less
306
+ .secret() {
307
+ font-size: 6000px;
308
+ }
309
+
310
+ .div {
311
+ .secret;
312
+ }
313
+ ```
314
+
315
+ If the mixin doesn't need any arguments, you can leave off the parentheses when
316
+ mixing it in, as seen above.
317
+
318
+ You can also mixin a block that is nested inside other blocks. You can think of
319
+ the outer block as a way of making a scope for your mixins. You just list the
320
+ names of the mixins separated by spaces, which describes the path to the mixin
321
+ you want to include. Optionally you can separate them by `>`.
322
+
323
+ ```less
324
+ .my_scope {
325
+ .some_color {
326
+ color: red;
327
+ .inner_block {
328
+ text-decoration: underline;
329
+ }
330
+ }
331
+ .bold {
332
+ font-weight: bold;
333
+ color: blue;
334
+ }
335
+ }
336
+
337
+ .a_block {
338
+ .my_scope .some_color;
339
+ .my_scope .some_color .inner_block;
340
+ }
341
+
342
+ .another_block {
343
+ // the alternative syntax
344
+ .my_scope > .bold;
345
+ }
346
+ ```
347
+
348
+ #### `@arguments` Variable
349
+
350
+ Within an mixin there is a special variable named `@arguments` that contains
351
+ all the arguments passed to the mixin along with any remaining arguments that
352
+ have default values. The value of the variable has all the values separated by
353
+ spaces.
354
+
355
+ This useful for quickly assigning all the arguments:
356
+
357
+ ```less
358
+ .box-shadow(@x, @y, @blur, @color) {
359
+ box-shadow: @arguments;
360
+ -webkit-box-shadow: @arguments;
361
+ -moz-box-shadow: @arguments;
362
+ }
363
+ .menu {
364
+ .box-shadow(1px, 1px, 5px, #aaa);
365
+ }
366
+ ```
367
+
368
+ In addition to the arguments passed to the mixin, `@arguments` will also include
369
+ remaining default values assigned by the mixin:
370
+
371
+
372
+ ```less
373
+ .border-mixin(@width, @style: solid, @color: black) {
374
+ border: @arguments;
375
+ }
376
+
377
+ pre {
378
+ .border-mixin(4px, dotted);
379
+ }
380
+
381
+ ```
382
+
383
+
384
+ #### Pattern Matching
385
+
386
+ When you *mix in* a mixin, all the available mixins of that name in the current
387
+ scope are checked to see if they match based on what was passed to the mixin
388
+ and how it was declared.
389
+
390
+ The simplest case is matching by number of arguments. Only the mixins that
391
+ match the number of arguments passed in are used, with the exception of 0
392
+ argument mixins, which are always included.
393
+
394
+ ```less
395
+ .simple() { // no argument mixin always included
396
+ height: 10px;
397
+ }
398
+
399
+ .simple(@a, @b) {
400
+ color: red;
401
+ }
402
+
403
+ .simple(@a) {
404
+ color: blue;
405
+ }
406
+
407
+ div {
408
+ .simple(10);
409
+ }
410
+
411
+ span {
412
+ .simple(10, 20);
413
+ }
414
+ ```
415
+
416
+ Another way of controlling whether a mixin matches is by specifying a value in
417
+ place of an argument name when declaring the mixin:
418
+
419
+ ```less
420
+ .style(old, @size) {
421
+ font: @size serif;
422
+ }
423
+
424
+ .style(new, @size) {
425
+ font: @size sans-serif;
426
+ }
427
+
428
+ .style(@_, @size) {
429
+ letter-spacing: floor(@size / 6px);
430
+ }
431
+
432
+ em {
433
+ @switch: old;
434
+ .style(@switch, 15px);
435
+ }
436
+ ```
437
+
438
+ Notice that two of the three mixins were matched. The mixin with a matching
439
+ first argument, and the generic mixin that matches two arguments. It's common
440
+ to use `@_` as the name of a variable we intend to not use. It has no special
441
+ meaning to LESS, just to the reader of the code.
442
+
443
+ #### Guards
444
+
445
+ Another way of restricting when a mixin is mixed in is by using guards. A guard
446
+ is a special expression that is associated with a mixin declaration that is
447
+ evaluated during the mixin process. It must evaluate to true before the mixin
448
+ can be used.
449
+
450
+ We use the `when` keyword to begin describing a list of guard expressions.
451
+
452
+ Here's a simple example:
453
+
454
+ ```less
455
+ .guarded(@arg) when (@arg = hello) {
456
+ color: blue;
457
+ }
458
+
459
+ div {
460
+ .guarded(hello); // match
461
+ }
462
+
463
+ span {
464
+ .guarded(world); // no match
465
+ }
466
+ ```
467
+ Only the `div`'s mixin will match in this case, because the guard expression
468
+ requires that `@arg` is equal to `hello`.
469
+
470
+ We can include many different guard expressions by separating them by commas.
471
+ Only one of them needs to match to trigger the mixin:
472
+
473
+ ```less
474
+ .x(@a, @b) when (@a = hello), (@b = world) {
475
+ width: 960px;
476
+ }
477
+
478
+ div {
479
+ .x(hello, bar); // match
480
+ }
481
+
482
+ span {
483
+ .x(foo, world); // match
484
+ }
485
+
486
+ pre {
487
+ .x(foo, bar); // no match
488
+ }
489
+ ```
490
+
491
+ Instead of a comma, we can use `and` keyword to make it so all of the guards
492
+ must match in order to trigger the mixin. `and` has higher precedence than the
493
+ comma.
494
+
495
+ ```less
496
+ .y(@a, @b) when (@a = hello) and (@b = world) {
497
+ height: 600px;
498
+ }
499
+
500
+ div {
501
+ .y(hello, world); // match
502
+ }
503
+
504
+ span {
505
+ .y(hello, bar); // no match
506
+ }
507
+ ```
508
+
509
+ Commas and `and`s can be mixed and matched.
510
+
511
+ You can also negate a guard expression by using `not` in from of the parentheses:
512
+
513
+ ```less
514
+ .x(@a) when not (@a = hello) {
515
+ color: blue;
516
+ }
517
+
518
+ div {
519
+ .x(hello); // no match
520
+ }
521
+ ```
522
+
523
+ The `=` operator is used to check equality between any two values. For numbers
524
+ the following comparison operators are also defined:
525
+
526
+ `<`, `>`, `=<`, `>=`
527
+
528
+ There is also a collection of predicate functions that can be used to test the
529
+ type of a value.
530
+
531
+ These are `isnumber`, `iscolor`, `iskeyword`, `isstring`, `ispixel`,
532
+ `ispercentage` and `isem`.
533
+
534
+ ```less
535
+ .mix(@a) when (ispercentage(@a)) {
536
+ height: 500px * @a;
537
+ }
538
+ .mix(@a) when (ispixel(@a)) {
539
+ height: @a;
540
+ }
541
+
542
+ div.a {
543
+ .mix(50%);
544
+ }
545
+
546
+ div.a {
547
+ .mix(350px);
548
+ }
549
+ ```
550
+
551
+ ### Import
552
+
553
+ Multiple LESS files can be compiled into a single CSS file by using the
554
+ `@import` statement. Be careful, the LESS import statement shares syntax with
555
+ the CSS import statement. If the file being imported ends in a `.less`
556
+ extension, or no extension, then it is treated as a LESS import. Otherwise it
557
+ is left alone and outputted directly:
558
+
559
+ ```less
560
+ // my_file.less
561
+ .some-mixin(@height) {
562
+ height: @height;
563
+ }
564
+
565
+ // main.less
566
+ @import "main.less" // will import the file if it can be found
567
+ @import "main.css" // will be left alone
568
+
569
+ body {
570
+ .some-mixin(400px);
571
+ }
572
+ ```
573
+
574
+ All of the following lines are valid ways to import the same file:
575
+
576
+ ```less
577
+ @import "file";
578
+ @import 'file.less';
579
+ @import url("file");
580
+ @import url('file');
581
+ @import url(file);
582
+ ```
583
+
584
+ When importing, the `importDir` is searched for files. This can be configured,
585
+ see [PHP Interface](#php_interface).
586
+
587
+ ### String Interpolation
588
+
589
+ String interpolation is a convenient way to insert the value of a variable
590
+ right into a string literal. Given some variable named `@var_name`, you just
591
+ need to write it as `@{var_name}` from within the string to have its value
592
+ inserted:
593
+
594
+ ```less
595
+ @symbol: ">";
596
+ h1:before {
597
+ content: "@{symbol}: ";
598
+ }
599
+
600
+ h2:before {
601
+ content: "@{symbol}@{symbol}: ";
602
+ }
603
+ ```
604
+
605
+ There are two kinds of strings, implicit and explicit strings. Explicit strings
606
+ are wrapped by double quotes, `"hello I am a string"`, or single quotes `'I am
607
+ another string'`. Implicit strings only appear when using `url()`. The text
608
+ between the parentheses is considered a string and thus string interpolation is
609
+ possible:
610
+
611
+ ```less
612
+ @path: "files/";
613
+ body {
614
+ background: url(@{path}my_background.png);
615
+ }
616
+ ```
617
+
618
+ ### String Format Function
619
+
620
+ The `%` function can be used to insert values into strings using a *format
621
+ string*. It works similar to `printf` seen in other languages. It has the
622
+ same purpose as string interpolation above, but gives explicit control over
623
+ the output format.
624
+
625
+ ```less
626
+ @symbol: ">";
627
+ h1:before {
628
+ content: %("%s: ", @symbol);
629
+ }
630
+ ```
631
+
632
+ The `%` function takes as its first argument the format string, following any
633
+ number of addition arguments that are inserted in place of the format
634
+ directives.
635
+
636
+ A format directive starts with a `%` and is followed by a single character that
637
+ is either `a`, `d`, or `s`:
638
+
639
+ ```less
640
+ strings: %("%a %d %s %a", hi, 1, 'ok', 'cool');
641
+ ```
642
+
643
+ `%a` and `%d` format the value the same way: they compile the argument to its
644
+ CSS value and insert it directly. When used with a string, the quotes are
645
+ included in the output. This typically isn't what we want, so we have the `%s`
646
+ format directive which strips quotes from strings before inserting them.
647
+
648
+ The `%d` directive functions the same as `%a`, but is typically used for numbers
649
+ assuming the output format of numbers might change in the future.
650
+
651
+ ### String Unquoting
652
+
653
+ Sometimes you will need to write proprietary CSS syntax that is unable to be
654
+ parsed. As a workaround you can place the code into a string and unquote it.
655
+ Unquoting is the process of outputting a string without its surrounding quotes.
656
+ There are two ways to unquote a string.
657
+
658
+ The `~` operator in front of a string will unquote that string:
659
+
660
+ ```less
661
+ .class {
662
+ // a made up, but problematic vendor specific CSS
663
+ filter: ~"Microsoft.AlphaImage(src='image.png')";
664
+ }
665
+ ```
666
+
667
+ If you are working with other types, such as variables, there is a built in
668
+ function that let's you unquote any value. It is called `e`.
669
+
670
+ ```less
671
+ @color: "red";
672
+ .class {
673
+ color: e(@color);
674
+ }
675
+ ```
676
+
677
+ ### Built In Functions
678
+
679
+ **lessphp** has a collection of built in functions:
680
+
681
+ * `e(str)` -- returns a string without the surrounding quotes.
682
+ See [String Unquoting](#string_unquoting)
683
+
684
+ * `floor(number)` -- returns the floor of a numerical input
685
+ * `round(number)` -- returns the rounded value of numerical input
686
+
687
+ * `lighten(color, percent)` -- lightens `color` by `percent` and returns it
688
+ * `darken(color, percent)` -- darkens `color` by `percent` and returns it
689
+
690
+ * `saturate(color, percent)` -- saturates `color` by `percent` and returns it
691
+ * `desaturate(color, percent)` -- desaturates `color` by `percent` and returns it
692
+
693
+ * `fadein(color, percent)` -- makes `color` less transparent by `percent` and returns it
694
+ * `fadeout(color, percent)` -- makes `color` more transparent by `percent` and returns it
695
+
696
+ * `spin(color, amount)` -- returns a color with `amount` degrees added to hue
697
+
698
+ * `fade(color, amount)` -- returns a color with the alpha set to `amount`
699
+
700
+ * `hue(color)` -- returns the hue of `color`
701
+
702
+ * `saturation(color)` -- returns the saturation of `color`
703
+
704
+ * `lightness(color)` -- returns the lightness of `color`
705
+
706
+ * `alpha(color)` -- returns the alpha value of `color` or 1.0 if it doesn't have an alpha
707
+
708
+ * `percentage(number)` -- converts a floating point number to a percentage, e.g. `0.65` -> `65%`
709
+
710
+ * `mix(color1, color1, percent)` -- mixes two colors by percentage where 100%
711
+ keeps all of `color1`, and 0% keeps all of `color2`. Will take into account
712
+ the alpha of the colors if it exists. See
713
+ <http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_method>.
714
+
715
+ * `rgbahex(color)` -- returns a string containing 4 part hex color.
716
+
717
+ This is used to convert a CSS color into the hex format that IE's filter
718
+ method expects when working with an alpha component.
719
+
720
+ ```less
721
+ .class {
722
+ @start: rgbahex(rgba(25, 34, 23, .5));
723
+ @end: rgbahex(rgba(85, 74, 103, .6));
724
+ // abridged example
725
+ -ms-filter:
726
+ e("gradient(start=@{start},end=@{end})");
727
+ }
728
+ ```
729
+
730
+ ## PHP Interface
731
+
732
+ The PHP interface lets you control the compiler from your PHP scripts. There is
733
+ only one file to include to get access to everything:
734
+
735
+ ```php
736
+ <?php
737
+ include "lessc.inc.php";
738
+ ```
739
+
740
+ To compile a file to a string (of CSS code):
741
+
742
+ ```php
743
+ $less = new lessc("myfile.less");
744
+ $css = $less->parse();
745
+ ```
746
+
747
+ To compile a string to a string:
748
+
749
+ ```php
750
+ $less = new lessc(); // a blank lessc
751
+ $css = $less->parse("body { a { color: red } }");
752
+ ```
753
+
754
+ ### Compiling Automatically
755
+
756
+ Often, you want to write the compiled CSS to a file, and only recompile when
757
+ the original LESS file has changed. The following function will check if the
758
+ modification date of the LESS file is more recent than the CSS file. The LESS
759
+ file will be compiled if it is. If the CSS file doesn't exist yet, then it will
760
+ also compile the LESS file.
761
+
762
+ ```php
763
+ lessc::ccompile('myfile.less', 'mystyle.css');
764
+ ```
765
+
766
+ `ccompile` is very basic, it only checks if the input file's modification time.
767
+ It is not of any files that are brought in using `@import`.
768
+
769
+ For this reason we also have `lessc::cexecute`. It functions slightly
770
+ differently, but gives us the ability to check changes to all files used during
771
+ the compile. It takes one argument, either the name of the file we want to
772
+ compile, or an existing *cache object*. Its return value is an updated cache
773
+ object.
774
+
775
+ If we don't have a cache object, then we call the function with the name of the
776
+ file to get the initial cache object. If we do have a cache object, then we
777
+ call the function with it. In both cases, an updated cache object is returned.
778
+
779
+ The cache object keeps track of all the files that must be checked in order to
780
+ determine if a rebuild is required.
781
+
782
+ The cache object is a plain PHP `array`. It stores the last time it compiled in
783
+ `$cache['updated']` and output of the compile in `$cache['compiled']`.
784
+
785
+ Here we demonstrate creating an new cache object, then using it to see if we
786
+ have a recompiled version available to be written:
787
+
788
+
789
+ ```php
790
+ $less_file = 'myfile.less';
791
+ $css_file = 'myfile.css';
792
+
793
+ // create a new cache object, and compile
794
+ $cache = lessc::cexecute('myfile.less');
795
+ file_put_contents($css_file, $cache['compiled']);
796
+
797
+ // the next time we run, write only if it has updated
798
+ $last_updated = $cache['updated'];
799
+ $cache = lessc::cexecute($cache);
800
+ if ($cache['updated'] > $last_updated) {
801
+ file_put_contents($css_file, $cache['compiled']);
802
+ }
803
+
804
+ ```
805
+
806
+ In order for the system to fully work, we must save cache object between
807
+ requests. Because it's a plain PHP `array`, it's sufficient to
808
+ [`serialize`](http://php.net/serialize) it and save it the string somewhere
809
+ like a file or in persistent memory.
810
+
811
+ An example with saving cache object to a file:
812
+
813
+ ```php
814
+ function auto_compile_less($less_fname, $css_fname) {
815
+ // load the cache
816
+ $cache_fname = $less_fname.".cache";
817
+ if (file_exists($cache_fname)) {
818
+ $cache = unserialize(file_get_contents($cache_fname));
819
+ } else {
820
+ $cache = $less_fname;
821
+ }
822
+
823
+ $new_cache = lessc::cexecute($cache);
824
+ if (!is_array($cache) || $new_cache['updated'] > $cache['updated']) {
825
+ file_put_contents($cache_fname, serialize($new_cache));
826
+ file_put_contents($css_fname, $new_cache['compiled']);
827
+ }
828
+ }
829
+
830
+ auto_compile_less('myfile.less', 'myfile.css');
831
+ ```
832
+
833
+ `lessc:cexecute` takes an optional second argument, `$force`. Passing in true
834
+ will cause the input to always be recompiled.
835
+
836
+ ### Error Handling
837
+
838
+ All of the following methods will throw an `Exception` if the parsing fails:
839
+
840
+ ```php
841
+ $less = new lessc();
842
+ try {
843
+ $less->parse("} invalid LESS }}}");
844
+ } catch (Exception $ex) {
845
+ echo "lessphp fatal error: ".$ex->getMessage();
846
+ }
847
+ ```
848
+ ### Setting Variables From PHP
849
+
850
+ The `parse` function takes a second optional argument. If you want to
851
+ initialize variables from outside the LESS file then you can pass in an
852
+ associative array of names and values. The values will parsed as CSS values:
853
+
854
+ ```php
855
+ $less = new lessc();
856
+ echo $less->parse(".magic { color: @color; width: @base - 200; }",
857
+ array(
858
+ 'color' => 'red';
859
+ 'base' => '960px';
860
+ ));
861
+ ```
862
+
863
+ You can also do this when loading from a file, but remember to set the first
864
+ argument of the parse function to `null`, otherwise it will try to compile that
865
+ instead of the file:
866
+
867
+ ```php
868
+ $less = new lessc("myfile.less");
869
+ echo $less->parse(null, array('color' => 'blue'));
870
+ ```
871
+
872
+ ### Custom Functions
873
+
874
+ **lessphp** has a simple extension interface where you can implement user
875
+ functions that will be exposed in LESS code during the compile. They can be a
876
+ little tricky though because you need to work with the **lessphp** type system.
877
+
878
+ An instance of `lessc`, the **lessphp** compiler has two relevant methods:
879
+ `registerFunction` and `unregisterFunction`. `registerFunction` takes two
880
+ arguments, a name and a callable value. `unregisterFunction` just takes the
881
+ name of an existing function to remove.
882
+
883
+ Here's an example that adds a function called `double` that doubles any numeric
884
+ argument:
885
+
886
+ ```php
887
+ <?php
888
+ include "lessc.inc.php";
889
+
890
+ function lessphp_double($arg) {
891
+ list($type, $value) = $arg;
892
+ return array($type, $value*2);
893
+ }
894
+
895
+ $myless = new myless();
896
+ $myless->registerFunction("double", "lessphp_double");
897
+
898
+ // gives us a width of 800px
899
+ echo $myless->parse("div { width: double(400px); }");
900
+ ```
901
+
902
+ The second argument to `registerFunction` is any *callable value* that is
903
+ understood by [`call_user_func`](http://php.net/call_user_func).
904
+
905
+ If we are using PHP 5.3 or above then we are free to pass a function literal
906
+ like so:
907
+
908
+ ```php
909
+ $myless->registerFunction("double", function($arg) {
910
+ list($type, $value) = $arg;
911
+ return array($type, $value*2);
912
+ });
913
+ ```
914
+
915
+ Now let's talk about the `double` function itself.
916
+
917
+ Although a little verbose, the implementation gives us some insight on the type
918
+ system. All values in **lessphp** are stored in an array where the 0th element
919
+ is a string representing the type, and the other elements make up the
920
+ associated data for that value.
921
+
922
+ The best way to get an understanding of the system is to register is dummy
923
+ function which does a `vardump` on the argument. Try passing the function
924
+ different values from LESS and see what the results are.
925
+
926
+ The return value of the registered function must also be a **lessphp** type, but if it is
927
+ a string or numeric value, it will automatically be coerced into an appropriate
928
+ typed value. In our example, we reconstruct the value with our modifications
929
+ while making sure that we preserve the original type.
930
+
931
+ In addition to the arguments passed from **lessphp**, the instance of
932
+ **lessphp** itself is sent to the registered function as the second argument.
933
+
934
+ ## Command Line Interface
935
+
936
+ **lessphp** comes with a command line script written in PHP that can be used to
937
+ invoke the compiler from the terminal. On Linux an OSX, all you need to do is
938
+ place `plessc` and `lessc.inc.php` somewhere in your PATH (or you can run it in
939
+ the current directory as well). On windows you'll need a copy of `php.exe` to
940
+ run the file. To compile a file, `input.less` to CSS, run:
941
+
942
+ ```bash
943
+ $ plessc input.less
944
+ ```
945
+
946
+ To write to a file, redirect standard out:
947
+
948
+ ```bash
949
+ $ plessc input.less > output.css
950
+ ```
951
+
952
+ To compile code directly on the command line:
953
+
954
+ ```bash
955
+ $ plessc -r "@color: red; body { color: @color; }"
956
+ ```
957
+
958
+ To watch a file for changes, and compile it as needed, use the `-w` flag:
959
+
960
+ ```bash
961
+ $ plessc -w input-file output-file
962
+ ```
963
+
964
+ Errors from watch mode are written to standard out.
965
+
966
+
967
+ ## License
968
+
969
+ Copyright (c) 2010 Leaf Corcoran, <http://leafo.net/lessphp>
970
+
971
+ Permission is hereby granted, free of charge, to any person obtaining
972
+ a copy of this software and associated documentation files (the
973
+ "Software"), to deal in the Software without restriction, including
974
+ without limitation the rights to use, copy, modify, merge, publish,
975
+ distribute, sublicense, and/or sell copies of the Software, and to
976
+ permit persons to whom the Software is furnished to do so, subject to
977
+ the following conditions:
978
+
979
+ The above copyright notice and this permission notice shall be
980
+ included in all copies or substantial portions of the Software.
981
+
982
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
983
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
984
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
985
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
986
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
987
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
988
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
989
+
990
+
991
+ *Also under GPL3 if required, see `LICENSE` file*
992
+
lib/lessphp/lessc.inc.php ADDED
@@ -0,0 +1,2622 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * lessphp v0.3.3
5
+ * http://leafo.net/lessphp
6
+ *
7
+ * LESS css compiler, adapted from http://lesscss.org
8
+ *
9
+ * Copyright 2011, Leaf Corcoran <leafot@gmail.com>
10
+ * Licensed under MIT or GPLv3, see LICENSE
11
+ */
12
+
13
+
14
+ /**
15
+ * The less compiler and parser.
16
+ *
17
+ * Converting LESS to CSS is a two stage process. First the incoming document
18
+ * must be parsed. Parsing creates a tree in memory that represents the
19
+ * structure of the document. Then, the tree of the document is recursively
20
+ * compiled into the CSS text. The compile step has an implicit step called
21
+ * reduction, where values are brought to their lowest form before being
22
+ * turned to text, eg. mathematical equations are solved, and variables are
23
+ * dereferenced.
24
+ *
25
+ * The parsing stage produces the final structure of the document, for this
26
+ * reason mixins are mixed in and attribute accessors are referenced during
27
+ * the parse step. A reduction is done on the mixed in block as it is mixed in.
28
+ *
29
+ * See the following:
30
+ * - entry point for parsing and compiling: lessc::parse()
31
+ * - parsing: lessc::parseChunk()
32
+ * - compiling: lessc::compileBlock()
33
+ *
34
+ */
35
+ class lessc {
36
+ public static $VERSION = "v0.3.3";
37
+ protected $buffer;
38
+ protected $count;
39
+ protected $line;
40
+ protected $libFunctions = array();
41
+ static protected $nextBlockId = 0;
42
+
43
+ static protected $TRUE = array("keyword", "true");
44
+ static protected $FALSE = array("keyword", "false");
45
+
46
+ public $indentLevel;
47
+ public $indentChar = ' ';
48
+
49
+ protected $env = null;
50
+
51
+ protected $allParsedFiles = array();
52
+
53
+ public $vPrefix = '@'; // prefix of abstract properties
54
+ public $mPrefix = '$'; // prefix of abstract blocks
55
+ public $imPrefix = '!'; // special character to add !important
56
+ public $parentSelector = '&';
57
+
58
+ // set to the parser that generated the current line when compiling
59
+ // so we know how to create error messages
60
+ protected $sourceParser = null;
61
+
62
+ static protected $precedence = array(
63
+ '=<' => 0,
64
+ '>=' => 0,
65
+ '=' => 0,
66
+ '<' => 0,
67
+ '>' => 0,
68
+
69
+ '+' => 1,
70
+ '-' => 1,
71
+ '*' => 2,
72
+ '/' => 2,
73
+ '%' => 2,
74
+ );
75
+ static protected $operatorString; // regex string to match any of the operators
76
+
77
+ // types that have delayed computation
78
+ static protected $dtypes = array('expression', 'variable',
79
+ 'function', 'negative', 'list', 'lookup');
80
+
81
+ // these properties will supress division unless it's inside parenthases
82
+ static protected $supressDivisionProps = array('/border-radius$/i', '/^font$/i');
83
+
84
+ /**
85
+ * @link http://www.w3.org/TR/css3-values/
86
+ */
87
+ static protected $units = array(
88
+ 'em', 'ex', 'px', 'gd', 'rem', 'vw', 'vh', 'vm', 'ch', // Relative length units
89
+ 'in', 'cm', 'mm', 'pt', 'pc', // Absolute length units
90
+ '%', // Percentages
91
+ 'deg', 'grad', 'rad', 'turn', // Angles
92
+ 'ms', 's', // Times
93
+ 'Hz', 'kHz', //Frequencies
94
+ );
95
+
96
+ public $importDisabled = false;
97
+ public $importDir = '';
98
+
99
+ public $compat = false; // lessjs compatibility mode, does nothing right now
100
+
101
+ /**
102
+ * if we are in an expression then we don't need to worry about parsing font shorthand
103
+ * $inExp becomes true after the first value in an expression, or if we enter parens
104
+ */
105
+ protected $inExp = false;
106
+
107
+ /**
108
+ * if we are in parens we can be more liberal with whitespace around operators because
109
+ * it must evaluate to a single value and thus is less ambiguous.
110
+ *
111
+ * Consider:
112
+ * property1: 10 -5; // is two numbers, 10 and -5
113
+ * property2: (10 -5); // should evaluate to 5
114
+ */
115
+ protected $inParens = false;
116
+
117
+ /**
118
+ * Parse a single chunk off the head of the buffer and place it.
119
+ * @return false when the buffer is empty, or when there is an error.
120
+ *
121
+ * This function is called repeatedly until the entire document is
122
+ * parsed.
123
+ *
124
+ * This parser is most similar to a recursive descent parser. Single
125
+ * functions represent discrete grammatical rules for the language, and
126
+ * they are able to capture the text that represents those rules.
127
+ *
128
+ * Consider the function lessc::keyword(). (all parse functions are
129
+ * structured the same)
130
+ *
131
+ * The function takes a single reference argument. When calling the
132
+ * function it will attempt to match a keyword on the head of the buffer.
133
+ * If it is successful, it will place the keyword in the referenced
134
+ * argument, advance the position in the buffer, and return true. If it
135
+ * fails then it won't advance the buffer and it will return false.
136
+ *
137
+ * All of these parse functions are powered by lessc::match(), which behaves
138
+ * the same way, but takes a literal regular expression. Sometimes it is
139
+ * more convenient to use match instead of creating a new function.
140
+ *
141
+ * Because of the format of the functions, to parse an entire string of
142
+ * grammatical rules, you can chain them together using &&.
143
+ *
144
+ * But, if some of the rules in the chain succeed before one fails, then
145
+ * the buffer position will be left at an invalid state. In order to
146
+ * avoid this, lessc::seek() is used to remember and set buffer positions.
147
+ *
148
+ * Before parsing a chain, use $s = $this->seek() to remember the current
149
+ * position into $s. Then if a chain fails, use $this->seek($s) to
150
+ * go back where we started.
151
+ */
152
+ function parseChunk() {
153
+ if (empty($this->buffer)) return false;
154
+ $s = $this->seek();
155
+
156
+ // setting a property
157
+ if ($this->keyword($key) && $this->assign() &&
158
+ $this->propertyValue($value, $key) && $this->end())
159
+ {
160
+ $this->append(array('assign', $key, $value), $s);
161
+ return true;
162
+ } else {
163
+ $this->seek($s);
164
+ }
165
+
166
+ // look for special css blocks
167
+ if ($this->env->parent == null && $this->literal('@', false)) {
168
+ $this->count--;
169
+
170
+ // a font-face block
171
+ if ($this->literal('@font-face') && $this->literal('{')) {
172
+ $b = $this->pushSpecialBlock('@font-face');
173
+ return true;
174
+ } else {
175
+ $this->seek($s);
176
+ }
177
+
178
+ // charset
179
+ if ($this->literal('@charset') && $this->propertyValue($value) &&
180
+ $this->end())
181
+ {
182
+ $this->append(array('charset', $value), $s);
183
+ return true;
184
+ } else {
185
+ $this->seek($s);
186
+ }
187
+
188
+
189
+ // media
190
+ if ($this->literal('@media') && $this->mediaTypes($types) &&
191
+ $this->literal('{'))
192
+ {
193
+ $b = $this->pushSpecialBlock('@media');
194
+ $b->media = $types;
195
+ return true;
196
+ } else {
197
+ $this->seek($s);
198
+ }
199
+
200
+ // css animations
201
+ if ($this->match('(@(-[a-z]+-)?keyframes)', $m) &&
202
+ $this->propertyValue($value) && $this->literal('{'))
203
+ {
204
+ $b = $this->pushSpecialBlock(trim($m[0]));
205
+ $b->keyframes = $value;
206
+ return true;
207
+ } else {
208
+ $this->seek($s);
209
+ }
210
+ }
211
+
212
+ if (isset($this->env->keyframes)) {
213
+ if ($this->match("(to|from|[0-9]+%)", $m) && $this->literal('{')) {
214
+ $this->pushSpecialBlock($m[1]);
215
+ return true;
216
+ } else {
217
+ $this->seek($s);
218
+ }
219
+ }
220
+
221
+ // setting a variable
222
+ if ($this->variable($var) && $this->assign() &&
223
+ $this->propertyValue($value) && $this->end())
224
+ {
225
+ $this->append(array('assign', $var, $value), $s);
226
+ return true;
227
+ } else {
228
+ $this->seek($s);
229
+ }
230
+
231
+ if ($this->import($url, $media)) {
232
+ // don't check .css files
233
+ if (empty($media) && substr_compare($url, '.css', -4, 4) !== 0) {
234
+ if ($this->importDisabled) {
235
+ $this->append(array('raw', '/* import disabled */'));
236
+ } else {
237
+ $path = $this->findImport($url);
238
+ if (!is_null($path)) {
239
+ $this->append(array('import', $path), $s);
240
+ return true;
241
+ }
242
+ }
243
+ }
244
+
245
+ $this->append(array('raw', '@import url("'.$url.'")'.
246
+ ($media ? ' '.$media : '').';'), $s);
247
+ return true;
248
+ }
249
+
250
+ // opening parametric mixin
251
+ if ($this->tag($tag, true) && $this->argumentDef($args) &&
252
+ ($this->guards($guards) || true) &&
253
+ $this->literal('{'))
254
+ {
255
+ $block = $this->pushBlock($this->fixTags(array($tag)));
256
+ $block->args = $args;
257
+ if (!empty($guards)) $block->guards = $guards;
258
+ return true;
259
+ } else {
260
+ $this->seek($s);
261
+ }
262
+
263
+ // opening a simple block
264
+ if ($this->tags($tags) && $this->literal('{')) {
265
+ $tags = $this->fixTags($tags);
266
+ $this->pushBlock($tags);
267
+ return true;
268
+ } else {
269
+ $this->seek($s);
270
+ }
271
+
272
+ // closing a block
273
+ if ($this->literal('}')) {
274
+ try {
275
+ $block = $this->pop();
276
+ } catch (exception $e) {
277
+ $this->seek($s);
278
+ $this->throwError($e->getMessage());
279
+ }
280
+
281
+ $hidden = true;
282
+ if (!isset($block->args)) foreach ($block->tags as $tag) {
283
+ if ($tag{0} != $this->mPrefix) {
284
+ $hidden = false;
285
+ break;
286
+ }
287
+ }
288
+
289
+ if (!$hidden) $this->append(array('block', $block), $s);
290
+
291
+ foreach ($block->tags as $tag) {
292
+ $this->env->children[$tag][] = $block;
293
+ }
294
+
295
+ return true;
296
+ }
297
+
298
+ // mixin
299
+ if ($this->mixinTags($tags) &&
300
+ ($this->argumentValues($argv) || true) && $this->end())
301
+ {
302
+ $tags = $this->fixTags($tags);
303
+ $this->append(array('mixin', $tags, $argv), $s);
304
+ return true;
305
+ } else {
306
+ $this->seek($s);
307
+ }
308
+
309
+ // spare ;
310
+ if ($this->literal(';')) return true;
311
+
312
+ return false; // got nothing, throw error
313
+ }
314
+
315
+ function fixTags($tags) {
316
+ // move @ tags out of variable namespace
317
+ foreach ($tags as &$tag) {
318
+ if ($tag{0} == $this->vPrefix) $tag[0] = $this->mPrefix;
319
+ }
320
+ return $tags;
321
+ }
322
+
323
+ // attempts to find the path of an import url, returns null for css files
324
+ function findImport($url) {
325
+ foreach ((array)$this->importDir as $dir) {
326
+ $full = $dir.(substr($dir, -1) != '/' ? '/' : '').$url;
327
+ if ($this->fileExists($file = $full.'.less') || $this->fileExists($file = $full)) {
328
+ return $file;
329
+ }
330
+ }
331
+
332
+ return null;
333
+ }
334
+
335
+ function fileExists($name) {
336
+ // sym link workaround
337
+ return file_exists($name) || file_exists(realpath(preg_replace('/\w+\/\.\.\//', '', $name)));
338
+ }
339
+
340
+ // a list of expressions
341
+ function expressionList(&$exps) {
342
+ $values = array();
343
+
344
+ while ($this->expression($exp)) {
345
+ $values[] = $exp;
346
+ }
347
+
348
+ if (count($values) == 0) return false;
349
+
350
+ $exps = $this->compressList($values, ' ');
351
+ return true;
352
+ }
353
+
354
+ /**
355
+ * Attempt to consume an expression.
356
+ * @link http://en.wikipedia.org/wiki/Operator-precedence_parser#Pseudo-code
357
+ */
358
+ function expression(&$out) {
359
+ $s = $this->seek();
360
+ if ($this->literal('(') && ($this->inExp = $this->inParens = true) && $this->expression($exp) && $this->literal(')')) {
361
+ $lhs = $exp;
362
+ } elseif ($this->seek($s) && $this->value($val)) {
363
+ $lhs = $val;
364
+ } else {
365
+ $this->inParens = $this->inExp = false;
366
+ $this->seek($s);
367
+ return false;
368
+ }
369
+
370
+ $out = $this->expHelper($lhs, 0);
371
+ $this->inParens = $this->inExp = false;
372
+ return true;
373
+ }
374
+
375
+ /**
376
+ * recursively parse infix equation with $lhs at precedence $minP
377
+ */
378
+ function expHelper($lhs, $minP) {
379
+ $this->inExp = true;
380
+ $ss = $this->seek();
381
+
382
+ // if there was whitespace before the operator, then we require whitespace after
383
+ // the operator for it to be a mathematical operator.
384
+
385
+ $needWhite = false;
386
+ if (!$this->inParens && preg_match('/\s/', $this->buffer{$this->count - 1})) {
387
+ $needWhite = true;
388
+ }
389
+
390
+ // try to find a valid operator
391
+ while ($this->match(self::$operatorString.($needWhite ? '\s' : ''), $m) && self::$precedence[$m[1]] >= $minP) {
392
+ if (!$this->inParens && isset($this->env->currentProperty) && $m[1] == "/") {
393
+ foreach (self::$supressDivisionProps as $pattern) {
394
+ if (preg_match($pattern, $this->env->currentProperty)) {
395
+ $this->env->supressedDivision = true;
396
+ break 2;
397
+ }
398
+ }
399
+ }
400
+
401
+ // get rhs
402
+ $s = $this->seek();
403
+ $p = $this->inParens;
404
+ if ($this->literal('(') && ($this->inParens = true) && $this->expression($exp) && $this->literal(')')) {
405
+ $this->inParens = $p;
406
+ $rhs = $exp;
407
+ } else {
408
+ $this->inParens = $p;
409
+ if ($this->seek($s) && $this->value($val)) {
410
+ $rhs = $val;
411
+ } else {
412
+ break;
413
+ }
414
+ }
415
+
416
+ // peek for next operator to see what to do with rhs
417
+ if ($this->peek(self::$operatorString, $next) && self::$precedence[$next[1]] > self::$precedence[$m[1]]) {
418
+ $rhs = $this->expHelper($rhs, self::$precedence[$next[1]]);
419
+ }
420
+
421
+ // don't evaluate yet if it is dynamic
422
+ if (in_array($rhs[0], self::$dtypes) || in_array($lhs[0], self::$dtypes))
423
+ $lhs = array('expression', $m[1], $lhs, $rhs);
424
+ else
425
+ $lhs = $this->evaluate($m[1], $lhs, $rhs);
426
+
427
+ $ss = $this->seek();
428
+
429
+ $needWhite = false;
430
+ if (!$this->inParens && preg_match('/\s/', $this->buffer{$this->count - 1})) {
431
+ $needWhite = true;
432
+ }
433
+ }
434
+ $this->seek($ss);
435
+
436
+ return $lhs;
437
+ }
438
+
439
+ // consume a list of values for a property
440
+ function propertyValue(&$value, $keyName=null) {
441
+ $values = array();
442
+
443
+ if (!is_null($keyName)) $this->env->currentProperty = $keyName;
444
+
445
+ $s = null;
446
+ while ($this->expressionList($v)) {
447
+ $values[] = $v;
448
+ $s = $this->seek();
449
+ if (!$this->literal(',')) break;
450
+ }
451
+
452
+ if ($s) $this->seek($s);
453
+
454
+ if (!is_null($keyName)) unset($this->env->currentProperty);
455
+
456
+ if (count($values) == 0) return false;
457
+
458
+ $value = $this->compressList($values, ', ');
459
+ return true;
460
+ }
461
+
462
+ // a single value
463
+ function value(&$value) {
464
+ // try a unit
465
+ if ($this->unit($value)) return true;
466
+
467
+ // see if there is a negation
468
+ $s = $this->seek();
469
+ if ($this->literal('-', false)) {
470
+ $value = null;
471
+ if ($this->variable($var)) {
472
+ $value = array('variable', $var);
473
+ } elseif ($this->buffer{$this->count} == "(" && $this->expression($exp)) {
474
+ $value = $exp;
475
+ } else {
476
+ $this->seek($s);
477
+ }
478
+
479
+ if (!is_null($value)) {
480
+ $value = array('negative', $value);
481
+ return true;
482
+ }
483
+ } else {
484
+ $this->seek($s);
485
+ }
486
+
487
+ // accessor
488
+ // must be done before color
489
+ // this needs negation too
490
+ if ($this->accessor($a)) {
491
+ $a[1] = $this->fixTags($a[1]);
492
+ $value = $a;
493
+ return true;
494
+ }
495
+
496
+ // color
497
+ if ($this->color($value)) return true;
498
+
499
+ // css function
500
+ // must be done after color
501
+ if ($this->func($value)) return true;
502
+
503
+ // string
504
+ if ($this->string($tmp, $d)) {
505
+ $value = array('string', $d.$tmp.$d);
506
+ return true;
507
+ }
508
+
509
+ // try a keyword
510
+ if ($this->keyword($word)) {
511
+ $value = array('keyword', $word);
512
+ return true;
513
+ }
514
+
515
+ // try a variable
516
+ if ($this->variable($var)) {
517
+ $value = array('variable', $var);
518
+ return true;
519
+ }
520
+
521
+ // unquote string
522
+ if ($this->literal("~") && $this->string($value, $d)) {
523
+ $value = array("keyword", $value);
524
+ return true;
525
+ } else {
526
+ $this->seek($s);
527
+ }
528
+
529
+ // css hack: \0
530
+ if ($this->literal('\\') && $this->match('([0-9]+)', $m)) {
531
+ $value = array('keyword', '\\'.$m[1]);
532
+ return true;
533
+ } else {
534
+ $this->seek($s);
535
+ }
536
+
537
+ // the spare / when supressing division
538
+ if (!empty($this->env->supressedDivision)) {
539
+ unset($this->env->supressedDivision);
540
+ if ($this->literal("/")) {
541
+ $value = array('keyword', '/');
542
+ return true;
543
+ }
544
+ }
545
+
546
+ return false;
547
+ }
548
+
549
+ // an import statement
550
+ function import(&$url, &$media) {
551
+ $s = $this->seek();
552
+ if (!$this->literal('@import')) return false;
553
+
554
+ // @import "something.css" media;
555
+ // @import url("something.css") media;
556
+ // @import url(something.css) media;
557
+
558
+ if ($this->literal('url(')) $parens = true; else $parens = false;
559
+
560
+ if (!$this->string($url)) {
561
+ if ($parens && $this->to(')', $url)) {
562
+ $parens = false; // got em
563
+ } else {
564
+ $this->seek($s);
565
+ return false;
566
+ }
567
+ }
568
+
569
+ if ($parens && !$this->literal(')')) {
570
+ $this->seek($s);
571
+ return false;
572
+ }
573
+
574
+ // now the rest is media
575
+ return $this->to(';', $media, false, true);
576
+ }
577
+
578
+ // a list of media types, very lenient
579
+ function mediaTypes(&$parts) {
580
+ $parts = array();
581
+ while ($this->to("(", $chunk, false, "[^{]")) {
582
+ $parts[] = array('raw', $chunk."(");
583
+ $s = $this->seek();
584
+ if ($this->keyword($name) && $this->assign() &&
585
+ $this->propertyValue($value))
586
+ {
587
+ $parts[] = array('assign', $name, $value);
588
+ } else {
589
+ $this->seek($s);
590
+ }
591
+ }
592
+
593
+ if ($this->to('{', $rest, true, true)) {
594
+ $parts[] = array('raw', $rest);
595
+ return true;
596
+ }
597
+
598
+ $parts = null;
599
+ return false;
600
+ }
601
+
602
+ // a scoped value accessor
603
+ // .hello > @scope1 > @scope2['value'];
604
+ function accessor(&$var) {
605
+ $s = $this->seek();
606
+
607
+ if (!$this->tags($scope, true, '>') || !$this->literal('[')) {
608
+ $this->seek($s);
609
+ return false;
610
+ }
611
+
612
+ // either it is a variable or a property
613
+ // why is a property wrapped in quotes, who knows!
614
+ if ($this->variable($name)) {
615
+ // ~
616
+ } elseif ($this->literal("'") && $this->keyword($name) && $this->literal("'")) {
617
+ // .. $this->count is messed up if we wanted to test another access type
618
+ } else {
619
+ $this->seek($s);
620
+ return false;
621
+ }
622
+
623
+ if (!$this->literal(']')) {
624
+ $this->seek($s);
625
+ return false;
626
+ }
627
+
628
+ $var = array('lookup', $scope, $name);
629
+ return true;
630
+ }
631
+
632
+ // a string
633
+ function string(&$string, &$d = null) {
634
+ $s = $this->seek();
635
+ if ($this->literal('"', false)) {
636
+ $delim = '"';
637
+ } elseif ($this->literal("'", false)) {
638
+ $delim = "'";
639
+ } else {
640
+ return false;
641
+ }
642
+
643
+ if (!$this->to($delim, $string)) {
644
+ $this->seek($s);
645
+ return false;
646
+ }
647
+
648
+ $d = $delim;
649
+ return true;
650
+ }
651
+
652
+ /**
653
+ * Consume a number and optionally a unit.
654
+ * Can also consume a font shorthand if it is a simple case.
655
+ * $allowed restricts the types that are matched.
656
+ */
657
+ function unit(&$unit, $allowed = null) {
658
+ if (!$allowed) $allowed = self::$units;
659
+
660
+ if ($this->match('(-?[0-9]*(\.)?[0-9]+)('.implode('|', $allowed).')?', $m)) {
661
+ if (!isset($m[3])) $m[3] = 'number';
662
+ $unit = array($m[3], $m[1]);
663
+
664
+ return true;
665
+ }
666
+
667
+ return false;
668
+ }
669
+
670
+ // a # color
671
+ function color(&$out) {
672
+ $color = array('color');
673
+
674
+ if ($this->match('(#([0-9a-f]{6})|#([0-9a-f]{3}))', $m)) {
675
+ if (isset($m[3])) {
676
+ $num = $m[3];
677
+ $width = 16;
678
+ } else {
679
+ $num = $m[2];
680
+ $width = 256;
681
+ }
682
+
683
+ $num = hexdec($num);
684
+ foreach (array(3,2,1) as $i) {
685
+ $t = $num % $width;
686
+ $num /= $width;
687
+
688
+ $color[$i] = $t * (256/$width) + $t * floor(16/$width);
689
+ }
690
+
691
+ $out = $color;
692
+ return true;
693
+ }
694
+
695
+ return false;
696
+ }
697
+
698
+ // consume a list of property values delimited by ; and wrapped in ()
699
+ function argumentValues(&$args, $delim = ',') {
700
+ $s = $this->seek();
701
+ if (!$this->literal('(')) return false;
702
+
703
+ $values = array();
704
+ while (true) {
705
+ if ($this->expressionList($value)) $values[] = $value;
706
+ if (!$this->literal($delim)) break;
707
+ else {
708
+ if ($value == null) $values[] = null;
709
+ $value = null;
710
+ }
711
+ }
712
+
713
+ if (!$this->literal(')')) {
714
+ $this->seek($s);
715
+ return false;
716
+ }
717
+
718
+ $args = $values;
719
+ return true;
720
+ }
721
+
722
+ // consume an argument definition list surrounded by ()
723
+ // each argument is a variable name with optional value
724
+ function argumentDef(&$args, $delim = ',') {
725
+ $s = $this->seek();
726
+ if (!$this->literal('(')) return false;
727
+
728
+ $values = array();
729
+ while (true) {
730
+ if ($this->variable($vname)) {
731
+ $arg = array("arg", $vname);
732
+ if ($this->assign() && $this->expressionList($value)) {
733
+ $arg[] = $value;
734
+ // let the : slide if there is no value
735
+ }
736
+ $values[] = $arg;
737
+ continue;
738
+ }
739
+
740
+ if ($this->value($literal)) {
741
+ $values[] = array("lit", $literal);
742
+ }
743
+
744
+ if (!$this->literal($delim)) break;
745
+ }
746
+
747
+ if (!$this->literal(')')) {
748
+ $this->seek($s);
749
+ return false;
750
+ }
751
+
752
+ $args = $values;
753
+
754
+ return true;
755
+ }
756
+
757
+ // consume a list of tags
758
+ // this accepts a hanging delimiter
759
+ function tags(&$tags, $simple = false, $delim = ',') {
760
+ $tags = array();
761
+ while ($this->tag($tt, $simple)) {
762
+ $tags[] = $tt;
763
+ if (!$this->literal($delim)) break;
764
+ }
765
+ if (count($tags) == 0) return false;
766
+
767
+ return true;
768
+ }
769
+
770
+ // list of tags of specifying mixin path
771
+ // optionally separated by > (lazy, accepts extra >)
772
+ function mixinTags(&$tags) {
773
+ $s = $this->seek();
774
+ $tags = array();
775
+ while ($this->tag($tt, true)) {
776
+ $tags[] = $tt;
777
+ $this->literal(">");
778
+ }
779
+
780
+ if (count($tags) == 0) return false;
781
+
782
+ return true;
783
+ }
784
+
785
+ // a bracketed value (contained within in a tag definition)
786
+ function tagBracket(&$value) {
787
+ $s = $this->seek();
788
+ if ($this->literal('[') && $this->to(']', $c, true) && $this->literal(']', false)) {
789
+ $value = '['.$c.']';
790
+ // whitespace?
791
+ if ($this->match('', $_)) $value .= $_[0];
792
+
793
+ // escape parent selector
794
+ $value = str_replace($this->parentSelector, "&&", $value);
795
+ return true;
796
+ }
797
+
798
+ $this->seek($s);
799
+ return false;
800
+ }
801
+
802
+ // a single tag
803
+ function tag(&$tag, $simple = false) {
804
+ if ($simple)
805
+ $chars = '^,:;{}\][>\(\) "\'';
806
+ else
807
+ $chars = '^,;{}["\'';
808
+
809
+ $tag = '';
810
+ while ($this->tagBracket($first)) $tag .= $first;
811
+ while ($this->match('(['.$chars.'0-9]['.$chars.']*)', $m)) {
812
+ $tag .= $m[1];
813
+ if ($simple) break;
814
+
815
+ while ($this->tagBracket($brack)) $tag .= $brack;
816
+ }
817
+ $tag = trim($tag);
818
+ if ($tag == '') return false;
819
+
820
+ return true;
821
+ }
822
+
823
+ // a css function
824
+ function func(&$func) {
825
+ $s = $this->seek();
826
+
827
+ if ($this->match('(%|[\w\-_][\w\-_:\.]+|[\w_])', $m) && $this->literal('(')) {
828
+ $fname = $m[1];
829
+
830
+ $s_pre_args = $this->seek();
831
+
832
+ $args = array();
833
+ while (true) {
834
+ $ss = $this->seek();
835
+ // this ugly nonsense is for ie filter properties
836
+ if ($this->keyword($name) && $this->literal('=') && $this->expressionList($value)) {
837
+ $args[] = array('list', '=', array(array('keyword', $name), $value));
838
+ } else {
839
+ $this->seek($ss);
840
+ if ($this->expressionList($value)) {
841
+ $args[] = $value;
842
+ }
843
+ }
844
+
845
+ if (!$this->literal(',')) break;
846
+ }
847
+ $args = array('list', ',', $args);
848
+
849
+ if ($this->literal(')')) {
850
+ $func = array('function', $fname, $args);
851
+ return true;
852
+ } elseif ($fname == 'url') {
853
+ // couldn't parse and in url? treat as string
854
+ $this->seek($s_pre_args);
855
+ if ($this->to(')', $content, true) && $this->literal(')')) {
856
+ $func = array('function', $fname,array('string', $content));
857
+ return true;
858
+ }
859
+ }
860
+ }
861
+
862
+ $this->seek($s);
863
+ return false;
864
+ }
865
+
866
+ // consume a less variable
867
+ function variable(&$name) {
868
+ $s = $this->seek();
869
+ if ($this->literal($this->vPrefix, false) &&
870
+ ($this->variable($sub) || $this->keyword($name)))
871
+ {
872
+ if (!empty($sub)) {
873
+ $name = array('variable', $sub);
874
+ } else {
875
+ $name = $this->vPrefix.$name;
876
+ }
877
+ return true;
878
+ }
879
+
880
+ $name = null;
881
+ $this->seek($s);
882
+ return false;
883
+ }
884
+
885
+ /**
886
+ * Consume an assignment operator
887
+ * Can optionally take a name that will be set to the current property name
888
+ */
889
+ function assign($name = null) {
890
+ if ($name) $this->currentProperty = $name;
891
+ return $this->literal(':') || $this->literal('=');
892
+ }
893
+
894
+ // consume a keyword
895
+ function keyword(&$word) {
896
+ if ($this->match('([\w_\-\*!"][\w\-_"]*)', $m)) {
897
+ $word = $m[1];
898
+ return true;
899
+ }
900
+ return false;
901
+ }
902
+
903
+ // consume an end of statement delimiter
904
+ function end() {
905
+ if ($this->literal(';'))
906
+ return true;
907
+ elseif ($this->count == strlen($this->buffer) || $this->buffer{$this->count} == '}') {
908
+ // if there is end of file or a closing block next then we don't need a ;
909
+ return true;
910
+ }
911
+ return false;
912
+ }
913
+
914
+ function guards(&$guards) {
915
+ $s = $this->seek();
916
+
917
+ if (!$this->literal("when")) {
918
+ $this->seek($s);
919
+ return false;
920
+ }
921
+
922
+ $guards = array();
923
+
924
+ while ($this->guard_group($g)) {
925
+ $guards[] = $g;
926
+ if (!$this->literal(",")) break;
927
+ }
928
+
929
+ if (count($guards) == 0) {
930
+ $guards = null;
931
+ $this->seek($s);
932
+ return false;
933
+ }
934
+
935
+ return true;
936
+ }
937
+
938
+ // a bunch of guards that are and'd together
939
+ function guard_group(&$guard_group) {
940
+ $s = $this->seek();
941
+ $guard_group = array();
942
+ while ($this->guard($guard)) {
943
+ $guard_group[] = $guard;
944
+ if (!$this->literal("and")) break;
945
+ }
946
+
947
+ if (count($guard_group) == 0) {
948
+ $guard_group = null;
949
+ $this->seek($s);
950
+ return false;
951
+ }
952
+
953
+ return true;
954
+ }
955
+
956
+ function guard(&$guard) {
957
+ $s = $this->seek();
958
+ $negate = $this->literal("not");
959
+
960
+ if ($this->literal("(") && $this->expression($exp) && $this->literal(")")) {
961
+ $guard = $exp;
962
+ if ($negate) $guard = array("negate", $guard);
963
+ return true;
964
+ }
965
+
966
+ $this->seek($s);
967
+ return false;
968
+ }
969
+
970
+ function compressList($items, $delim) {
971
+ if (count($items) == 1) return $items[0];
972
+ else return array('list', $delim, $items);
973
+ }
974
+
975
+ // just do a shallow property merge, seems to be what lessjs does
976
+ function mergeBlock($target, $from) {
977
+ $target = clone $target;
978
+ $target->props = array_merge($target->props, $from->props);
979
+ return $target;
980
+ }
981
+
982
+ // import all imports into the block
983
+ function mixImports($block) {
984
+ $props = array();
985
+ foreach ($block->props as $prop) {
986
+ if ($prop[0] == 'import') {
987
+ list(, $path) = $prop;
988
+ $this->addParsedFile($path);
989
+ $child_less = $this->createChild($path);
990
+ $root = $child_less->parseTree();
991
+
992
+ $root->parent = $block;
993
+ $this->mixImports($root);
994
+
995
+ // inject imported blocks into this block, local will overwrite import
996
+ $block->children = array_merge($root->children, $block->children);
997
+
998
+ // splice in all the props
999
+ foreach ($root->props as $sub_prop) {
1000
+ if (isset($sub_prop[-1])) {
1001
+ // leave a reference to the imported file for error messages
1002
+ $sub_prop[-1] = array($child_less, $sub_prop[-1]);
1003
+ }
1004
+ $props[] = $sub_prop;
1005
+ }
1006
+ } else {
1007
+ $props[] = $prop;
1008
+ }
1009
+ }
1010
+ $block->props = $props;
1011
+ }
1012
+
1013
+ /**
1014
+ * Recursively compiles a block.
1015
+ * @param $block the block
1016
+ * @param $parentTags the tags of the block that contained this one
1017
+ *
1018
+ * A block is analogous to a CSS block in most cases. A single less document
1019
+ * is encapsulated in a block when parsed, but it does not have parent tags
1020
+ * so all of it's children appear on the root level when compiled.
1021
+ *
1022
+ * Blocks are made up of props and children.
1023
+ *
1024
+ * Props are property instructions, array tuples which describe an action
1025
+ * to be taken, eg. write a property, set a variable, mixin a block.
1026
+ *
1027
+ * The children of a block are just all the blocks that are defined within.
1028
+ *
1029
+ * Compiling the block involves pushing a fresh environment on the stack,
1030
+ * and iterating through the props, compiling each one.
1031
+ *
1032
+ * See lessc::compileProp()
1033
+ *
1034
+ */
1035
+ function compileBlock($block, $parent_tags = null) {
1036
+ $isRoot = $parent_tags == null && $block->tags == null;
1037
+
1038
+ $indent = str_repeat($this->indentChar, $this->indentLevel);
1039
+
1040
+ if (!empty($block->no_multiply)) {
1041
+ $special_block = true;
1042
+ $this->indentLevel++;
1043
+ $tags = array();
1044
+ } else {
1045
+ $special_block = false;
1046
+ $tags = $this->multiplyTags($parent_tags, $block->tags);
1047
+ }
1048
+
1049
+ $env = $this->pushEnv();
1050
+ $env->nameDepth = array();
1051
+
1052
+ $lines = array();
1053
+ $blocks = array();
1054
+ $this->mixImports($block);
1055
+ foreach ($block->props as $prop) {
1056
+ $this->compileProp($prop, $block, $tags, $lines, $blocks);
1057
+ }
1058
+
1059
+ $block->scope = $env;
1060
+
1061
+ $this->pop();
1062
+
1063
+ $nl = $isRoot ? "\n".$indent :
1064
+ "\n".$indent.$this->indentChar;
1065
+
1066
+ ob_start();
1067
+
1068
+ if ($special_block) {
1069
+ $this->indentLevel--;
1070
+ if (isset($block->media)) {
1071
+ echo $this->compileMedia($block);
1072
+ } elseif (isset($block->keyframes)) {
1073
+ echo $block->tags[0]." ".
1074
+ $this->compileValue($this->reduce($block->keyframes));
1075
+ } else {
1076
+ list($name) = $block->tags;
1077
+ echo $indent.$name;
1078
+ }
1079
+
1080
+ echo ' {'.(count($lines) > 0 ? $nl : "\n");
1081
+ }
1082
+
1083
+ // dump it
1084
+ if (count($lines) > 0) {
1085
+ if (!$special_block && !$isRoot) {
1086
+ echo $indent.implode(", ", $tags);
1087
+ if (count($lines) > 1) echo " {".$nl;
1088
+ else echo " { ";
1089
+ }
1090
+
1091
+ echo implode($nl, $lines);
1092
+
1093
+ if (!$special_block && !$isRoot) {
1094
+ if (count($lines) > 1) echo "\n".$indent."}\n";
1095
+ else echo " }\n";
1096
+ } else echo "\n";
1097
+ }
1098
+
1099
+ foreach ($blocks as $b) echo $b;
1100
+
1101
+ if ($special_block) {
1102
+ echo $indent."}\n";
1103
+ }
1104
+
1105
+ return ob_get_clean();
1106
+ }
1107
+
1108
+ // find the fully qualified tags for a block and its parent's tags
1109
+ function multiplyTags($parents, $current) {
1110
+ if ($parents == null) return $current;
1111
+
1112
+ $tags = array();
1113
+ foreach ($parents as $ptag) {
1114
+ foreach ($current as $tag) {
1115
+ // inject parent in place of parent selector, ignoring escaped values
1116
+ $count = 0;
1117
+ $parts = explode("&&", $tag);
1118
+
1119
+ foreach ($parts as $i => $chunk) {
1120
+ $parts[$i] = str_replace($this->parentSelector, $ptag, $chunk, $c);
1121
+ $count += $c;
1122
+ }
1123
+
1124
+ $tag = implode("&", $parts);
1125
+
1126
+ if ($count > 0) {
1127
+ $tags[] = trim($tag);
1128
+ } else {
1129
+ $tags[] = trim($ptag . ' ' . $tag);
1130
+ }
1131
+ }
1132
+ }
1133
+
1134
+ return $tags;
1135
+ }
1136
+
1137
+ function eq($left, $right) {
1138
+ return $left == $right;
1139
+ }
1140
+
1141
+ function patternMatch($block, $callingArgs) {
1142
+ // match the guards if it has them
1143
+ // any one of the groups must have all its guards pass for a match
1144
+ if (!empty($block->guards)) {
1145
+ $group_passed = false;
1146
+ foreach ($block->guards as $guard_group) {
1147
+ foreach ($guard_group as $guard) {
1148
+ $this->pushEnv();
1149
+ $this->zipSetArgs($block->args, $callingArgs);
1150
+
1151
+ $negate = false;
1152
+ if ($guard[0] == "negate") {
1153
+ $guard = $guard[1];
1154
+ $negate = true;
1155
+ }
1156
+
1157
+ $passed = $this->reduce($guard) == self::$TRUE;
1158
+ if ($negate) $passed = !$passed;
1159
+
1160
+ $this->pop();
1161
+
1162
+ if ($passed) {
1163
+ $group_passed = true;
1164
+ } else {
1165
+ $group_passed = false;
1166
+ break;
1167
+ }
1168
+ }
1169
+
1170
+ if ($group_passed) break;
1171
+ }
1172
+
1173
+ if (!$group_passed) {
1174
+ return false;
1175
+ }
1176
+ }
1177
+
1178
+ // blocks with no required arguments are mixed into everything
1179
+ if (empty($block->args)) return true;
1180
+
1181
+ // has args but all have default values
1182
+ $pseudoEmpty = true;
1183
+ foreach ($block->args as $arg) {
1184
+ if (!isset($arg[2])) {
1185
+ $pseudoEmpty = false;
1186
+ break;
1187
+ }
1188
+ }
1189
+
1190
+ if ($pseudoEmpty) return true;
1191
+
1192
+ // try to match by arity or by argument literal
1193
+ foreach ($block->args as $i => $arg) {
1194
+ switch ($arg[0]) {
1195
+ case "lit":
1196
+ if (empty($callingArgs[$i]) || !$this->eq($arg[1], $callingArgs[$i])) {
1197
+ return false;
1198
+ }
1199
+ break;
1200
+ case "arg":
1201
+ // no arg and no default value
1202
+ if (!isset($callingArgs[$i]) && !isset($arg[2])) {
1203
+ return false;
1204
+ }
1205
+ break;
1206
+ }
1207
+ }
1208
+
1209
+ return $i >= count($callingArgs) - 1;
1210
+ }
1211
+
1212
+ function patternMatchAll($blocks, $callingArgs) {
1213
+ $matches = null;
1214
+ foreach ($blocks as $block) {
1215
+ if ($this->patternMatch($block, $callingArgs)) {
1216
+ $matches[] = $block;
1217
+ }
1218
+ }
1219
+
1220
+ return $matches;
1221
+ }
1222
+
1223
+ // attempt to find blocks matched by path and args
1224
+ function findBlocks($search_in, $path, $args, $seen=array()) {
1225
+ if ($search_in == null) return null;
1226
+ if (isset($seen[$search_in->id])) return null;
1227
+ $seen[$search_in->id] = true;
1228
+
1229
+ $name = $path[0];
1230
+
1231
+ if (isset($search_in->children[$name])) {
1232
+ $blocks = $search_in->children[$name];
1233
+ if (count($path) == 1) {
1234
+ $matches = $this->patternMatchAll($blocks, $args);
1235
+ if (!empty($matches)) {
1236
+ // This will return all blocks that match in the closest
1237
+ // scope that has any matching block, like lessjs
1238
+ return $matches;
1239
+ }
1240
+ } else {
1241
+ return $this->findBlocks($blocks[0],
1242
+ array_slice($path, 1), $args, $seen);
1243
+ }
1244
+ }
1245
+
1246
+ if ($search_in->parent === $search_in) return null;
1247
+ return $this->findBlocks($search_in->parent, $path, $args, $seen);
1248
+ }
1249
+
1250
+ // sets all argument names in $args to either the default value
1251
+ // or the one passed in through $values
1252
+ function zipSetArgs($args, $values) {
1253
+ $i = 0;
1254
+ $assigned_values = array();
1255
+ foreach ($args as $a) {
1256
+ if ($a[0] == "arg") {
1257
+ if ($i < count($values) && !is_null($values[$i])) {
1258
+ $value = $values[$i];
1259
+ } elseif (isset($a[2])) {
1260
+ $value = $a[2];
1261
+ } else $value = null;
1262
+
1263
+ $value = $this->reduce($value);
1264
+ $this->set($a[1], $value);
1265
+ $assigned_values[] = $value;
1266
+ }
1267
+ $i++;
1268
+ }
1269
+
1270
+ $this->env->arguments = $assigned_values;
1271
+ }
1272
+
1273
+ // compile a prop and update $lines or $blocks appropriately
1274
+ function compileProp($prop, $block, $tags, &$_lines, &$_blocks) {
1275
+ // set error position context
1276
+ if (isset($prop[-1])) {
1277
+ if (is_array($prop[-1])) {
1278
+ list($less, $count) = $prop[-1];
1279
+ $parentParser = $this->sourceParser;
1280
+ $this->sourceParser = $less;
1281
+ $this->count = $count;
1282
+ } else {
1283
+ $this->count = $prop[-1];
1284
+ }
1285
+ } else {
1286
+ $this->count = -1;
1287
+ }
1288
+
1289
+ switch ($prop[0]) {
1290
+ case 'assign':
1291
+ list(, $name, $value) = $prop;
1292
+ if ($name[0] == $this->vPrefix) {
1293
+ $this->set($name, $value);
1294
+ } else {
1295
+ $_lines[] = "$name:".
1296
+ $this->compileValue($this->reduce($value)).";";
1297
+ }
1298
+ break;
1299
+ case 'block':
1300
+ list(, $child) = $prop;
1301
+ $_blocks[] = $this->compileBlock($child, $tags);
1302
+ break;
1303
+ case 'mixin':
1304
+ list(, $path, $args) = $prop;
1305
+
1306
+ $args = array_map(array($this, "reduce"), (array)$args);
1307
+ $mixins = $this->findBlocks($block, $path, $args);
1308
+ if (is_null($mixins)) {
1309
+ // echo "failed to find block: ".implode(" > ", $path)."\n";
1310
+ break; // throw error here??
1311
+ }
1312
+
1313
+ foreach ($mixins as $mixin) {
1314
+ $old_scope = null;
1315
+ if (isset($mixin->parent->scope)) {
1316
+ $old_scope = $this->env;
1317
+ $this->env = $mixin->parent->scope;
1318
+ }
1319
+
1320
+ $have_args = false;
1321
+ if (isset($mixin->args)) {
1322
+ $have_args = true;
1323
+ $this->pushEnv();
1324
+ $this->zipSetArgs($mixin->args, $args);
1325
+ }
1326
+
1327
+ $old_parent = $mixin->parent;
1328
+ $mixin->parent = $block;
1329
+
1330
+ foreach ($mixin->props as $sub_prop) {
1331
+ $this->compileProp($sub_prop, $mixin, $tags, $_lines, $_blocks);
1332
+ }
1333
+
1334
+ $mixin->parent = $old_parent;
1335
+
1336
+ if ($have_args) $this->pop();
1337
+
1338
+ if ($old_scope) {
1339
+ $this->env = $old_scope;
1340
+ }
1341
+ }
1342
+
1343
+ break;
1344
+ case 'raw':
1345
+ $_lines[] = $prop[1];
1346
+ break;
1347
+ case 'charset':
1348
+ list(, $value) = $prop;
1349
+ $_lines[] = '@charset '.$this->compileValue($this->reduce($value)).';';
1350
+ break;
1351
+ default:
1352
+ $this->throwError("unknown op: {$prop[0]}\n");
1353
+ }
1354
+
1355
+ if (isset($parentParser)) {
1356
+ $this->sourceParser = $parentParser;
1357
+ }
1358
+ }
1359
+
1360
+
1361
+ /**
1362
+ * Compiles a primitive value into a CSS property value.
1363
+ *
1364
+ * Values in lessphp are typed by being wrapped in arrays, their format is
1365
+ * typically:
1366
+ *
1367
+ * array(type, contents [, additional_contents]*)
1368
+ *
1369
+ * Will not work on non reduced values (expressions, variables, etc)
1370
+ */
1371
+ function compileValue($value) {
1372
+ switch ($value[0]) {
1373
+ case 'list':
1374
+ // [1] - delimiter
1375
+ // [2] - array of values
1376
+ return implode($value[1], array_map(array($this, 'compileValue'), $value[2]));
1377
+ case 'keyword':
1378
+ // [1] - the keyword
1379
+ case 'number':
1380
+ // [1] - the number
1381
+ return $value[1];
1382
+ case 'string':
1383
+ // [1] - contents of string (includes quotes)
1384
+
1385
+ // search for inline variables to replace
1386
+ $replace = array();
1387
+ if (preg_match_all('/'.$this->preg_quote($this->vPrefix).'\{([\w-_][0-9\w-_]*)\}/', $value[1], $m)) {
1388
+ foreach ($m[1] as $name) {
1389
+ if (!isset($replace[$name]))
1390
+ $replace[$name] = $this->compileValue($this->reduce(array('variable', $this->vPrefix . $name)));
1391
+ }
1392
+ }
1393
+
1394
+ foreach ($replace as $var=>$val) {
1395
+ if ($this->quoted($val)) {
1396
+ $val = substr($val, 1, -1);
1397
+ }
1398
+ $value[1] = str_replace($this->vPrefix. '{'.$var.'}', $val, $value[1]);
1399
+ }
1400
+
1401
+ return $value[1];
1402
+ case 'color':
1403
+ // [1] - red component (either number for a %)
1404
+ // [2] - green component
1405
+ // [3] - blue component
1406
+ // [4] - optional alpha component
1407
+ list(, $r, $g, $b) = $value;
1408
+ $r = round($r);
1409
+ $g = round($g);
1410
+ $b = round($b);
1411
+
1412
+ if (count($value) == 5 && $value[4] != 1) { // rgba
1413
+ return 'rgba('.$r.','.$g.','.$b.','.$value[4].')';
1414
+ }
1415
+ return sprintf("#%02x%02x%02x", $r, $g, $b);
1416
+ case 'function':
1417
+ // [1] - function name
1418
+ // [2] - some array value representing arguments, either ['string', value] or ['list', ',', values[]]
1419
+
1420
+ // see if function evaluates to something else
1421
+ $value = $this->reduce($value);
1422
+ if ($value[0] == 'function') {
1423
+ return $value[1].'('.$this->compileValue($value[2]).')';
1424
+ }
1425
+ else return $this->compileValue($value);
1426
+ default: // assumed to be unit
1427
+ return $value[1].$value[0];
1428
+ }
1429
+ }
1430
+
1431
+ function compileMedia($block) {
1432
+ $mediaParts = array();
1433
+ foreach ($block->media as $part) {
1434
+ if ($part[0] == "raw") {
1435
+ $mediaParts[] = $part[1];
1436
+ } elseif ($part[0] == "assign") {
1437
+ list(, $propName, $propVal) = $part;
1438
+ $mediaParts[] = "$propName: ".
1439
+ $this->compileValue($this->reduce($propVal));
1440
+ }
1441
+ }
1442
+
1443
+ return "@media ".trim(implode($mediaParts));
1444
+ }
1445
+
1446
+ function lib_isnumber($value) {
1447
+ return $this->toBool(is_numeric($value[1]));
1448
+ }
1449
+
1450
+ function lib_isstring($value) {
1451
+ return $this->toBool($value[0] == "string");
1452
+ }
1453
+
1454
+ function lib_iscolor($value) {
1455
+ return $this->toBool($this->coerceColor($value));
1456
+ }
1457
+
1458
+ function lib_iskeyword($value) {
1459
+ return $this->toBool($value[0] == "keyword");
1460
+ }
1461
+
1462
+ function lib_ispixel($value) {
1463
+ return $this->toBool($value[0] == "px");
1464
+ }
1465
+
1466
+ function lib_ispercentage($value) {
1467
+ return $this->toBool($value[0] == "%");
1468
+ }
1469
+
1470
+ function lib_isem($value) {
1471
+ return $this->toBool($value[0] == "em");
1472
+ }
1473
+
1474
+ function lib_rgbahex($color) {
1475
+ if ($color[0] != 'color')
1476
+ $this->throwError("color expected for rgbahex");
1477
+
1478
+ return sprintf("#%02x%02x%02x%02x",
1479
+ isset($color[4]) ? $color[4]*255 : 0,
1480
+ $color[1],$color[2], $color[3]);
1481
+ }
1482
+
1483
+ // utility func to unquote a string
1484
+ function lib_e($arg) {
1485
+ switch ($arg[0]) {
1486
+ case "list":
1487
+ $items = $arg[2];
1488
+ if (isset($items[0])) {
1489
+ return $this->lib_e($items[0]);
1490
+ }
1491
+ return "";
1492
+ case "string":
1493
+ $str = $this->compileValue($arg);
1494
+ return substr($str, 1, -1);
1495
+ default:
1496
+ return $this->compileValue($arg);
1497
+ }
1498
+ }
1499
+
1500
+ function lib__sprintf($args) {
1501
+ if ($args[0] != "list") return $args;
1502
+ $values = $args[2];
1503
+ $source = $this->reduce(array_shift($values));
1504
+ if ($source[0] != "string") {
1505
+ return $source;
1506
+ }
1507
+
1508
+ $str = $source[1];
1509
+ $i = 0;
1510
+ if (preg_match_all('/%[dsa]/', $str, $m)) {
1511
+ foreach ($m[0] as $match) {
1512
+ $val = isset($values[$i]) ? $this->reduce($values[$i]) : array('keyword', '');
1513
+ $i++;
1514
+ switch ($match[1]) {
1515
+ case "s":
1516
+ if ($val[0] == "string") {
1517
+ $rep = substr($val[1], 1, -1);
1518
+ break;
1519
+ }
1520
+ default:
1521
+ $rep = $this->compileValue($val);
1522
+ }
1523
+ $str = preg_replace('/'.$this->preg_quote($match).'/', $rep, $str, 1);
1524
+ }
1525
+ }
1526
+
1527
+ return array('string', $str);
1528
+ }
1529
+
1530
+ function lib_floor($arg) {
1531
+ return array($arg[0], floor($arg[1]));
1532
+ }
1533
+
1534
+ function lib_round($arg) {
1535
+ return array($arg[0], round($arg[1]));
1536
+ }
1537
+
1538
+ // is a string surrounded in quotes? returns the quoting char if true
1539
+ function quoted($s) {
1540
+ if (preg_match('/^("|\').*?\1$/', $s, $m))
1541
+ return $m[1];
1542
+ else return false;
1543
+ }
1544
+
1545
+ /**
1546
+ * Helper function to get arguments for color functions.
1547
+ * Accepts invalid input, non colors interpreted as being black.
1548
+ */
1549
+ function colorArgs($args) {
1550
+ if ($args[0] != 'list' || count($args[2]) < 2) {
1551
+ return array(array('color', 0, 0, 0));
1552
+ }
1553
+ list($color, $delta) = $args[2];
1554
+ $color = $this->coerceColor($color);
1555
+ if (is_null($color))
1556
+ $color = array('color', 0, 0, 0);
1557
+
1558
+ $delta = floatval($delta[1]);
1559
+
1560
+ return array($color, $delta);
1561
+ }
1562
+
1563
+ function lib_darken($args) {
1564
+ list($color, $delta) = $this->colorArgs($args);
1565
+
1566
+ $hsl = $this->toHSL($color);
1567
+ $hsl[3] = $this->clamp($hsl[3] - $delta, 100);
1568
+ return $this->toRGB($hsl);
1569
+ }
1570
+
1571
+ function lib_lighten($args) {
1572
+ list($color, $delta) = $this->colorArgs($args);
1573
+
1574
+ $hsl = $this->toHSL($color);
1575
+ $hsl[3] = $this->clamp($hsl[3] + $delta, 100);
1576
+ return $this->toRGB($hsl);
1577
+ }
1578
+
1579
+ function lib_saturate($args) {
1580
+ list($color, $delta) = $this->colorArgs($args);
1581
+
1582
+ $hsl = $this->toHSL($color);
1583
+ $hsl[2] = $this->clamp($hsl[2] + $delta, 100);
1584
+ return $this->toRGB($hsl);
1585
+ }
1586
+
1587
+ function lib_desaturate($args) {
1588
+ list($color, $delta) = $this->colorArgs($args);
1589
+
1590
+ $hsl = $this->toHSL($color);
1591
+ $hsl[2] = $this->clamp($hsl[2] - $delta, 100);
1592
+ return $this->toRGB($hsl);
1593
+ }
1594
+
1595
+ function lib_spin($args) {
1596
+ list($color, $delta) = $this->colorArgs($args);
1597
+
1598
+ $hsl = $this->toHSL($color);
1599
+
1600
+ $hsl[1] = $hsl[1] + $delta % 360;
1601
+ if ($hsl[1] < 0) $hsl[1] += 360;
1602
+
1603
+ return $this->toRGB($hsl);
1604
+ }
1605
+
1606
+ function lib_fadeout($args) {
1607
+ list($color, $delta) = $this->colorArgs($args);
1608
+ $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) - $delta/100);
1609
+ return $color;
1610
+ }
1611
+
1612
+ function lib_fadein($args) {
1613
+ list($color, $delta) = $this->colorArgs($args);
1614
+ $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) + $delta/100);
1615
+ return $color;
1616
+ }
1617
+
1618
+ function lib_hue($color) {
1619
+ if ($color[0] != 'color') return 0;
1620
+ $hsl = $this->toHSL($color);
1621
+ return round($hsl[1]);
1622
+ }
1623
+
1624
+ function lib_saturation($color) {
1625
+ if ($color[0] != 'color') return 0;
1626
+ $hsl = $this->toHSL($color);
1627
+ return round($hsl[2]);
1628
+ }
1629
+
1630
+ function lib_lightness($color) {
1631
+ if ($color[0] != 'color') return 0;
1632
+ $hsl = $this->toHSL($color);
1633
+ return round($hsl[3]);
1634
+ }
1635
+
1636
+ // get the alpha of a color
1637
+ // defaults to 1 for non-colors or colors without an alpha
1638
+ function lib_alpha($color) {
1639
+ if ($color[0] != 'color') return 1;
1640
+ return isset($color[4]) ? $color[4] : 1;
1641
+ }
1642
+
1643
+ // set the alpha of the color
1644
+ function lib_fade($args) {
1645
+ list($color, $alpha) = $this->colorArgs($args);
1646
+ $color[4] = $this->clamp($alpha / 100.0);
1647
+ return $color;
1648
+ }
1649
+
1650
+ function lib_percentage($number) {
1651
+ return array('%', $number[1]*100);
1652
+ }
1653
+
1654
+ // mixes two colors by weight
1655
+ // mix(@color1, @color2, @weight);
1656
+ // http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_method
1657
+ function lib_mix($args) {
1658
+ if ($args[0] != "list" || count($args[2]) < 3)
1659
+ $this->throwError("mix expects (color1, color2, weight)");
1660
+
1661
+ list($first, $second, $weight) = $args[2];
1662
+ $first = $this->assertColor($first);
1663
+ $second = $this->assertColor($second);
1664
+
1665
+ $first_a = $this->lib_alpha($first);
1666
+ $second_a = $this->lib_alpha($second);
1667
+ $weight = $weight[1] / 100.0;
1668
+
1669
+ $w = $weight * 2 - 1;
1670
+ $a = $first_a - $second_a;
1671
+
1672
+ $w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0;
1673
+ $w2 = 1.0 - $w1;
1674
+
1675
+ $new = array('color',
1676
+ $w1 * $first[1] + $w2 * $second[1],
1677
+ $w1 * $first[2] + $w2 * $second[2],
1678
+ $w1 * $first[3] + $w2 * $second[3],
1679
+ );
1680
+
1681
+ if ($first_a != 1.0 || $second_a != 1.0) {
1682
+ $new[] = $first_a * $p + $second_a * ($p - 1);
1683
+ }
1684
+
1685
+ return $this->fixColor($new);
1686
+ }
1687
+
1688
+ function assertColor($value, $error = "expected color value") {
1689
+ $color = $this->coerceColor($value);
1690
+ if (is_null($color)) $this->throwError($error);
1691
+ return $color;
1692
+ }
1693
+
1694
+ function toHSL($color) {
1695
+ if ($color[0] == 'hsl') return $color;
1696
+
1697
+ $r = $color[1] / 255;
1698
+ $g = $color[2] / 255;
1699
+ $b = $color[3] / 255;
1700
+
1701
+ $min = min($r, $g, $b);
1702
+ $max = max($r, $g, $b);
1703
+
1704
+ $L = ($min + $max) / 2;
1705
+ if ($min == $max) {
1706
+ $S = $H = 0;
1707
+ } else {
1708
+ if ($L < 0.5)
1709
+ $S = ($max - $min)/($max + $min);
1710
+ else
1711
+ $S = ($max - $min)/(2.0 - $max - $min);
1712
+
1713
+ if ($r == $max) $H = ($g - $b)/($max - $min);
1714
+ elseif ($g == $max) $H = 2.0 + ($b - $r)/($max - $min);
1715
+ elseif ($b == $max) $H = 4.0 + ($r - $g)/($max - $min);
1716
+
1717
+ }
1718
+
1719
+ $out = array('hsl',
1720
+ ($H < 0 ? $H + 6 : $H)*60,
1721
+ $S*100,
1722
+ $L*100,
1723
+ );
1724
+
1725
+ if (count($color) > 4) $out[] = $color[4]; // copy alpha
1726
+ return $out;
1727
+ }
1728
+
1729
+ function toRGB_helper($comp, $temp1, $temp2) {
1730
+ if ($comp < 0) $comp += 1.0;
1731
+ elseif ($comp > 1) $comp -= 1.0;
1732
+
1733
+ if (6 * $comp < 1) return $temp1 + ($temp2 - $temp1) * 6 * $comp;
1734
+ if (2 * $comp < 1) return $temp2;
1735
+ if (3 * $comp < 2) return $temp1 + ($temp2 - $temp1)*((2/3) - $comp) * 6;
1736
+
1737
+ return $temp1;
1738
+ }
1739
+
1740
+ /**
1741
+ * Converts a hsl array into a color value in rgb.
1742
+ * Expects H to be in range of 0 to 360, S and L in 0 to 100
1743
+ */
1744
+ function toRGB($color) {
1745
+ if ($color == 'color') return $color;
1746
+
1747
+ $H = $color[1] / 360;
1748
+ $S = $color[2] / 100;
1749
+ $L = $color[3] / 100;
1750
+
1751
+ if ($S == 0) {
1752
+ $r = $g = $b = $L;
1753
+ } else {
1754
+ $temp2 = $L < 0.5 ?
1755
+ $L*(1.0 + $S) :
1756
+ $L + $S - $L * $S;
1757
+
1758
+ $temp1 = 2.0 * $L - $temp2;
1759
+
1760
+ $r = $this->toRGB_helper($H + 1/3, $temp1, $temp2);
1761
+ $g = $this->toRGB_helper($H, $temp1, $temp2);
1762
+ $b = $this->toRGB_helper($H - 1/3, $temp1, $temp2);
1763
+ }
1764
+
1765
+ // $out = array('color', round($r*255), round($g*255), round($b*255));
1766
+ $out = array('color', $r*255, $g*255, $b*255);
1767
+ if (count($color) > 4) $out[] = $color[4]; // copy alpha
1768
+ return $out;
1769
+ }
1770
+
1771
+ function clamp($v, $max = 1, $min = 0) {
1772
+ return min($max, max($min, $v));
1773
+ }
1774
+
1775
+ /**
1776
+ * Convert the rgb, rgba, hsl color literals of function type
1777
+ * as returned by the parser into values of color type.
1778
+ */
1779
+ function funcToColor($func) {
1780
+ $fname = $func[1];
1781
+ if ($func[2][0] != 'list') return false; // need a list of arguments
1782
+ $rawComponents = $func[2][2];
1783
+
1784
+ if ($fname == 'hsl' || $fname == 'hsla') {
1785
+ $hsl = array('hsl');
1786
+ $i = 0;
1787
+ foreach ($rawComponents as $c) {
1788
+ $val = $this->reduce($c);
1789
+ $val = isset($val[1]) ? floatval($val[1]) : 0;
1790
+
1791
+ if ($i == 0) $clamp = 360;
1792
+ elseif ($i < 4) $clamp = 100;
1793
+ else $clamp = 1;
1794
+
1795
+ $hsl[] = $this->clamp($val, $clamp);
1796
+ $i++;
1797
+ }
1798
+
1799
+ while (count($hsl) < 4) $hsl[] = 0;
1800
+ return $this->toRGB($hsl);
1801
+
1802
+ } elseif ($fname == 'rgb' || $fname == 'rgba') {
1803
+ $components = array();
1804
+ $i = 1;
1805
+ foreach ($rawComponents as $c) {
1806
+ $c = $this->reduce($c);
1807
+ if ($i < 4) {
1808
+ if ($c[0] == '%') $components[] = 255 * ($c[1] / 100);
1809
+ else $components[] = floatval($c[1]);
1810
+ } elseif ($i == 4) {
1811
+ if ($c[0] == '%') $components[] = 1.0 * ($c[1] / 100);
1812
+ else $components[] = floatval($c[1]);
1813
+ } else break;
1814
+
1815
+ $i++;
1816
+ }
1817
+ while (count($components) < 3) $components[] = 0;
1818
+ array_unshift($components, 'color');
1819
+ return $this->fixColor($components);
1820
+ }
1821
+
1822
+ return false;
1823
+ }
1824
+
1825
+ function toName($val) {
1826
+ switch($val[0]) {
1827
+ case "string":
1828
+ return substr($val[1], 1, -1);
1829
+ default:
1830
+ return $val[1];
1831
+ }
1832
+ }
1833
+
1834
+ // reduce a delayed type to its final value
1835
+ // dereference variables and solve equations
1836
+ function reduce($var) {
1837
+ // this is done here for infinite loop checking
1838
+ if ($var[0] == "variable") {
1839
+ $key = is_array($var[1]) ?
1840
+ $this->vPrefix.$this->toName($this->reduce($var[1])) : $var[1];
1841
+
1842
+ $seen =& $this->env->seenNames;
1843
+
1844
+ if (!empty($seen[$key])) {
1845
+ $this->throwError("infinite loop detected: $key");
1846
+ }
1847
+
1848
+ $seen[$key] = true;
1849
+
1850
+ $out = $this->reduce($this->get($key));
1851
+
1852
+ $seen[$key] = false;
1853
+
1854
+ return $out;
1855
+ }
1856
+
1857
+ while (in_array($var[0], self::$dtypes)) {
1858
+ if ($var[0] == 'list') {
1859
+ foreach ($var[2] as &$value) $value = $this->reduce($value);
1860
+ break;
1861
+ } elseif ($var[0] == 'expression') {
1862
+ $var = $this->evaluate($var[1], $var[2], $var[3]);
1863
+ } elseif ($var[0] == 'lookup') {
1864
+ // do accessor here....
1865
+ $var = array('number', 0);
1866
+ } elseif ($var[0] == 'function') {
1867
+ $color = $this->funcToColor($var);
1868
+ if ($color) $var = $color;
1869
+ else {
1870
+ list($_, $name, $args) = $var;
1871
+ if ($name == "%") $name = "_sprintf";
1872
+ $f = isset($this->libFunctions[$name]) ?
1873
+ $this->libFunctions[$name] : array($this, 'lib_'.$name);
1874
+
1875
+ if (is_callable($f)) {
1876
+ if ($args[0] == 'list')
1877
+ $args = $this->compressList($args[2], $args[1]);
1878
+
1879
+ $var = call_user_func($f, $this->reduce($args), $this);
1880
+
1881
+ // convert to a typed value if the result is a php primitive
1882
+ if (is_numeric($var)) $var = array('number', $var);
1883
+ elseif (!is_array($var)) $var = array('keyword', $var);
1884
+ } else {
1885
+ // plain function, reduce args
1886
+ $var[2] = $this->reduce($var[2]);
1887
+ }
1888
+ }
1889
+ break; // done reducing after a function
1890
+ } elseif ($var[0] == 'negative') {
1891
+ $value = $this->reduce($var[1]);
1892
+ if (is_numeric($value[1])) {
1893
+ $value[1] = -1*$value[1];
1894
+ }
1895
+ $var = $value;
1896
+ }
1897
+ }
1898
+
1899
+ return $var;
1900
+ }
1901
+
1902
+ function coerceColor($value) {
1903
+ switch($value[0]) {
1904
+ case 'color': return $value;
1905
+ case 'keyword':
1906
+ $name = $value[1];
1907
+ if (isset(self::$cssColors[$name])) {
1908
+ list($r, $g, $b) = explode(',', self::$cssColors[$name]);
1909
+ return array('color', $r, $g, $b);
1910
+ }
1911
+ return null;
1912
+ }
1913
+ }
1914
+
1915
+ function toBool($a) {
1916
+ if ($a) return self::$TRUE;
1917
+ else return self::$FALSE;
1918
+ }
1919
+
1920
+ // evaluate an expression
1921
+ function evaluate($op, $left, $right) {
1922
+ $left = $this->reduce($left);
1923
+ $right = $this->reduce($right);
1924
+
1925
+ if ($left_color = $this->coerceColor($left)) {
1926
+ $left = $left_color;
1927
+ }
1928
+
1929
+ if ($right_color = $this->coerceColor($right)) {
1930
+ $right = $right_color;
1931
+ }
1932
+
1933
+ if ($op == "and") {
1934
+ return $this->toBool($left == self::$TRUE && $right == self::$TRUE);
1935
+ }
1936
+
1937
+ if ($op == "=") {
1938
+ return $this->toBool($this->eq($left, $right) );
1939
+ }
1940
+
1941
+ if ($left[0] == 'color' && $right[0] == 'color') {
1942
+ $out = $this->op_color_color($op, $left, $right);
1943
+ return $out;
1944
+ }
1945
+
1946
+ if ($left[0] == 'color') {
1947
+ return $this->op_color_number($op, $left, $right);
1948
+ }
1949
+
1950
+ if ($right[0] == 'color') {
1951
+ return $this->op_number_color($op, $left, $right);
1952
+ }
1953
+
1954
+ // concatenate strings
1955
+ if ($op == '+' && $left[0] == 'string') {
1956
+ $append = $this->compileValue($right);
1957
+ if ($this->quoted($append)) $append = substr($append, 1, -1);
1958
+
1959
+ $lhs = $this->compileValue($left);
1960
+ if ($q = $this->quoted($lhs)) $lhs = substr($lhs, 1, -1);
1961
+ if (!$q) $q = '';
1962
+
1963
+ return array('string', $q.$lhs.$append.$q);
1964
+ }
1965
+
1966
+ if ($left[0] == 'keyword' || $right[0] == 'keyword' ||
1967
+ $left[0] == 'string' || $right[0] == 'string')
1968
+ {
1969
+ // look for negative op
1970
+ if ($op == '-') $right[1] = '-'.$right[1];
1971
+ return array('keyword', $this->compileValue($left) .' '. $this->compileValue($right));
1972
+ }
1973
+
1974
+ // default to number operation
1975
+ return $this->op_number_number($op, $left, $right);
1976
+ }
1977
+
1978
+ // make sure a color's components don't go out of bounds
1979
+ function fixColor($c) {
1980
+ foreach (range(1, 3) as $i) {
1981
+ if ($c[$i] < 0) $c[$i] = 0;
1982
+ if ($c[$i] > 255) $c[$i] = 255;
1983
+ }
1984
+
1985
+ return $c;
1986
+ }
1987
+
1988
+ function op_number_color($op, $lft, $rgt) {
1989
+ if ($op == '+' || $op = '*') {
1990
+ return $this->op_color_number($op, $rgt, $lft);
1991
+ }
1992
+ }
1993
+
1994
+ function op_color_number($op, $lft, $rgt) {
1995
+ if ($rgt[0] == '%') $rgt[1] /= 100;
1996
+
1997
+ return $this->op_color_color($op, $lft,
1998
+ array_fill(1, count($lft) - 1, $rgt[1]));
1999
+ }
2000
+
2001
+ function op_color_color($op, $left, $right) {
2002
+ $out = array('color');
2003
+ $max = count($left) > count($right) ? count($left) : count($right);
2004
+ foreach (range(1, $max - 1) as $i) {
2005
+ $lval = isset($left[$i]) ? $left[$i] : 0;
2006
+ $rval = isset($right[$i]) ? $right[$i] : 0;
2007
+ switch ($op) {
2008
+ case '+':
2009
+ $out[] = $lval + $rval;
2010
+ break;
2011
+ case '-':
2012
+ $out[] = $lval - $rval;
2013
+ break;
2014
+ case '*':
2015
+ $out[] = $lval * $rval;
2016
+ break;
2017
+ case '%':
2018
+ $out[] = $lval % $rval;
2019
+ break;
2020
+ case '/':
2021
+ if ($rval == 0) $this->throwError("evaluate error: can't divide by zero");
2022
+ $out[] = $lval / $rval;
2023
+ break;
2024
+ default:
2025
+ $this->throwError('evaluate error: color op number failed on op '.$op);
2026
+ }
2027
+ }
2028
+ return $this->fixColor($out);
2029
+ }
2030
+
2031
+ // operator on two numbers
2032
+ function op_number_number($op, $left, $right) {
2033
+ $type = is_null($left) ? "number" : $left[0];
2034
+ if ($type == "number") $type = $right[0];
2035
+
2036
+ $value = 0;
2037
+ switch ($op) {
2038
+ case '+':
2039
+ $value = $left[1] + $right[1];
2040
+ break;
2041
+ case '*':
2042
+ $value = $left[1] * $right[1];
2043
+ break;
2044
+ case '-':
2045
+ $value = $left[1] - $right[1];
2046
+ break;
2047
+ case '%':
2048
+ $value = $left[1] % $right[1];
2049
+ break;
2050
+ case '/':
2051
+ if ($right[1] == 0) $this->throwError('parse error: divide by zero');
2052
+ $value = $left[1] / $right[1];
2053
+ break;
2054
+ case '<':
2055
+ return $this->toBool($left[1] < $right[1]);
2056
+ case '>':
2057
+ return $this->toBool($left[1] > $right[1]);
2058
+ case '>=':
2059
+ return $this->toBool($left[1] >= $right[1]);
2060
+ case '=<':
2061
+ return $this->toBool($left[1] <= $right[1]);
2062
+ default:
2063
+ $this->throwError('parse error: unknown number operator: '.$op);
2064
+ }
2065
+
2066
+ return array($type, $value);
2067
+ }
2068
+
2069
+
2070
+ /* environment functions */
2071
+
2072
+ // push a new block on the stack, used for parsing
2073
+ function pushBlock($tags) {
2074
+ $b = new stdclass;
2075
+ $b->parent = $this->env;
2076
+
2077
+ $b->id = self::$nextBlockId++;
2078
+ $b->tags = $tags;
2079
+ $b->props = array();
2080
+ $b->children = array();
2081
+
2082
+ $this->env = $b;
2083
+ return $b;
2084
+ }
2085
+
2086
+ // push a block that doesn't multiply tags
2087
+ function pushSpecialBlock($name) {
2088
+ $b = $this->pushBlock(array($name));
2089
+ $b->no_multiply = true;
2090
+ return $b;
2091
+ }
2092
+
2093
+ // used for compiliation variable state
2094
+ function pushEnv() {
2095
+ $e = new stdclass;
2096
+ $e->parent = $this->env;
2097
+
2098
+ $this->store = array();
2099
+
2100
+ $this->env = $e;
2101
+ return $e;
2102
+ }
2103
+
2104
+ // pop something off the stack
2105
+ function pop() {
2106
+ $old = $this->env;
2107
+ $this->env = $this->env->parent;
2108
+ return $old;
2109
+ }
2110
+
2111
+ // set something in the current env
2112
+ function set($name, $value) {
2113
+ $this->env->store[$name] = $value;
2114
+ }
2115
+
2116
+ // append an property
2117
+ function append($prop, $pos = null) {
2118
+ if (!is_null($pos)) $prop[-1] = $pos;
2119
+ $this->env->props[] = $prop;
2120
+ }
2121
+
2122
+ // get the highest occurrence entry for a name
2123
+ function get($name) {
2124
+ $current = $this->env;
2125
+
2126
+ $is_arguments = $name == $this->vPrefix . 'arguments';
2127
+ while ($current) {
2128
+ if ($is_arguments && isset($current->arguments)) {
2129
+ return array('list', ' ', $current->arguments);
2130
+ }
2131
+
2132
+ if (isset($current->store[$name]))
2133
+ return $current->store[$name];
2134
+ else
2135
+ $current = $current->parent;
2136
+ }
2137
+
2138
+ return null;
2139
+ }
2140
+
2141
+ /* raw parsing functions */
2142
+
2143
+ function literal($what, $eatWhitespace = true) {
2144
+ // this is here mainly prevent notice from { } string accessor
2145
+ if ($this->count >= strlen($this->buffer)) return false;
2146
+
2147
+ // shortcut on single letter
2148
+ if (!$eatWhitespace && strlen($what) == 1) {
2149
+ if ($this->buffer{$this->count} == $what) {
2150
+ $this->count++;
2151
+ return true;
2152
+ }
2153
+ else return false;
2154
+ }
2155
+
2156
+ return $this->match($this->preg_quote($what), $m, $eatWhitespace);
2157
+ }
2158
+
2159
+ function preg_quote($what) {
2160
+ return preg_quote($what, '/');
2161
+ }
2162
+
2163
+ // advance counter to next occurrence of $what
2164
+ // $until - don't include $what in advance
2165
+ // $allowNewline, if string, will be used as valid char set
2166
+ function to($what, &$out, $until = false, $allowNewline = false) {
2167
+ if (is_string($allowNewline)) {
2168
+ $validChars = $allowNewline;
2169
+ } else {
2170
+ $validChars = $allowNewline ? "." : "[^\n]";
2171
+ }
2172
+ if (!$this->match('('.$validChars.'*?)'.$this->preg_quote($what), $m, !$until)) return false;
2173
+ if ($until) $this->count -= strlen($what); // give back $what
2174
+ $out = $m[1];
2175
+ return true;
2176
+ }
2177
+
2178
+ // try to match something on head of buffer
2179
+ function match($regex, &$out, $eatWhitespace = true) {
2180
+ $r = '/'.$regex.($eatWhitespace ? '\s*' : '').'/Ais';
2181
+ if (preg_match($r, $this->buffer, $out, null, $this->count)) {
2182
+ $this->count += strlen($out[0]);
2183
+ return true;
2184
+ }
2185
+ return false;
2186
+ }
2187
+
2188
+ // match something without consuming it
2189
+ function peek($regex, &$out = null) {
2190
+ $r = '/'.$regex.'/Ais';
2191
+ $result = preg_match($r, $this->buffer, $out, null, $this->count);
2192
+
2193
+ return $result;
2194
+ }
2195
+
2196
+ // seek to a spot in the buffer or return where we are on no argument
2197
+ function seek($where = null) {
2198
+ if ($where === null) return $this->count;
2199
+ else $this->count = $where;
2200
+ return true;
2201
+ }
2202
+
2203
+ /**
2204
+ * Initialize state for a fresh parse
2205
+ */
2206
+ protected function prepareParser($buff) {
2207
+ $this->env = null;
2208
+ $this->expandStack = array();
2209
+ $this->indentLevel = 0;
2210
+ $this->count = 0;
2211
+ $this->line = 1;
2212
+
2213
+ $this->buffer = $this->removeComments($buff);
2214
+ $this->pushBlock(null); // set up global scope
2215
+
2216
+ // trim whitespace on head
2217
+ if (preg_match('/^\s+/', $this->buffer, $m)) {
2218
+ $this->line += substr_count($m[0], "\n");
2219
+ $this->buffer = ltrim($this->buffer);
2220
+ }
2221
+ }
2222
+
2223
+ // create a child parser (for compiling an import)
2224
+ protected function createChild($fname) {
2225
+ $less = new lessc($fname);
2226
+ $less->importDir = array_merge((array)$less->importDir, (array)$this->importDir);
2227
+ $less->indentChar = $this->indentChar;
2228
+ $less->compat = $this->compat;
2229
+ return $less;
2230
+ }
2231
+
2232
+ // parse code and return intermediate tree
2233
+ public function parseTree($str = null) {
2234
+ $this->prepareParser(is_null($str) ? $this->buffer : $str);
2235
+ while (false !== $this->parseChunk());
2236
+
2237
+ if ($this->count != strlen($this->buffer))
2238
+ $this->throwError();
2239
+
2240
+ if (!is_null($this->env->parent))
2241
+ throw new exception('parse error: unclosed block');
2242
+
2243
+ $root = $this->env;
2244
+ $this->env = null;
2245
+ return $root;
2246
+ }
2247
+
2248
+ // inject array of unparsed strings into environment as variables
2249
+ protected function injectVariables($args) {
2250
+ $this->pushEnv();
2251
+ $parser = new lessc();
2252
+ foreach ($args as $name => $str_value) {
2253
+ if ($name{0} != '@') $name = '@'.$name;
2254
+ $parser->count = 0;
2255
+ $parser->buffer = (string)$str_value;
2256
+ if (!$parser->propertyValue($value)) {
2257
+ throw new Exception("failed to parse passed in variable $name: $str_value");
2258
+ }
2259
+
2260
+ $this->set($name, $value);
2261
+ }
2262
+ }
2263
+
2264
+ // parse and compile buffer
2265
+ function parse($str = null, $initial_variables = null) {
2266
+ $locale = setlocale(LC_NUMERIC, 0);
2267
+ setlocale(LC_NUMERIC, "C");
2268
+ $root = $this->parseTree($str);
2269
+
2270
+ if ($initial_variables) $this->injectVariables($initial_variables);
2271
+ $out = $this->compileBlock($root);
2272
+ setlocale(LC_NUMERIC, $locale);
2273
+ return $out;
2274
+ }
2275
+
2276
+ /**
2277
+ * Uses the current value of $this->count to show line and line number
2278
+ */
2279
+ function throwError($msg = 'parse error') {
2280
+ if (!empty($this->sourceParser)) {
2281
+ $this->sourceParser->count = $this->count;
2282
+ return $this->sourceParser->throwError($msg);
2283
+ } elseif ($this->count > 0) {
2284
+ $line = $this->line + substr_count(substr($this->buffer, 0, $this->count), "\n");
2285
+ if (isset($this->fileName)) {
2286
+ $loc = $this->fileName.' on line '.$line;
2287
+ } else {
2288
+ $loc = "line: ".$line;
2289
+ }
2290
+
2291
+ if ($this->peek("(.*?)(\n|$)", $m))
2292
+ throw new exception($msg.': failed at `'.$m[1].'` '.$loc);
2293
+ }
2294
+
2295
+ throw new exception($msg);
2296
+ }
2297
+
2298
+ /**
2299
+ * Initialize any static state, can initialize parser for a file
2300
+ */
2301
+ function __construct($fname = null, $opts = null) {
2302
+ if (!self::$operatorString) {
2303
+ self::$operatorString =
2304
+ '('.implode('|', array_map(array($this, 'preg_quote'),
2305
+ array_keys(self::$precedence))).')';
2306
+ }
2307
+
2308
+ if ($fname) {
2309
+ if (!is_file($fname)) {
2310
+ throw new Exception('load error: failed to find '.$fname);
2311
+ }
2312
+ $pi = pathinfo($fname);
2313
+
2314
+ $this->fileName = $fname;
2315
+ $this->importDir = $pi['dirname'].'/';
2316
+ $this->buffer = file_get_contents($fname);
2317
+
2318
+ $this->addParsedFile($fname);
2319
+ }
2320
+ }
2321
+
2322
+ public function registerFunction($name, $func) {
2323
+ $this->libFunctions[$name] = $func;
2324
+ }
2325
+
2326
+ public function unregisterFunction($name) {
2327
+ unset($this->libFunctions[$name]);
2328
+ }
2329
+
2330
+ // remove comments from $text
2331
+ // todo: make it work for all functions, not just url
2332
+ function removeComments($text) {
2333
+ $look = array(
2334
+ 'url(', '//', '/*', '"', "'"
2335
+ );
2336
+
2337
+ $out = '';
2338
+ $min = null;
2339
+ $done = false;
2340
+ while (true) {
2341
+ // find the next item
2342
+ foreach ($look as $token) {
2343
+ $pos = strpos($text, $token);
2344
+ if ($pos !== false) {
2345
+ if (!isset($min) || $pos < $min[1]) $min = array($token, $pos);
2346
+ }
2347
+ }
2348
+
2349
+ if (is_null($min)) break;
2350
+
2351
+ $count = $min[1];
2352
+ $skip = 0;
2353
+ $newlines = 0;
2354
+ switch ($min[0]) {
2355
+ case 'url(':
2356
+ if (preg_match('/url\(.*?\)/', $text, $m, 0, $count))
2357
+ $count += strlen($m[0]) - strlen($min[0]);
2358
+ break;
2359
+ case '"':
2360
+ case "'":
2361
+ if (preg_match('/'.$min[0].'.*?'.$min[0].'/', $text, $m, 0, $count))
2362
+ $count += strlen($m[0]) - 1;
2363
+ break;
2364
+ case '//':
2365
+ $skip = strpos($text, "\n", $count);
2366
+ if ($skip === false) $skip = strlen($text) - $count;
2367
+ else $skip -= $count;
2368
+ break;
2369
+ case '/*':
2370
+ if (preg_match('/\/\*.*?\*\//s', $text, $m, 0, $count)) {
2371
+ $skip = strlen($m[0]);
2372
+ $newlines = substr_count($m[0], "\n");
2373
+ }
2374
+ break;
2375
+ }
2376
+
2377
+ if ($skip == 0) $count += strlen($min[0]);
2378
+
2379
+ $out .= substr($text, 0, $count).str_repeat("\n", $newlines);
2380
+ $text = substr($text, $count + $skip);
2381
+
2382
+ $min = null;
2383
+ }
2384
+
2385
+ return $out.$text;
2386
+ }
2387
+
2388
+ public function allParsedFiles() { return $this->allParsedFiles; }
2389
+ protected function addParsedFile($file) {
2390
+ $this->allParsedFiles[realpath($file)] = filemtime($file);
2391
+ }
2392
+
2393
+
2394
+ // compile file $in to file $out if $in is newer than $out
2395
+ // returns true when it compiles, false otherwise
2396
+ public static function ccompile($in, $out) {
2397
+ if (!is_file($out) || filemtime($in) > filemtime($out)) {
2398
+ $less = new lessc($in);
2399
+ file_put_contents($out, $less->parse());
2400
+ return true;
2401
+ }
2402
+
2403
+ return false;
2404
+ }
2405
+
2406
+ /**
2407
+ * Execute lessphp on a .less file or a lessphp cache structure
2408
+ *
2409
+ * The lessphp cache structure contains information about a specific
2410
+ * less file having been parsed. It can be used as a hint for future
2411
+ * calls to determine whether or not a rebuild is required.
2412
+ *
2413
+ * The cache structure contains two important keys that may be used
2414
+ * externally:
2415
+ *
2416
+ * compiled: The final compiled CSS
2417
+ * updated: The time (in seconds) the CSS was last compiled
2418
+ *
2419
+ * The cache structure is a plain-ol' PHP associative array and can
2420
+ * be serialized and unserialized without a hitch.
2421
+ *
2422
+ * @param mixed $in Input
2423
+ * @param bool $force Force rebuild?
2424
+ * @return array lessphp cache structure
2425
+ */
2426
+ public static function cexecute($in, $force = false) {
2427
+
2428
+ // assume no root
2429
+ $root = null;
2430
+
2431
+ if (is_string($in)) {
2432
+ $root = $in;
2433
+ } elseif (is_array($in) and isset($in['root'])) {
2434
+ if ($force or ! isset($in['files'])) {
2435
+ // If we are forcing a recompile or if for some reason the
2436
+ // structure does not contain any file information we should
2437
+ // specify the root to trigger a rebuild.
2438
+ $root = $in['root'];
2439
+ } elseif (isset($in['files']) and is_array($in['files'])) {
2440
+ foreach ($in['files'] as $fname => $ftime ) {
2441
+ if (!file_exists($fname) or filemtime($fname) > $ftime) {
2442
+ // One of the files we knew about previously has changed
2443
+ // so we should look at our incoming root again.
2444
+ $root = $in['root'];
2445
+ break;
2446
+ }
2447
+ }
2448
+ }
2449
+ } else {
2450
+ // TODO: Throw an exception? We got neither a string nor something
2451
+ // that looks like a compatible lessphp cache structure.
2452
+ return null;
2453
+ }
2454
+
2455
+ if ($root !== null) {
2456
+ // If we have a root value which means we should rebuild.
2457
+ $less = new lessc($root);
2458
+ $out = array();
2459
+ $out['root'] = $root;
2460
+ $out['compiled'] = $less->parse();
2461
+ $out['files'] = $less->allParsedFiles();
2462
+ $out['updated'] = time();
2463
+ return $out;
2464
+ } else {
2465
+ // No changes, pass back the structure
2466
+ // we were given initially.
2467
+ return $in;
2468
+ }
2469
+
2470
+ }
2471
+
2472
+ static protected $cssColors = array(
2473
+ 'aliceblue' => '240,248,255',
2474
+ 'antiquewhite' => '250,235,215',
2475
+ 'aqua' => '0,255,255',
2476
+ 'aquamarine' => '127,255,212',
2477
+ 'azure' => '240,255,255',
2478
+ 'beige' => '245,245,220',
2479
+ 'bisque' => '255,228,196',
2480
+ 'black' => '0,0,0',
2481
+ 'blanchedalmond' => '255,235,205',
2482
+ 'blue' => '0,0,255',
2483
+ 'blueviolet' => '138,43,226',
2484
+ 'brown' => '165,42,42',
2485
+ 'burlywood' => '222,184,135',
2486
+ 'cadetblue' => '95,158,160',
2487
+ 'chartreuse' => '127,255,0',
2488
+ 'chocolate' => '210,105,30',
2489
+ 'coral' => '255,127,80',
2490
+ 'cornflowerblue' => '100,149,237',
2491
+ 'cornsilk' => '255,248,220',
2492
+ 'crimson' => '220,20,60',
2493
+ 'cyan' => '0,255,255',
2494
+ 'darkblue' => '0,0,139',
2495
+ 'darkcyan' => '0,139,139',
2496
+ 'darkgoldenrod' => '184,134,11',
2497
+ 'darkgray' => '169,169,169',
2498
+ 'darkgreen' => '0,100,0',
2499
+ 'darkgrey' => '169,169,169',
2500
+ 'darkkhaki' => '189,183,107',
2501
+ 'darkmagenta' => '139,0,139',
2502
+ 'darkolivegreen' => '85,107,47',
2503
+ 'darkorange' => '255,140,0',
2504
+ 'darkorchid' => '153,50,204',
2505
+ 'darkred' => '139,0,0',
2506
+ 'darksalmon' => '233,150,122',
2507
+ 'darkseagreen' => '143,188,143',
2508
+ 'darkslateblue' => '72,61,139',
2509
+ 'darkslategray' => '47,79,79',
2510
+ 'darkslategrey' => '47,79,79',
2511
+ 'darkturquoise' => '0,206,209',
2512
+ 'darkviolet' => '148,0,211',
2513
+ 'deeppink' => '255,20,147',
2514
+ 'deepskyblue' => '0,191,255',
2515
+ 'dimgray' => '105,105,105',
2516
+ 'dimgrey' => '105,105,105',
2517
+ 'dodgerblue' => '30,144,255',
2518
+ 'firebrick' => '178,34,34',
2519
+ 'floralwhite' => '255,250,240',
2520
+ 'forestgreen' => '34,139,34',
2521
+ 'fuchsia' => '255,0,255',
2522
+ 'gainsboro' => '220,220,220',
2523
+ 'ghostwhite' => '248,248,255',
2524
+ 'gold' => '255,215,0',
2525
+ 'goldenrod' => '218,165,32',
2526
+ 'gray' => '128,128,128',
2527
+ 'green' => '0,128,0',
2528
+ 'greenyellow' => '173,255,47',
2529
+ 'grey' => '128,128,128',
2530
+ 'honeydew' => '240,255,240',
2531
+ 'hotpink' => '255,105,180',
2532
+ 'indianred' => '205,92,92',
2533
+ 'indigo' => '75,0,130',
2534
+ 'ivory' => '255,255,240',
2535
+ 'khaki' => '240,230,140',
2536
+ 'lavender' => '230,230,250',
2537
+ 'lavenderblush' => '255,240,245',
2538
+ 'lawngreen' => '124,252,0',
2539
+ 'lemonchiffon' => '255,250,205',
2540
+ 'lightblue' => '173,216,230',
2541
+ 'lightcoral' => '240,128,128',
2542
+ 'lightcyan' => '224,255,255',
2543
+ 'lightgoldenrodyellow' => '250,250,210',
2544
+ 'lightgray' => '211,211,211',
2545
+ 'lightgreen' => '144,238,144',
2546
+ 'lightgrey' => '211,211,211',
2547
+ 'lightpink' => '255,182,193',
2548
+ 'lightsalmon' => '255,160,122',
2549
+ 'lightseagreen' => '32,178,170',
2550
+ 'lightskyblue' => '135,206,250',
2551
+ 'lightslategray' => '119,136,153',
2552
+ 'lightslategrey' => '119,136,153',
2553
+ 'lightsteelblue' => '176,196,222',
2554
+ 'lightyellow' => '255,255,224',
2555
+ 'lime' => '0,255,0',
2556
+ 'limegreen' => '50,205,50',
2557
+ 'linen' => '250,240,230',
2558
+ 'magenta' => '255,0,255',
2559
+ 'maroon' => '128,0,0',
2560
+ 'mediumaquamarine' => '102,205,170',
2561
+ 'mediumblue' => '0,0,205',
2562
+ 'mediumorchid' => '186,85,211',
2563
+ 'mediumpurple' => '147,112,219',
2564
+ 'mediumseagreen' => '60,179,113',
2565
+ 'mediumslateblue' => '123,104,238',
2566
+ 'mediumspringgreen' => '0,250,154',
2567
+ 'mediumturquoise' => '72,209,204',
2568
+ 'mediumvioletred' => '199,21,133',
2569
+ 'midnightblue' => '25,25,112',
2570
+ 'mintcream' => '245,255,250',
2571
+ 'mistyrose' => '255,228,225',
2572
+ 'moccasin' => '255,228,181',
2573
+ 'navajowhite' => '255,222,173',
2574
+ 'navy' => '0,0,128',
2575
+ 'oldlace' => '253,245,230',
2576
+ 'olive' => '128,128,0',
2577
+ 'olivedrab' => '107,142,35',
2578
+ 'orange' => '255,165,0',
2579
+ 'orangered' => '255,69,0',
2580
+ 'orchid' => '218,112,214',
2581
+ 'palegoldenrod' => '238,232,170',
2582
+ 'palegreen' => '152,251,152',
2583
+ 'paleturquoise' => '175,238,238',
2584
+ 'palevioletred' => '219,112,147',
2585
+ 'papayawhip' => '255,239,213',
2586
+ 'peachpuff' => '255,218,185',
2587
+ 'peru' => '205,133,63',
2588
+ 'pink' => '255,192,203',
2589
+ 'plum' => '221,160,221',
2590
+ 'powderblue' => '176,224,230',
2591
+ 'purple' => '128,0,128',
2592
+ 'red' => '255,0,0',
2593
+ 'rosybrown' => '188,143,143',
2594
+ 'royalblue' => '65,105,225',
2595
+ 'saddlebrown' => '139,69,19',
2596
+ 'salmon' => '250,128,114',
2597
+ 'sandybrown' => '244,164,96',
2598
+ 'seagreen' => '46,139,87',
2599
+ 'seashell' => '255,245,238',
2600
+ 'sienna' => '160,82,45',
2601
+ 'silver' => '192,192,192',
2602
+ 'skyblue' => '135,206,235',
2603
+ 'slateblue' => '106,90,205',
2604
+ 'slategray' => '112,128,144',
2605
+ 'slategrey' => '112,128,144',
2606
+ 'snow' => '255,250,250',
2607
+ 'springgreen' => '0,255,127',
2608
+ 'steelblue' => '70,130,180',
2609
+ 'tan' => '210,180,140',
2610
+ 'teal' => '0,128,128',
2611
+ 'thistle' => '216,191,216',
2612
+ 'tomato' => '255,99,71',
2613
+ 'turquoise' => '64,224,208',
2614
+ 'violet' => '238,130,238',
2615
+ 'wheat' => '245,222,179',
2616
+ 'white' => '255,255,255',
2617
+ 'whitesmoke' => '245,245,245',
2618
+ 'yellow' => '255,255,0',
2619
+ 'yellowgreen' => '154,205,50'
2620
+ );
2621
+ }
2622
+
lib/lessphp/lessify ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/php -q
2
+ <?php
3
+
4
+ if (php_sapi_name() != "cli") {
5
+ err($fa.$argv[0]." must be run in the command line.");
6
+ exit(1);
7
+ }
8
+ $exe = array_shift($argv); // remove filename
9
+
10
+ if (!$fname = array_shift($argv)) {
11
+ exit("Usage: ".$exe." input-file\n");
12
+ }
13
+
14
+ require "lessify.inc.php";
15
+
16
+ try {
17
+ $parser = new lessify($fname);
18
+ echo $parser->parse();
19
+ } catch (exception $e) {
20
+ exit("Fatal error: ".$e->getMessage()."\n");
21
+ }
22
+
23
+
lib/lessphp/lessify.inc.php ADDED
@@ -0,0 +1,447 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * lessify
4
+ * Convert a css file into a less file
5
+ * http://leafo.net/lessphp
6
+ * Copyright 2010, leaf corcoran <leafot@gmail.com>
7
+ *
8
+ * WARNING: THIS DOES NOT WORK ANYMORE. NEEDS TO BE UPDATED FOR
9
+ * LATEST VERSION OF LESSPHP.
10
+ *
11
+ */
12
+
13
+ require "lessc.inc.php";
14
+
15
+ //
16
+ // check if the merge during mixin is overwriting values. should or should it not?
17
+ //
18
+
19
+ //
20
+ // 1. split apart class tags
21
+ //
22
+
23
+ class easyparse {
24
+ var $buffer;
25
+ var $count;
26
+
27
+ function __construct($str) {
28
+ $this->count = 0;
29
+ $this->buffer = trim($str);
30
+ }
31
+
32
+ function seek($where = null) {
33
+ if ($where === null) return $this->count;
34
+ else $this->count = $where;
35
+ return true;
36
+ }
37
+
38
+ function preg_quote($what) {
39
+ return preg_quote($what, '/');
40
+ }
41
+
42
+ function match($regex, &$out, $eatWhitespace = true) {
43
+ $r = '/'.$regex.($eatWhitespace ? '\s*' : '').'/Ais';
44
+ if (preg_match($r, $this->buffer, $out, null, $this->count)) {
45
+ $this->count += strlen($out[0]);
46
+ return true;
47
+ }
48
+ return false;
49
+ }
50
+
51
+ function literal($what, $eatWhitespace = true) {
52
+ // this is here mainly prevent notice from { } string accessor
53
+ if ($this->count >= strlen($this->buffer)) return false;
54
+
55
+ // shortcut on single letter
56
+ if (!$eatWhitespace and strlen($what) == 1) {
57
+ if ($this->buffer{$this->count} == $what) {
58
+ $this->count++;
59
+ return true;
60
+ }
61
+ else return false;
62
+ }
63
+
64
+ return $this->match($this->preg_quote($what), $m, $eatWhitespace);
65
+ }
66
+
67
+ }
68
+
69
+ class tagparse extends easyparse {
70
+ static private $combinators = null;
71
+ static private $match_opts = null;
72
+
73
+ function parse() {
74
+ if (empty(self::$combinators)) {
75
+ self::$combinators = '('.implode('|', array_map(array($this, 'preg_quote'),
76
+ array('+', '>', '~'))).')';
77
+ self::$match_opts = '('.implode('|', array_map(array($this, 'preg_quote'),
78
+ array('=', '~=', '|=', '$=', '*='))).')';
79
+ }
80
+
81
+ // crush whitespace
82
+ $this->buffer = preg_replace('/\s+/', ' ', $this->buffer).' ';
83
+
84
+ $tags = array();
85
+ while ($this->tag($t)) $tags[] = $t;
86
+
87
+ return $tags;
88
+ }
89
+
90
+ static function compileString($string) {
91
+ list(, $delim, $str) = $string;
92
+ $str = str_replace($delim, "\\".$delim, $str);
93
+ $str = str_replace("\n", "\\\n", $str);
94
+ return $delim.$str.$delim;
95
+ }
96
+
97
+ static function compilePaths($paths) {
98
+ return implode(', ', array_map(array('self', 'compilePath'), $paths));
99
+ }
100
+
101
+ // array of tags
102
+ static function compilePath($path) {
103
+ return implode(' ', array_map(array('self', 'compileTag'), $path));
104
+ }
105
+
106
+
107
+ static function compileTag($tag) {
108
+ ob_start();
109
+ if (isset($tag['comb'])) echo $tag['comb']." ";
110
+ if (isset($tag['front'])) echo $tag['front'];
111
+ if (isset($tag['attr'])) {
112
+ echo '['.$tag['attr'];
113
+ if (isset($tag['op'])) {
114
+ echo $tag['op'].$tag['op_value'];
115
+ }
116
+ echo ']';
117
+ }
118
+ return ob_get_clean();
119
+ }
120
+
121
+ function string(&$out) {
122
+ $s = $this->seek();
123
+
124
+ if ($this->literal('"')) {
125
+ $delim = '"';
126
+ } elseif ($this->literal("'")) {
127
+ $delim = "'";
128
+ } else {
129
+ return false;
130
+ }
131
+
132
+ while (true) {
133
+ // step through letters looking for either end or escape
134
+ $buff = "";
135
+ $escapeNext = false;
136
+ $finished = false;
137
+ for ($i = $this->count; $i < strlen($this->buffer); $i++) {
138
+ $char = $this->buffer[$i];
139
+ switch ($char) {
140
+ case $delim:
141
+ if ($escapeNext) {
142
+ $buff .= $char;
143
+ $escapeNext = false;
144
+ break;
145
+ }
146
+ $finished = true;
147
+ break 2;
148
+ case "\\":
149
+ if ($escapeNext) {
150
+ $buff .= $char;
151
+ $escapeNext = false;
152
+ } else {
153
+ $escapeNext = true;
154
+ }
155
+ break;
156
+ case "\n":
157
+ if (!$escapeNext) {
158
+ break 3;
159
+ }
160
+
161
+ $buff .= $char;
162
+ $escapeNext = false;
163
+ break;
164
+ default:
165
+ if ($escapeNext) {
166
+ $buff .= "\\";
167
+ $escapeNext = false;
168
+ }
169
+ $buff .= $char;
170
+ }
171
+ }
172
+ if (!$finished) break;
173
+ $out = array('string', $delim, $buff);
174
+ $this->seek($i+1);
175
+ return true;
176
+ }
177
+
178
+ $this->seek($s);
179
+ return false;
180
+ }
181
+
182
+ function tag(&$out) {
183
+ $s = $this->seek();
184
+ $tag = array();
185
+ if ($this->combinator($op)) $tag['comb'] = $op;
186
+
187
+ if (!$this->match('(.*?)( |$|\[|'.self::$combinators.')', $match)) {
188
+ $this->seek($s);
189
+ return false;
190
+ }
191
+
192
+ if (!empty($match[3])) {
193
+ // give back combinator
194
+ $this->count-=strlen($match[3]);
195
+ }
196
+
197
+ if (!empty($match[1])) $tag['front'] = $match[1];
198
+
199
+ if ($match[2] == '[') {
200
+ if ($this->ident($i)) {
201
+ $tag['attr'] = $i;
202
+
203
+ if ($this->match(self::$match_opts, $m) && $this->value($v)) {
204
+ $tag['op'] = $m[1];
205
+ $tag['op_value'] = $v;
206
+ }
207
+
208
+ if ($this->literal(']')) {
209
+ $out = $tag;
210
+ return true;
211
+ }
212
+ }
213
+ } elseif (isset($tag['front'])) {
214
+ $out = $tag;
215
+ return true;
216
+ }
217
+
218
+ $this->seek($s);
219
+ return false;
220
+ }
221
+
222
+ function ident(&$out) {
223
+ // [-]?{nmstart}{nmchar}*
224
+ // nmstart: [_a-z]|{nonascii}|{escape}
225
+ // nmchar: [_a-z0-9-]|{nonascii}|{escape}
226
+ if ($this->match('(-?[_a-z][_\w]*)', $m)) {
227
+ $out = $m[1];
228
+ return true;
229
+ }
230
+ return false;
231
+ }
232
+
233
+ function value(&$out) {
234
+ if ($this->string($str)) {
235
+ $out = $this->compileString($str);
236
+ return true;
237
+ } elseif ($this->ident($id)) {
238
+ $out = $id;
239
+ return true;
240
+ }
241
+ return false;
242
+ }
243
+
244
+
245
+ function combinator(&$op) {
246
+ if ($this->match(self::$combinators, $m)) {
247
+ $op = $m[1];
248
+ return true;
249
+ }
250
+ return false;
251
+ }
252
+ }
253
+
254
+ class nodecounter {
255
+ var $count = 0;
256
+ var $children = array();
257
+
258
+ var $name;
259
+ var $child_blocks;
260
+ var $the_block;
261
+
262
+ function __construct($name) {
263
+ $this->name = $name;
264
+ }
265
+
266
+ function dump($stack = null) {
267
+ if (is_null($stack)) $stack = array();
268
+ $stack[] = $this->getName();
269
+ echo implode(' -> ', $stack)." ($this->count)\n";
270
+ foreach ($this->children as $child) {
271
+ $child->dump($stack);
272
+ }
273
+ }
274
+
275
+ static function compileProperties($c, $block) {
276
+ foreach($block as $name => $value) {
277
+ if ($c->isProperty($name, $value)) {
278
+ echo $c->compileProperty($name, $value)."\n";
279
+ }
280
+ }
281
+ }
282
+
283
+ function compile($c, $path = null) {
284
+ if (is_null($path)) $path = array();
285
+ $path[] = $this->name;
286
+
287
+ $isVisible = !is_null($this->the_block) || !is_null($this->child_blocks);
288
+
289
+ if ($isVisible) {
290
+ echo $c->indent(implode(' ', $path).' {');
291
+ $c->indentLevel++;
292
+ $path = array();
293
+
294
+ if ($this->the_block) {
295
+ $this->compileProperties($c, $this->the_block);
296
+ }
297
+
298
+ if ($this->child_blocks) {
299
+ foreach ($this->child_blocks as $block) {
300
+ echo $c->indent(tagparse::compilePaths($block['__tags']).' {');
301
+ $c->indentLevel++;
302
+ $this->compileProperties($c, $block);
303
+ $c->indentLevel--;
304
+ echo $c->indent('}');
305
+ }
306
+ }
307
+ }
308
+
309
+ // compile child nodes
310
+ foreach($this->children as $node) {
311
+ $node->compile($c, $path);
312
+ }
313
+
314
+ if ($isVisible) {
315
+ $c->indentLevel--;
316
+ echo $c->indent('}');
317
+ }
318
+
319
+ }
320
+
321
+ function getName() {
322
+ if (is_null($this->name)) return "[root]";
323
+ else return $this->name;
324
+ }
325
+
326
+ function getNode($name) {
327
+ if (!isset($this->children[$name])) {
328
+ $this->children[$name] = new nodecounter($name);
329
+ }
330
+
331
+ return $this->children[$name];
332
+ }
333
+
334
+ function findNode($path) {
335
+ $current = $this;
336
+ for ($i = 0; $i < count($path); $i++) {
337
+ $t = tagparse::compileTag($path[$i]);
338
+ $current = $current->getNode($t);
339
+ }
340
+
341
+ return $current;
342
+ }
343
+
344
+ function addBlock($path, $block) {
345
+ $node = $this->findNode($path);
346
+ if (!is_null($node->the_block)) throw new exception("can this happen?");
347
+
348
+ unset($block['__tags']);
349
+ $node->the_block = $block;
350
+ }
351
+
352
+ function addToNode($path, $block) {
353
+ $node = $this->findNode($path);
354
+ $node->child_blocks[] = $block;
355
+ }
356
+ }
357
+
358
+ /**
359
+ * create a less file from a css file by combining blocks where appropriate
360
+ */
361
+ class lessify extends lessc {
362
+ public function dump() {
363
+ print_r($this->env);
364
+ }
365
+
366
+ public function parse($str = null) {
367
+ $this->prepareParser($str ? $str : $this->buffer);
368
+ while (false !== $this->parseChunk());
369
+
370
+ $root = new nodecounter(null);
371
+
372
+ // attempt to preserve some of the block order
373
+ $order = array();
374
+
375
+ $visitedTags = array();
376
+ foreach (end($this->env) as $name => $block) {
377
+ if (!$this->isBlock($name, $block)) continue;
378
+ if (isset($visitedTags[$name])) continue;
379
+
380
+ foreach ($block['__tags'] as $t) {
381
+ $visitedTags[$t] = true;
382
+ }
383
+
384
+ // skip those with more than 1
385
+ if (count($block['__tags']) == 1) {
386
+ $p = new tagparse(end($block['__tags']));
387
+ $path = $p->parse();
388
+ $root->addBlock($path, $block);
389
+ $order[] = array('compressed', $path, $block);
390
+ continue;
391
+ } else {
392
+ $common = null;
393
+ $paths = array();
394
+ foreach ($block['__tags'] as $rawtag) {
395
+ $p = new tagparse($rawtag);
396
+ $paths[] = $path = $p->parse();
397
+ if (is_null($common)) $common = $path;
398
+ else {
399
+ $new_common = array();
400
+ foreach ($path as $tag) {
401
+ $head = array_shift($common);
402
+ if ($tag == $head) {
403
+ $new_common[] = $head;
404
+ } else break;
405
+ }
406
+ $common = $new_common;
407
+ if (empty($common)) {
408
+ // nothing in common
409
+ break;
410
+ }
411
+ }
412
+ }
413
+
414
+ if (!empty($common)) {
415
+ $new_paths = array();
416
+ foreach ($paths as $p) $new_paths[] = array_slice($p, count($common));
417
+ $block['__tags'] = $new_paths;
418
+ $root->addToNode($common, $block);
419
+ $order[] = array('compressed', $common, $block);
420
+ continue;
421
+ }
422
+
423
+ }
424
+
425
+ $order[] = array('none', $block['__tags'], $block);
426
+ }
427
+
428
+
429
+ $compressed = $root->children;
430
+ foreach ($order as $item) {
431
+ list($type, $tags, $block) = $item;
432
+ if ($type == 'compressed') {
433
+ $top = tagparse::compileTag(reset($tags));
434
+ if (isset($compressed[$top])) {
435
+ $compressed[$top]->compile($this);
436
+ unset($compressed[$top]);
437
+ }
438
+ } else {
439
+ echo $this->indent(implode(', ', $tags).' {');
440
+ $this->indentLevel++;
441
+ nodecounter::compileProperties($this, $block);
442
+ $this->indentLevel--;
443
+ echo $this->indent('}');
444
+ }
445
+ }
446
+ }
447
+ }
lib/lessphp/package.sh ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+
3
+ # creates tar.gz for current version
4
+
5
+ VERSION=`./plessc -v | sed -n 's/^v\(.*\)$/\1/p'`
6
+ OUT_DIR="tmp/lessphp"
7
+ TMP=`dirname $OUT_DIR`
8
+
9
+ mkdir -p $OUT_DIR
10
+ tar -c `git ls-files` | tar -C $OUT_DIR -x
11
+
12
+ rm $OUT_DIR/.gitignore
13
+ rm $OUT_DIR/package.sh
14
+ rm $OUT_DIR/lessify
15
+ rm $OUT_DIR/lessify.inc.php
16
+
17
+ OUT_NAME="lessphp-$VERSION.tar.gz"
18
+ tar -czf $OUT_NAME -C $TMP lessphp/
19
+ echo "Wrote $OUT_NAME"
20
+
21
+ rm -r $TMP
22
+
lib/lessphp/plessc ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/php -q
2
+ <?php
3
+ //
4
+ // command line utility to compile less to stdout
5
+ // leaf corcoran <leafo.net>
6
+
7
+ error_reporting(E_ALL);
8
+ $path = realpath(dirname(__FILE__)).'/';
9
+
10
+ require $path."lessc.inc.php";
11
+
12
+ $VERSION = lessc::$VERSION;
13
+
14
+ $fa = "Fatal Error: ";
15
+ function err($msg) {
16
+ fwrite(STDERR, $msg."\n");
17
+ }
18
+
19
+ if (php_sapi_name() != "cli") {
20
+ err($fa.$argv[0]." must be run in the command line.");
21
+ exit(1);
22
+ }
23
+ $exe = array_shift($argv); // remove filename
24
+
25
+ function process($data, $import = null) {
26
+ global $fa;
27
+
28
+ $l = new lessc();
29
+ if ($import) $l->importDir = $import;
30
+ try {
31
+ echo $l->parse($data);
32
+ exit(0);
33
+ } catch (exception $ex) {
34
+ err($fa."\n".str_repeat('=', 20)."\n".
35
+ $ex->getMessage());
36
+ exit(1);
37
+ }
38
+ }
39
+
40
+ // process args
41
+ $opts = array();
42
+ foreach ($argv as $loc => $a) {
43
+ if (preg_match("/^-([a-zA-Z]+)$/", $a, $m)) {
44
+ $m = $m[1];
45
+ for ($i = 0; $i < strlen($m); $i++)
46
+ $opts[$m{$i}] = $loc;
47
+ unset($argv[$loc]);
48
+ }
49
+ }
50
+
51
+ function has($o, &$loc = null) {
52
+ global $opts;
53
+ if (!isset($opts[$o])) return false;
54
+ $loc = $opts[$o];
55
+ return true;
56
+ }
57
+
58
+ function hasValue($o, &$value = null) {
59
+ global $argv;
60
+ if (!has($o,$loc)) return false;
61
+ if (!isset($argv[$loc+1])) return false;
62
+ $value = $argv[$loc+1];
63
+ return true;
64
+ }
65
+
66
+ if (has("v")) {
67
+ exit($VERSION."\n");
68
+ }
69
+
70
+ if (has("r", $loc)) {
71
+ if (!hasValue("r", $data)) {
72
+ while (!feof(STDIN)) {
73
+ $data .= fread(STDIN, 8192);
74
+ }
75
+ }
76
+ return process($data);
77
+ }
78
+
79
+ if (has("w")) {
80
+ // need two files
81
+ if (!is_file($in = array_shift($argv)) ||
82
+ null == $out = array_shift($argv))
83
+ {
84
+ err($fa.$exe." -w infile outfile");
85
+ exit(1);
86
+ }
87
+
88
+ echo "Watching ".$in.
89
+ (has("n") ? ' with notifications' : '').
90
+ ", press Ctrl + c to exit.\n";
91
+
92
+ $cache = $in;
93
+ $last_action = 0;
94
+ while (1) {
95
+ clearstatcache();
96
+
97
+ // check if anything has changed since last fail
98
+ $updated = false;
99
+ if (is_array($cache)) {
100
+ foreach ($cache['files'] as $fname=>$_) {
101
+ if (filemtime($fname) > $last_action) {
102
+ $updated = true;
103
+ break;
104
+ }
105
+ }
106
+ } else $updated = true;
107
+
108
+ // try to compile it
109
+ if ($updated) {
110
+ $last_action = time();
111
+
112
+ try {
113
+ $cache = lessc::cexecute($cache);
114
+ echo "Writing updated file: ".$out."\n";
115
+ if (!file_put_contents($out, $cache['compiled'])) {
116
+ err($fa."Could not write to file ".$out);
117
+ exit(1);
118
+ }
119
+ } catch (exception $ex) {
120
+ echo "\nFatal Error:\n".str_repeat('=', 20)."\n".$ex->getMessage()."\n\n";
121
+
122
+ if (has("n")) {
123
+ `notify-send -u critical "compile failed" "{$ex->getMessage()}"`;
124
+ }
125
+ }
126
+ }
127
+
128
+ sleep(1);
129
+ }
130
+ exit(0);
131
+ }
132
+
133
+ if (!$fname = array_shift($argv)) {
134
+ echo "Usage: ".$exe." input-file [output-file]\n";
135
+ exit(1);
136
+ }
137
+
138
+ function dumpValue($node, $depth = 0) {
139
+ if (is_object($node)) {
140
+ $indent = str_repeat(" ", $depth);
141
+ $out = array();
142
+ foreach ($node->props as $prop) {
143
+ $out[] = $indent . dumpValue($prop, $depth + 1);
144
+ }
145
+ $out = implode("\n", $out);
146
+ if (!empty($node->tags)) {
147
+ $out = "+ ".implode(", ", $node->tags)."\n".$out;
148
+ }
149
+ return $out;
150
+ } elseif (is_array($node)) {
151
+ if (empty($node)) return "[]";
152
+ $type = $node[0];
153
+ if ($type == "block")
154
+ return dumpValue($node[1], $depth);
155
+
156
+ $out = array();
157
+ foreach ($node as $value) {
158
+ $out[] = dumpValue($value, $depth);
159
+ }
160
+ return "{ ".implode(", ", $out)." }";
161
+ } else {
162
+ if (is_string($node) && preg_match("/[\s,]/", $node)) {
163
+ return '"'.$node.'"';
164
+ }
165
+ return $node; // normal value
166
+ }
167
+ }
168
+
169
+ try {
170
+ $l = new lessc($fname);
171
+ if (has("T") || has("X")) {
172
+ $t = $l->parseTree();
173
+ if (has("X"))
174
+ $out = print_r($t, 1);
175
+ else
176
+ $out = dumpValue($t)."\n";
177
+ } else {
178
+ $out = $l->parse();
179
+ }
180
+
181
+ if (!$fout = array_shift($argv)) {
182
+ echo $out;
183
+ } else {
184
+ file_put_contents($fout, $out);
185
+ }
186
+
187
+ } catch (exception $ex) {
188
+ err($fa.$ex->getMessage());
189
+ exit(1);
190
+ }
191
+
192
+
193
+ ?>
lib/lessphp/tests/README.md ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## test.php
2
+
3
+ To run:
4
+
5
+ php test.php [flags] [test-name-glob]
6
+
7
+
8
+ Runs through all files in `inputs`, compiles them, then compares to respective
9
+ file in `outputs`. If there are any differences then the test will fail.
10
+
11
+ Add the `-d` flag to show the differences of failed tests. Defaults to showing
12
+ differences with `diff` but you can set the tool by doing `-d=toolname`.
13
+
14
+ Pass the `-C` flag to save the output of the inputs to the appropriate file. This
15
+ will overwrite any existing outputs. Use this when you want to save verified
16
+ test results. Combine with a *test-name-glob* to selectively compile.
17
+
18
+ You can also run specific tests by passing in an argument that contains any
19
+ part of the test name.
20
+
21
+ ## bootstrap.sh
22
+
23
+ It's a syntetic test comparing lessc and lessphp output compiling twitter bootstrap;
24
+ see bootstrap.sh for details.
lib/lessphp/tests/bootstrap.sh ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ echo "This script clones twitter bootsrap, compiles it with lessc and lessphp,"
2
+ echo "cleans up results with csstidy, and outputs diff. To run it, you need to"
3
+ echo "have git, csstidy and lessc installed."
4
+ echo ""
5
+
6
+ csstidy_params="--allow_html_in_templates=false --compress_colors=false
7
+ --compress_font-weight=false --discard_invalid_properties=false
8
+ --lowercase_s=false --preserve_css=true --remove_bslash=false
9
+ --remove_last_;=false --sort-properties=true --sort-selectors=true
10
+ --timestamp=false --silent=true --merge_selectors=0 --case-properties=0
11
+ --optimize-shorthands=0 --template=high"
12
+
13
+ if [ -z "$@" ]; then
14
+ diff_tool="diff -b -u -t -B"
15
+ else
16
+ diff_tool=$@
17
+ fi
18
+
19
+ mkdir -p tmp
20
+
21
+ if [ ! -d 'bootstrap/' ]; then
22
+ echo ">> Cloning bootstrap to bootstrap/"
23
+ git clone https://github.com/twitter/bootstrap
24
+ fi
25
+
26
+ echo ">> Lessc compilation"
27
+ lessc bootstrap/less/bootstrap.less tmp/bootstrap.lessc.css
28
+
29
+ echo ">> Lessphp compilation"
30
+ ../plessc bootstrap/less/bootstrap.less tmp/bootstrap.lessphp.css
31
+ echo ">> Cleanup and convert"
32
+
33
+ # csstidy tmp/bootstrap.lessc.css $csstidy_params tmp/bootstrap.lessc.clean.css
34
+ # csstidy tmp/bootstrap.lessphp.css $csstidy_params tmp/bootstrap.lessphp.clean.css
35
+ #
36
+ # # put a newline after { and :
37
+ # function split() {
38
+ # sed 's/\(;\|{\)/\1\n/g'
39
+ # }
40
+ #
41
+ # # csstidy is messed up and wont output to stdout when there are a bunch of options
42
+ # cat tmp/bootstrap.lessc.clean.css | split | tee tmp/bootstrap.lessc.clean.css
43
+ # cat tmp/bootstrap.lessphp.clean.css | split | tee tmp/bootstrap.lessphp.clean.css
44
+
45
+ php sort.php tmp/bootstrap.lessc.css > tmp/bootstrap.lessc.clean.css
46
+ php sort.php tmp/bootstrap.lessphp.css > tmp/bootstrap.lessphp.clean.css
47
+
48
+ echo ">> Doing diff"
49
+ $diff_tool tmp/bootstrap.lessc.clean.css tmp/bootstrap.lessphp.clean.css
lib/lessphp/tests/inputs/accessors.less.disable ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* accessors */
2
+
3
+ #defaults {
4
+ @width: 960px;
5
+ @color: black;
6
+ .something {
7
+ @space: 10px;
8
+ @hello {
9
+ color: green;
10
+ }
11
+ }
12
+ }
13
+
14
+ .article { color: #294366; }
15
+
16
+ .comment {
17
+ width: #defaults[@width];
18
+ color: .article['color'];
19
+ padding: #defaults > .something[@space];
20
+ }
21
+
22
+ .wow {
23
+ height: .comment['width'];
24
+ background-color: .comment['color'];
25
+ color: #defaults > .something > @hello['color'];
26
+
27
+ padding: #defaults > non-existant['padding'];
28
+ margin: #defaults > .something['non-existant'];
29
+ }
30
+
31
+ .mix {
32
+ #defaults;
33
+ font-size: .something[@space];
34
+ }
35
+
36
+
lib/lessphp/tests/inputs/arity.less ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ // simple arity
3
+
4
+ .hello(@a) {
5
+ color: one;
6
+ }
7
+
8
+ .hello(@a, @b) {
9
+ color: two;
10
+ }
11
+
12
+ .hello(@a, @b, @c) {
13
+ color: three;
14
+ }
15
+
16
+
17
+ .world(@a, @b, @c) {
18
+ color: three;
19
+ }
20
+
21
+ .world(@a, @b) {
22
+ color: two;
23
+ }
24
+
25
+ .world(@a) {
26
+ color: one;
27
+ }
28
+
29
+ .one {
30
+ .hello(1);
31
+ .world(1);
32
+ }
33
+
34
+ .two {
35
+ .hello(1, 1);
36
+ .world(1, 1);
37
+ }
38
+
39
+ .three {
40
+ .hello(1, 1, 1);
41
+ .world(1, 1, 1);
42
+ }
43
+
44
+
45
+ // arity with default values
46
+
47
+ .foo(@a, @b: cool) {
48
+ color: two;
49
+ }
50
+
51
+ .foo(@a, @b: cool, @c: yeah) {
52
+ color: three;
53
+ }
54
+
55
+
56
+ .baz(@a, @b, @c: yeah) {
57
+ color: three;
58
+ }
59
+
60
+ .baz(@a, @b: cool) {
61
+ color: two;
62
+ }
63
+
64
+
65
+ .multi-foo {
66
+ .foo(1);
67
+ .foo(1, 1);
68
+ .foo(1,1,1);
69
+ }
70
+
71
+ .multi-baz {
72
+ .baz(1);
73
+ .baz(1, 1);
74
+ .baz(1,1,1);
75
+ }
76
+
77
+
lib/lessphp/tests/inputs/attributes.less ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * { color: blue; }
2
+ E { color: blue; }
3
+ E[foo] { color: blue; }
4
+ [foo] { color: blue; }
5
+ [foo] .helloWorld { color: blue; }
6
+ [foo].helloWorld { color: blue; }
7
+ E[foo="barbar"] { color: blue; }
8
+ E[foo~="hello#$@%@$#^"] { color: blue; }
9
+ E[foo^="color: green;"] { color: blue; }
10
+ E[foo$="239023"] { color: blue; }
11
+ E[foo*="29302"] { color: blue; }
12
+ E[foo|="239032"] { color: blue; }
13
+ E:root { color: blue; }
14
+
15
+ E:nth-child(odd) { color: blue; }
16
+ E:nth-child(2n+1) { color: blue; }
17
+ E:nth-child(5) { color: blue; }
18
+ E:nth-last-child(-n+2) { color: blue; }
19
+ E:nth-of-type(2n) { color: blue; }
20
+ E:nth-last-of-type(n) { color: blue; }
21
+
22
+ E:first-child { color: blue; }
23
+ E:last-child { color: blue; }
24
+ E:first-of-type { color: blue; }
25
+ E:last-of-type { color: blue; }
26
+ E:only-child { color: blue; }
27
+ E:only-of-type { color: blue; }
28
+ E:empty { color: blue; }
29
+
30
+ E:lang(en) { color: blue; }
31
+ E::first-line { color: blue; }
32
+ E::before { color: blue; }
33
+
34
+ E#id { color: blue; }
35
+ E:not(:link) { color: blue; }
36
+
37
+ E F { color: blue; }
38
+ E > F { color: blue; }
39
+ E + F { color: blue; }
40
+ E ~ F { color: blue; }
41
+
lib/lessphp/tests/inputs/builtins.less ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // builtin
2
+
3
+ @something: "hello world";
4
+ @color: #112233;
5
+ @color2: rgba(44,55,66, .6);
6
+
7
+ body {
8
+ color: @something;
9
+
10
+ @num: 7 / 6;
11
+ height: @num + 4;
12
+ height: floor(@num) + 4px;
13
+
14
+ @num2: 2 / 3;
15
+ width: @num2;
16
+ width: round(@num2);
17
+ width: floor(@num2);
18
+ width: round(10px / 3);
19
+
20
+ color: rgbahex(@color);
21
+ color: rgbahex(@color2);
22
+ }
23
+
24
+
25
+ format {
26
+ @r: 32;
27
+ format: %("rgb(%d, %d, %d)", @r, 128, 64);
28
+ format-string: %("hello %s", "world");
29
+ format-multiple: %("hello %s %d", "earth", 2);
30
+ format-url-encode: %('red is %A', #ff0000);
31
+ eformat: e(%("rgb(%d, %d, %d)", @r, 128, 64));
32
+ }
33
+
34
+
lib/lessphp/tests/inputs/colors.less ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ body {
3
+ color: hsl(34, 50%, 40%);
4
+ color: hsla(34, 50%, 40%, 0.3);
5
+
6
+ lighten: lighten(#efefef, 10%);
7
+ lighten: lighten(rgb(23, 53, 231), 22%);
8
+ lighten: lighten(rgba(212, 103, 88, 0.5), 10%);
9
+
10
+ darken: darken(#efefef, 10%);
11
+ darken: darken(rgb(23, 53, 231), 22%);
12
+ darken: darken(rgba(23, 53, 231, 0.5), 10%);
13
+
14
+ saturate: saturate(#efefef, 10%);
15
+ saturate: saturate(rgb(23, 53, 231), 22%);
16
+ saturate: saturate(rgba(23, 53, 231, 0.5), 10%);
17
+
18
+ desaturate: desaturate(#efefef, 10%);
19
+ desaturate: desaturate(rgb(23, 53, 231), 22%);
20
+ desaturate: desaturate(rgba(23, 53, 231, 0.5), 10%);
21
+
22
+ spin: spin(#efefef, 12);
23
+ spin: spin(rgb(23, 53, 231), 15);
24
+ spin: spin(rgba(23, 53, 231, 0.5), 19);
25
+
26
+ spin: spin(#efefef, -12);
27
+ spin: spin(rgb(23, 53, 231), -15);
28
+ spin: spin(rgba(23, 53, 231, 0.5), -19);
29
+
30
+ one: fadein(#abcdef, 10%);
31
+ one: fadeout(#abcdef, -10%);
32
+
33
+ two: fadeout(#029f23, 10%);
34
+ two: fadein(#029f23, -10%);
35
+
36
+
37
+ three: fadein(rgba(1,2,3, 0.5), 10%);
38
+ three: fadeout(rgba(1,2,3, 0.5), -10%);
39
+
40
+ four: fadeout(rgba(1,2,3, 0), 10%);
41
+ four: fadein(rgba(1,2,3, 0), -10%);
42
+
43
+ hue: hue(rgb(34,20,40));
44
+ sat: saturation(rgb(34,20,40));
45
+ lit: lightness(rgb(34,20,40));
46
+
47
+ @old: #34fa03;
48
+ @new: hsl(hue(@old), 45%, 90%);
49
+ what: @new;
50
+
51
+ zero: saturate(#123456, -100%);
52
+ zero: saturate(#123456, 100%);
53
+ zero: saturate(#000000, 100%);
54
+ zero: saturate(#ffffff, 100%);
55
+
56
+ zero: lighten(#123456, -100%);
57
+ zero: lighten(#123456, 100%);
58
+ zero: lighten(#000000, 100%);
59
+ zero: lighten(#ffffff, 100%);
60
+
61
+ zero: spin(#123456, -100);
62
+ zero: spin(#123456, 100);
63
+ zero: spin(#000000, 100);
64
+ zero: spin(#ffffff, 100);
65
+ }
66
+
67
+
68
+ alpha {
69
+ // g: alpha(red);
70
+ g: alpha(rgba(0,0,0,0));
71
+ g: alpha(rgb(155,55,0));
72
+ }
73
+
74
+ fade {
75
+ f: fade(red, 50%);
76
+ f: fade(#fff, 20%);
77
+ f: fade(rgba(34,23,64,0.4), 50%);
78
+ }
79
+
80
+ @a: rgb(255,255,255);
81
+ @b: rgb(0,0,0);
82
+
83
+ .mix {
84
+ color: mix(@a, @b, 50%);
85
+ }
86
+
87
+ .percent {
88
+ per: percentage(0.5);
89
+ }
90
+
91
+ // color keywords
92
+
93
+ .colorz {
94
+ color: whitesmoke - 10;
95
+ color: spin(red, 34);
96
+ }
97
+
98
+
99
+
100
+ // purposfuly whacky to match less.js
101
+
102
+ @color: #fcf8e3;
103
+
104
+ body {
105
+ start: @color;
106
+ spin: spin(@color, -10); // #fcf4e3
107
+ chained: darken(spin(@color, -10), 3%); // gives #fbeed5, should be #fbefd5
108
+ direct: darken(#fcf4e3, 3%); // #fbefd5
109
+ }
110
+
111
+ // spin around
112
+ pre {
113
+ @errorBackground: #f2dede;
114
+ spin: spin(@errorBackground, -10);
115
+ }
116
+
117
+ dd {
118
+ @white: #fff;
119
+ background-color: mix(@white, darken(@white, 10%), 60%);
120
+ }
121
+
122
+
lib/lessphp/tests/inputs/compile_on_mixin.less ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ @mixin {
3
+ height: 22px;
4
+ ul {
5
+ height: 20px;
6
+ li {
7
+ @color: red;
8
+ height: 10px;
9
+ div span, link {
10
+ margin: 10px;
11
+ color: @color;
12
+ }
13
+ }
14
+
15
+ div, p {
16
+ border: 1px;
17
+ &.hello {
18
+ color: green;
19
+ }
20
+
21
+ :what {
22
+ color: blue;
23
+ }
24
+ }
25
+
26
+
27
+ a {
28
+ b {
29
+ color: blue;
30
+ }
31
+ }
32
+ }
33
+ }
34
+
35
+
36
+
37
+ body {
38
+ @mixin;
39
+ }
lib/lessphp/tests/inputs/escape.less ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ body {
3
+ @hello: "world";
4
+ border: e("this is simple");
5
+ border: e(this is simple); // bug in lessjs
6
+ border: e("this is simple", "cool lad");
7
+ border: e(1232);
8
+ border: e(@hello);
9
+ border: e("one" + 'more'); // no string addition lessjs
10
+ border: e(); // syntax error lessjs
11
+
12
+ line-height: ~"eating rice";
13
+ line-height: ~"string cheese";
14
+ line-height: a b c ~"string me" d e f;
15
+ }
16
+
17
+ .class {
18
+ filter: ~"progid:DXImageTransform.Microsoft.AlphaImageLoader(src='image.png')";
19
+ }
lib/lessphp/tests/inputs/font_family.less ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ @font-directory: 'fonts/';
3
+ @some-family: Gentium;
4
+
5
+ @font-face: maroon; // won't collide with @font-face { }
6
+
7
+ @font-face {
8
+ font-family: Graublau Sans Web;
9
+ src: url(@{font-directory}GraublauWeb.otf) format("opentype");
10
+ }
11
+
12
+ @font-face {
13
+ font-family: @some-family;
14
+ src: url('@{font-directory}Gentium.ttf');
15
+ }
16
+
17
+ @font-face {
18
+ font-family: @some-family;
19
+ src: url("@{font-directory}GentiumItalic.ttf");
20
+ font-style: italic;
21
+ }
22
+
23
+ h2 {
24
+ font-family: @some-family;
25
+ crazy: @font-face;
26
+ }
27
+
28
+
lib/lessphp/tests/inputs/guards.less ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ .simple(@hi) when (@hi) {
3
+ color: yellow;
4
+ }
5
+
6
+
7
+ .something(@hi) when (@hi = cool) {
8
+ color: red;
9
+ }
10
+
11
+ .another(@x) when (@x > 10) {
12
+ color: green;
13
+ }
14
+
15
+
16
+ .flipped(@x) when (@x =< 10) {
17
+ color: teal;
18
+ }
19
+
20
+ .yeah(@arg) when (isnumber(@arg)) {
21
+ color: purple;
22
+ }
23
+
24
+
25
+ .yeah(@arg) when (ispixel(@arg)) {
26
+ color: silver;
27
+ }
28
+
29
+
30
+ .hello(@arg) when not (@arg) {
31
+ color: orange;
32
+ }
33
+
34
+ dd {
35
+ .simple(true);
36
+ .simple(2344px);
37
+ }
38
+
39
+ b {
40
+ .something(cool);
41
+ .something(birthday);
42
+ }
43
+
44
+ img {
45
+ .another(12);
46
+ .another(2);
47
+
48
+ .flipped(12);
49
+ .flipped(2);
50
+ }
51
+
52
+ body {
53
+ .yeah("world");
54
+ .yeah(232px);
55
+ .yeah(232);
56
+
57
+ .hello(true);
58
+ }
59
+
60
+ .something(@x) when (@x) and (@y), not (@x = what) {
61
+ color: blue;
62
+ }
63
+
64
+ div {
65
+ @y: true;
66
+ .something(true);
67
+
68
+ }
69
+
70
+ pre {
71
+ .something(what);
72
+ }
73
+
74
+
75
+ .coloras(@g) when (iscolor(@g)) {
76
+ color: true @g;
77
+ }
78
+
79
+ link {
80
+ .coloras(red);
81
+ .coloras(10px);
82
+ .coloras(ffjref);
83
+ .coloras(#fff);
84
+ .coloras(#fffddd);
85
+ .coloras(rgb(0,0,0));
86
+ .coloras(rgba(0,0,0, .34));
87
+ }
88
+
lib/lessphp/tests/inputs/hacks.less ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ // css hacks
2
+
3
+ :root .alert-message, :root .btn {
4
+ border-radius: 0 \0;
5
+ }
6
+
lib/lessphp/tests/inputs/import.less ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ @import 'file1.less'; // file found and imported
3
+
4
+ @import "something.css" media;
5
+ @import url("something.css") media;
6
+ @import url(something.css) media, screen, print;
7
+
8
+ @color: maroon;
9
+
10
+ @import url(file2); // found and imported
11
+
12
+ body {
13
+ line-height: 10em;
14
+ @colors;
15
+ }
16
+
17
+ div {
18
+ @color: fuchsia;
19
+ @import "file2";
20
+ }
21
+
lib/lessphp/tests/inputs/keyframes.less ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @keyframes 'bounce' {
2
+ from {
3
+ top: 100px;
4
+ animation-timing-function: ease-out;
5
+ }
6
+
7
+ 25% {
8
+ top: 50px;
9
+ animation-timing-function: ease-in;
10
+ }
11
+
12
+ 50% {
13
+ top: 100px;
14
+ animation-timing-function: ease-out;
15
+ }
16
+
17
+ 75% {
18
+ top: 75px;
19
+ animation-timing-function: ease-in;
20
+ }
21
+
22
+ to {
23
+ top: 100px;
24
+ }
25
+ }
26
+
27
+
28
+
29
+ div {
30
+ animation-name: 'diagonal-slide';
31
+ animation-duration: 5s;
32
+ animation-iteration-count: 10;
33
+ }
34
+
35
+ @keyframes 'diagonal-slide' {
36
+
37
+ from {
38
+ left: 0;
39
+ top: 0;
40
+ }
41
+
42
+ to {
43
+ left: 100px;
44
+ top: 100px;
45
+ }
46
+
47
+ }
48
+
lib/lessphp/tests/inputs/math.less ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ .unary {
3
+ // all operators are parsable as unary operators, anything
4
+ // but - throws an error right now though,
5
+
6
+ // this gives two numbers
7
+ sub: 10 -5;
8
+ // add: 10 +5; // error
9
+ // div: 10 /5; // error
10
+ // mul: 10 *5; // error
11
+ }
12
+
13
+ .spaces {
14
+ // we can make the parser do math by leaving out the
15
+ // space after the first value, or putting spaces on both sides
16
+
17
+ sub: 10-5;
18
+ sub: 10 - 5;
19
+
20
+ add: 10+5;
21
+ add: 10 + 5;
22
+
23
+ // div: 10/5; // this wont work, read on
24
+ div: 10 / 5;
25
+
26
+ mul: 10*5;
27
+ mul: 10 * 5;
28
+ }
29
+
30
+ // these properties have divison not in parenthases
31
+ .supress-division {
32
+ border-radius: 10px / 10px;
33
+ border-radius: 10px/10px;
34
+ border-radius: hello (10px/10px) world;
35
+ @x: 10px;
36
+ font: @x/30 sans-serif;
37
+ font: 10px / 20px sans-serif;
38
+ font: 10px/20px sans-serif;
39
+ border-radius:0 15px 15px 15px / 0 50% 50% 50%;
40
+ }
41
+
42
+
43
+ .parens {
44
+ // if you are unsure, then just wrap the expression in parentheses and it will
45
+ // always evaluate.
46
+
47
+ // notice we no longer have unary operators, and these will evaluate
48
+ sub: (10 -5);
49
+ add: (10 +5);
50
+ div: (10 /5);
51
+ div: (10/5); // no longer interpreted as the shorthand
52
+ mul: (10 *5);
53
+ }
54
+
55
+ .keyword-names {
56
+ // watch out when doing math with keywords, - is a valid keyword character
57
+ @a: 100;
58
+ @b: 25;
59
+ @a-: "hello";
60
+ height: @a-@b; // here we get "hello" 25, not 75
61
+ }
62
+
63
+
64
+ .negation {
65
+ hello: -(1px);
66
+ hello: 0-(1px);
67
+
68
+ @something: 10;
69
+ hello: -@something;
70
+ }
71
+
72
+
73
+ // and now here are the tests
74
+
75
+ .test {
76
+ single: (5);
77
+ single: 5+(5);
78
+ single: (5)+((5));
79
+
80
+ parens: (5 +(5)) -2;
81
+ // parens: ((5 +(5)) -2); // FAILS - fixme
82
+
83
+ math: (5 + 5)*(2 / 1);
84
+ math: (5+5)*(2/1);
85
+
86
+ width: 2 * (4 * (2 + (1 + 6))) - 1;
87
+ height: ((2+3)*(2+3) / (9-4)) + 1;
88
+ padding: (2px + 4px) 1em 2px 2;
89
+
90
+ @var: (2 * 2);
91
+ padding: (2 * @var) 4 4 (@var * 1px);
92
+ width: (@var * @var) * 6;
93
+ height: (7 * 7) + (8 * 8);
94
+ margin: 4 * (5 + 5) / 2 - (@var * 2);
95
+ }
96
+
97
+ .percents {
98
+ color: 100 * 10%;
99
+ color: 10% * 100;
100
+ color: 10% * 10%;
101
+
102
+ color: 100px * 10%; // lessjs makes this px
103
+ color: 10% * 100px; // lessjs makes this %
104
+
105
+ color: 20% + 10%;
106
+ color: 20% - 10%;
107
+
108
+ color: 20% / 10%;
109
+ }
110
+
111
+ .misc {
112
+ x: 10px * 4em;
113
+ y: 10 * 4em;
114
+
115
+ }
116
+
lib/lessphp/tests/inputs/media.less ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @media screen, 3D {
2
+ P { color: green; }
3
+ }
4
+ @media print {
5
+ body { font-size: 10pt }
6
+ }
7
+ @media screen {
8
+ body { font-size: 13px }
9
+ }
10
+ @media screen, print {
11
+ body { line-height: 1.2 }
12
+ }
13
+
14
+ @media all and (min-width: 0px) {
15
+ body { line-height: 1.2 }
16
+ }
17
+
18
+ @media all and (min-width: 0) {
19
+ body { line-height: 1.2 }
20
+ }
21
+
22
+ @media
23
+ screen and (min-width: 102.5em) and (max-width: 117.9375em),
24
+ screen and (min-width: 150em) {
25
+ body { color: blue }
26
+ }
27
+
28
+
29
+ @media screen and (min-height: 100px + 10px) {
30
+ body { color: red; }
31
+ }
32
+
33
+ @cool: 100px;
34
+
35
+ @media screen and (height: @cool) and (width: @cool + 10), (size: @cool + 20) {
36
+ body { color: red; }
37
+ }
38
+
lib/lessphp/tests/inputs/misc.less ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ @hello: "utf-8";
3
+ @charset @hello;
4
+
5
+ @color: #fff;
6
+ @base_path: "/assets/images/";
7
+ @images: @base_path + "test/";
8
+ .topbar { background: url(@{images}topbar.png); }
9
+ .hello { test: empty-function(@images, 40%, to(@color)); }
10
+
11
+ .css3 {
12
+ background-image: -webkit-gradient(linear, 0% 0%, 0% 90%,
13
+ from(#E9A000), to(#A37000));
14
+ }
15
+
16
+
17
+ /**
18
+
19
+ Here is a block comment
20
+
21
+ **/
22
+
23
+
24
+ // this is a comment
25
+
26
+ .test, /*hello*/.world {
27
+ border: 1px solid red; // world
28
+ /* another property */
29
+ color: url(http://mage-page.com);
30
+ string: "hello /* this is not a comment */";
31
+ world: "// neither is this";
32
+ string: 'hello /* this is not a comment */' /*what if this is a comment */;
33
+ world: '// neither is this' // hell world;
34
+ ;
35
+ what-/*something?*/ever: 100px;
36
+ background: url(/*no comment here*/);
37
+ }
38
+
39
+
40
+ .urls {
41
+ @var: "http://google.com";
42
+ background: url(@var);
43
+ background: url(@{var});
44
+ background: url("@{var}");
45
+ }
46
+
47
+ .mix(@arg) { color: @arg; }
48
+ @aaa: aaa;
49
+ @bbb: bbb;
50
+ // make sure the opening selector isn't too greedy
51
+ .cool {.mix("@{aaa}, @{bbb}")}
52
+
53
+ .cool("{hello");
54
+ .cool('{hello');
55
+
56
+
57
+ // merging of mixins
58
+ .span-17 { float: left; }
59
+ .span-17 { width: 660px; }
60
+
61
+ .x {.span-17;}
62
+
63
+ .hi {
64
+ pre {
65
+ color: red;
66
+ }
67
+ }
68
+
69
+ .hi {
70
+ pre {
71
+ color: blue;
72
+ }
73
+ }
74
+
75
+ .rad {
76
+ .hi;
77
+ }
78
+
lib/lessphp/tests/inputs/mixin_functions.less ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ @outer: 10px;
3
+ @class(@var:22px, @car: 400px + @outer) {
4
+ margin: @var;
5
+ height: @car;
6
+ }
7
+
8
+ @group {
9
+ @f(@color) {
10
+ color: @color;
11
+ }
12
+ .cool {
13
+ border-bottom: 1px solid green;
14
+ }
15
+ }
16
+
17
+ .class(@width:200px) {
18
+ padding: @width;
19
+ }
20
+
21
+ body {
22
+ .class(2.0em);
23
+ @group > @f(red);
24
+ @class(10px, 10px + 2);
25
+ @group > .cool;
26
+ }
27
+
28
+
29
+ @lots(@a: 10px, @b: 20px, @c: 30px, @d: 40px, @e: 4px, @f:3px, @g:2px, @h: 1px) {
30
+ padding: @a @b @c @d;
31
+ margin: @e @f @g @h;
32
+ }
33
+
34
+ .skip_args {
35
+ @class(,12px);
36
+ @lots(,,,88px,,12px);
37
+ @group > @f(red,,,,);
38
+ @group > @f(red);
39
+ }
40
+
lib/lessphp/tests/inputs/mixin_merging.less.disable ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ @tester {
3
+ p, div { height: 10px; }
4
+ }
5
+
6
+ #test1 {
7
+ div { color: red; }
8
+ @tester;
9
+ }
10
+
11
+
12
+ @cool {
13
+ a,b,i { width: 1px; }
14
+ }
15
+
16
+ #test2 {
17
+ b { color: red; }
18
+ @cool;
19
+ }
20
+
21
+ #test3 {
22
+ @cool;
23
+ b { color: red; }
24
+ }
25
+
26
+ @cooler {
27
+ a { margin: 1px; }
28
+ }
29
+
30
+ #test4 {
31
+ a, div, html { color: blue; }
32
+ @cooler;
33
+ }
34
+
35
+ @hi {
36
+ img, strong { float: right; }
37
+ }
38
+
39
+ #test5 {
40
+ img, strong { padding: 2px; }
41
+ @hi;
42
+ }
43
+
44
+ @nested {
45
+ div, span {
46
+ a {
47
+ color: red;
48
+ }
49
+ }
50
+ }
51
+
52
+ #test6 {
53
+ div, span {
54
+ a {
55
+ line-height: 10px;
56
+ }
57
+ }
58
+ @nested;
59
+ }
60
+
61
+ @broken-nesting {
62
+ div, span {
63
+ strong, b {
64
+ color: red;
65
+ }
66
+ }
67
+
68
+ }
69
+
70
+ #test7 {
71
+ div {
72
+ strong {
73
+ margin: 1px;
74
+ }
75
+ }
76
+ @broken-nesting;
77
+ }
78
+
79
+
80
+ @another-nest {
81
+ a,b {
82
+ i {
83
+ color: red;
84
+ }
85
+
86
+ s {
87
+ color: blue;
88
+ }
89
+ }
90
+ }
91
+
92
+ #test8 {
93
+ a, b {
94
+ i,s {
95
+ background: red;
96
+ }
97
+ }
98
+ @another-nest;
99
+ }
100
+
lib/lessphp/tests/inputs/mixins.less ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ @rounded-corners {
3
+ border-radius: 10px;
4
+ }
5
+
6
+ .bold {
7
+ @font-size: 20px;
8
+ font-size: @font-size;
9
+ font-weight: bold;
10
+ }
11
+
12
+ body #window {
13
+ @rounded-corners;
14
+ .bold;
15
+ line-height: @font-size * 1.5;
16
+ }
17
+
18
+ #bundle {
19
+ .button {
20
+ display: block;
21
+ border: 1px solid black;
22
+ background-color: grey;
23
+ &:hover { background-color: white }
24
+ }
25
+ }
26
+ #header a {
27
+ color: orange;
28
+ #bundle > .button; // mixin the button class
29
+ }
30
+
31
+ div {
32
+ @abstract {
33
+ hello: world;
34
+ b {
35
+ color: blue;
36
+ }
37
+ }
38
+
39
+ @abstract > b;
40
+ @abstract;
41
+ }
42
+
43
+ @poop {
44
+ big: baby;
45
+ }
46
+
47
+ body {
48
+ div;
49
+ }
50
+
51
+ // not using > to list mixins
52
+
53
+ .hello {
54
+ .world {
55
+ color: blue;
56
+ }
57
+ }
58
+
59
+ .foobar {
60
+ .hello .world;
61
+ }
62
+
63
+
64
+ .butter {
65
+ .this .one .isnt .found;
66
+ }
67
+
68
+
69
+ // arguments
70
+
71
+ .spam(@something: 100, @dad: land) {
72
+ @wow: 23434;
73
+ foo: @arguments;
74
+ bar: @arguments;
75
+ }
76
+
77
+ .eggs {
78
+ .spam(1px, 2px);
79
+ .spam();
80
+ }
81
+
82
+ .first(@one, @two, @three, @four: cool) {
83
+ cool: @arguments;
84
+ }
85
+
86
+ #hello {
87
+ .first(one, two, three);
88
+ }
89
+
90
+ .rad(@name) {
91
+ cool: @arguments;
92
+ }
93
+
94
+ #world {
95
+ @hello: "world";
96
+ .rad("@{hello}");
97
+ }
98
+
99
+ .second(@x, @y:skip, @z: me) {
100
+ things: @arguments;
101
+ }
102
+
103
+ #another {
104
+ .second(red, blue, green);
105
+ .second(red blue green);
106
+ }
107
+
108
+
109
+ .another(@x, @y:skip, @z:me) {
110
+ .cool {
111
+ color: @arguments;
112
+ }
113
+ }
114
+
115
+ #day {
116
+ .another(one,two, three);
117
+ .another(one two three);
118
+ }
119
+
120
+
121
+
122
+
lib/lessphp/tests/inputs/nested.less ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #header {
2
+ color: black;
3
+
4
+ .navigation {
5
+ font-size: 12px;
6
+ .border {
7
+ .outside {
8
+ color: blue;
9
+ }
10
+ }
11
+ }
12
+ .logo {
13
+ width: 300px;
14
+ &:hover { text-decoration: none }
15
+ }
16
+ }
17
+
18
+ a { b { ul { li { color: green; } } } }
19
+
20
+ this { will { not { show { } } } }
21
+
22
+ .cool {
23
+ div & { color: green; }
24
+ p & span { color: yellow; }
25
+ }
26
+
27
+ another {
28
+ .cool;
29
+ }
30
+
31
+ b {
32
+ & .something {
33
+ color: blue;
34
+ }
35
+
36
+ &.something {
37
+ color: blue;
38
+ }
39
+ }
40
+
41
+ .foo {
42
+ .bar, .baz {
43
+ & .qux {
44
+ display: block;
45
+ }
46
+ .qux & {
47
+ display: inline;
48
+ }
49
+ .qux & .biz {
50
+ display: none;
51
+ }
52
+ }
53
+ }
54
+
55
+ b {
56
+ hello [x="&yeah"] {
57
+ color: red;
58
+ }
59
+ }
60
+
lib/lessphp/tests/inputs/pattern_matching.less ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ .mixin (light, @color) {
3
+ color: lighten(@color, 10%);
4
+ }
5
+ .mixin (@_, @color) {
6
+ display: block;
7
+ }
8
+
9
+ @switch: light;
10
+
11
+ .class {
12
+ .mixin(@switch, #888);
13
+ }
14
+
15
+ // by arity
16
+
17
+ .mixin () {
18
+ zero: 0;
19
+ }
20
+ .mixin (@a: 1px) {
21
+ one: 1;
22
+ }
23
+ .mixin (@a) {
24
+ one-req: 1;
25
+ }
26
+ .mixin (@a: 1px, @b: 2px) {
27
+ two: 2;
28
+ }
29
+
30
+ .mixin (@a, @b, @c) {
31
+ three-req: 3;
32
+ }
33
+
34
+ .mixin (@a: 1px, @b: 2px, @c: 3px) {
35
+ three: 3;
36
+ }
37
+
38
+ .zero {
39
+ .mixin();
40
+ }
41
+
42
+ .one {
43
+ .mixin(1);
44
+ }
45
+
46
+ .two {
47
+ .mixin(1, 2);
48
+ }
49
+
50
+ .three {
51
+ .mixin(1, 2, 3);
52
+ }
53
+
54
+ //
55
+
56
+ .mixout ('left') {
57
+ left: 1;
58
+ }
59
+
60
+ .mixout ('right') {
61
+ right: 1;
62
+ }
63
+
64
+ .left {
65
+ .mixout('left');
66
+ }
67
+ .right {
68
+ .mixout('right');
69
+ }
70
+
71
+ //
72
+
73
+ .border (@side, @width) {
74
+ color: black;
75
+ .border-side(@side, @width);
76
+ }
77
+ .border-side (left, @w) {
78
+ border-left: @w;
79
+ }
80
+ .border-side (right, @w) {
81
+ border-right: @w;
82
+ }
83
+
84
+ .border-right {
85
+ .border(right, 4px);
86
+ }
87
+ .border-left {
88
+ .border(left, 4px);
89
+ }
90
+
91
+ //
92
+
93
+
94
+ .border-radius (@r) {
95
+ both: @r * 10;
96
+ }
97
+ .border-radius (@r, left) {
98
+ left: @r;
99
+ }
100
+ .border-radius (@r, right) {
101
+ right: @r;
102
+ }
103
+
104
+ .only-right {
105
+ .border-radius(33, right);
106
+ }
107
+ .only-left {
108
+ .border-radius(33, left);
109
+ }
110
+ .left-right {
111
+ .border-radius(33);
112
+ }
113
+
lib/lessphp/tests/inputs/scopes.less ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ @a: 10;
4
+ @some {
5
+ @b: @a + 10;
6
+ div {
7
+ @c: @b + 10;
8
+ other {
9
+ @d: @c + 10;
10
+ world {
11
+ @e: @d + 10;
12
+ height: @e;
13
+ }
14
+ }
15
+ }
16
+ }
17
+
18
+
19
+ body {
20
+ @some;
21
+ }
22
+
23
+ @some;
24
+
25
+ .test(@x: 10) {
26
+ height: @x;
27
+ .test(@y: 11) {
28
+ height: @y;
29
+ .test(@z: 12) {
30
+ height: @z;
31
+ }
32
+ .test;
33
+ }
34
+ .test;
35
+ }
36
+
37
+ pre {
38
+ .test;
39
+ }
40
+
lib/lessphp/tests/inputs/site_demos.less ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // these are the demos from the lessphp homepage
2
+
3
+ default {
4
+ @base: 24px;
5
+ @border-color: #B2B;
6
+
7
+ .underline { border-bottom: 1px solid green }
8
+
9
+ #header {
10
+ color: black;
11
+ border: 1px solid @border-color + #222222;
12
+
13
+ .navigation {
14
+ font-size: @base / 2;
15
+ a {
16
+ .underline;
17
+ }
18
+ }
19
+ .logo {
20
+ width: 300px;
21
+ &:hover { text-decoration: none }
22
+ }
23
+ }
24
+ }
25
+
26
+ variables {
27
+ @a: 2;
28
+ @x: @a * @a;
29
+ @y: @x + 1;
30
+ @z: @x * 2 + @y;
31
+
32
+ @nice-blue: #5B83AD;
33
+ @light-blue: @nice-blue + #111;
34
+
35
+ @b: @a * 10;
36
+ @c: #888;
37
+ @fonts: "Trebuchet MS", Verdana, sans-serif;
38
+
39
+ .variables {
40
+ width: @z + 1cm; // 14cm
41
+ height: @b + @x + 0px; // 24px
42
+ color: @c;
43
+ background: @light-blue;
44
+ font-family: @fonts;
45
+ }
46
+ }
47
+
48
+ mixins {
49
+ .bordered {
50
+ border-top: dotted 1px black;
51
+ border-bottom: solid 2px black;
52
+ }
53
+ #menu a {
54
+ color: #111;
55
+ .bordered;
56
+ }
57
+
58
+ .post a {
59
+ color: red;
60
+ .bordered;
61
+ }
62
+ }
63
+
64
+ nested-rules {
65
+ #header {
66
+ color: black;
67
+
68
+ .navigation {
69
+ font-size: 12px;
70
+ }
71
+ .logo {
72
+ width: 300px;
73
+ &:hover { text-decoration: none }
74
+ }
75
+ }
76
+ }
77
+
78
+ namespaces {
79
+ #bundle {
80
+ .button {
81
+ display: block;
82
+ border: 1px solid black;
83
+ background-color: grey;
84
+ &:hover { background-color: white }
85
+ }
86
+ }
87
+ #header a {
88
+ color: orange;
89
+ #bundle > .button; // mixin the button class
90
+ }
91
+ }
92
+
93
+ mixin-functions {
94
+ @outer: 10px;
95
+ @class(@var:22px, @car: 400px + @outer) {
96
+ margin: @var;
97
+ height: @car;
98
+ }
99
+
100
+ @group {
101
+ @f(@color) {
102
+ color: @color;
103
+ }
104
+ .cool {
105
+ border-bottom: 1px solid green;
106
+ }
107
+ }
108
+
109
+ .class(@width:200px) {
110
+ padding: @width;
111
+ }
112
+
113
+ body {
114
+ .class(2.0em);
115
+ @group > @f(red);
116
+ @class(10px, 10px + 2);
117
+ @group > .cool;
118
+ }
119
+ }
120
+
lib/lessphp/tests/inputs/test-imports/file1.less ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ /**
4
+ * This is a test import file
5
+ */
6
+
7
+ @colors {
8
+ div.bright {
9
+ color: red;
10
+ }
11
+
12
+ div.sad {
13
+ color: blue;
14
+ }
15
+ }
16
+
lib/lessphp/tests/inputs/test-imports/file2.less ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+
2
+ b {
3
+ color: @color;
4
+ padding: 16px;
5
+ }
6
+
lib/lessphp/tests/inputs/variables.less ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @a: 2;
2
+ @x: @a * @a;
3
+ @y: @x + 1;
4
+ @z: @y + @x * 2;
5
+ @m: @z % @y;
6
+
7
+ @nice-blue: #5B83AD;
8
+ @light-blue: @nice-blue + #111;
9
+
10
+ @rgb-color: rgb(20%, 15%, 80%);
11
+ @rgba-color: rgba(23,68,149,0.5);
12
+
13
+ @b: @a * 10px;
14
+ @c: #888;
15
+ @fonts: "Trebuchet MS", Verdana, sans-serif;
16
+
17
+ .variables {
18
+ width: @z + 1cm; // 14cm
19
+ height: @b + @x + 0px; // 24px
20
+ margin-top: -@b; // -20px
21
+ margin-bottom: 10 - -@b; // 30px
22
+ @d: @c + #001;
23
+ color: @d;
24
+ background: @light-blue;
25
+ font-family: @fonts;
26
+ margin: @m + 0px; // 3px
27
+ font: 10px/12px serif;
28
+ font: 120%/120% serif;
29
+ }
30
+
31
+ .external {
32
+ color: @c;
33
+ border: 1px solid @rgb-color;
34
+ background: @rgba-color;
35
+ padding: @nonexistant + 4px;
36
+ }
37
+
38
+ @hello: 44px;
39
+ @something: "hello";
40
+ @cool: something;
41
+
42
+ color: @@something;
43
+ color: @@@cool;
44
+
45
+
lib/lessphp/tests/outputs/accessors.css ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .article { color:#294366; }
2
+ .comment {
3
+ width:960px;
4
+ color:#294366;
5
+ padding:10px;
6
+ }
7
+ .wow {
8
+ height:960px;
9
+ background-color:#294366;
10
+ color:green;
11
+ padding:;
12
+ margin:;
13
+ }
14
+ .mix { font-size:10px; }
lib/lessphp/tests/outputs/arity.css ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .one {
2
+ color:one;
3
+ color:one;
4
+ }
5
+ .two {
6
+ color:two;
7
+ color:two;
8
+ }
9
+ .three {
10
+ color:three;
11
+ color:three;
12
+ }
13
+ .multi-foo {
14
+ color:two;
15
+ color:three;
16
+ color:two;
17
+ color:three;
18
+ color:three;
19
+ }
20
+ .multi-baz {
21
+ color:two;
22
+ color:three;
23
+ color:two;
24
+ color:three;
25
+ }
lib/lessphp/tests/outputs/attributes.css ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * { color:blue; }
2
+ E { color:blue; }
3
+ E[foo] { color:blue; }
4
+ [foo] { color:blue; }
5
+ [foo] .helloWorld { color:blue; }
6
+ [foo].helloWorld { color:blue; }
7
+ E[foo="barbar"] { color:blue; }
8
+ E[foo~="hello#$@%@$#^"] { color:blue; }
9
+ E[foo^="color: green;"] { color:blue; }
10
+ E[foo$="239023"] { color:blue; }
11
+ E[foo*="29302"] { color:blue; }
12
+ E[foo|="239032"] { color:blue; }
13
+ E:root { color:blue; }
14
+ E:nth-child(odd) { color:blue; }
15
+ E:nth-child(2n+1) { color:blue; }
16
+ E:nth-child(5) { color:blue; }
17
+ E:nth-last-child(-n+2) { color:blue; }
18
+ E:nth-of-type(2n) { color:blue; }
19
+ E:nth-last-of-type(n) { color:blue; }
20
+ E:first-child { color:blue; }
21
+ E:last-child { color:blue; }
22
+ E:first-of-type { color:blue; }
23
+ E:last-of-type { color:blue; }
24
+ E:only-child { color:blue; }
25
+ E:only-of-type { color:blue; }
26
+ E:empty { color:blue; }
27
+ E:lang(en) { color:blue; }
28
+ E::first-line { color:blue; }
29
+ E::before { color:blue; }
30
+ E#id { color:blue; }
31
+ E:not(:link) { color:blue; }
32
+ E F { color:blue; }
33
+ E > F { color:blue; }
34
+ E + F { color:blue; }
35
+ E ~ F { color:blue; }
lib/lessphp/tests/outputs/builtins.css ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ color:"hello world";
3
+ height:5.1666666666667;
4
+ height:5px;
5
+ width:0.66666666666667;
6
+ width:1;
7
+ width:0;
8
+ width:3px;
9
+ color:#00112233;
10
+ color:#992c3742;
11
+ }
12
+ format {
13
+ format:"rgb(32, 128, 64)";
14
+ format-string:"hello world";
15
+ format-multiple:"hello earth 2";
16
+ format-url-encode:'red is %A';
17
+ eformat:rgb(32, 128, 64);
18
+ }
lib/lessphp/tests/outputs/colors.css ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ color:#996d33;
3
+ color:rgba(153,109,51,0.3);
4
+ lighten:#ffffff;
5
+ lighten:#7c8df2;
6
+ lighten:rgba(222,140,129,0.5);
7
+ darken:#d6d6d6;
8
+ darken:#0d1e81;
9
+ darken:rgba(18,42,185,0.5);
10
+ saturate:#f1eded;
11
+ saturate:#0025fe;
12
+ saturate:rgba(10,44,244,0.5);
13
+ desaturate:#efefef;
14
+ desaturate:#3349cb;
15
+ desaturate:rgba(36,62,218,0.5);
16
+ spin:#efefef;
17
+ spin:#2d17e7;
18
+ spin:rgba(59,23,231,0.5);
19
+ spin:#efefef;
20
+ spin:#1769e7;
21
+ spin:rgba(23,119,231,0.5);
22
+ one:#abcdef;
23
+ one:#abcdef;
24
+ two:rgba(2,159,35,0.9);
25
+ two:rgba(2,159,35,0.9);
26
+ three:rgba(1,2,3,0.6);
27
+ three:rgba(1,2,3,0.6);
28
+ four:rgba(1,2,3,0);
29
+ four:rgba(1,2,3,0);
30
+ hue:282;
31
+ sat:33;
32
+ lit:12;
33
+ what:#dff1da;
34
+ zero:#343434;
35
+ zero:#003468;
36
+ zero:#000000;
37
+ zero:#ffffff;
38
+ zero:#000000;
39
+ zero:#ffffff;
40
+ zero:#ffffff;
41
+ zero:#ffffff;
42
+ zero:#1d5612;
43
+ zero:#56124b;
44
+ zero:#000000;
45
+ zero:#ffffff;
46
+ }
47
+ alpha {
48
+ g:0;
49
+ g:1;
50
+ }
51
+ fade {
52
+ f:rgba(255,0,0,0.5);
53
+ f:rgba(255,255,255,0.2);
54
+ f:rgba(34,23,64,0.5);
55
+ }
56
+ .mix { color:#808080; }
57
+ .percent { per:50%; }
58
+ .colorz {
59
+ color:#ebebeb;
60
+ color:#ff9100;
61
+ }
62
+ body {
63
+ start:#fcf8e3;
64
+ spin:#fcf4e3;
65
+ chained:#fbeed5;
66
+ direct:#fbefd5;
67
+ }
68
+ pre { spin:#f2dee1; }
69
+ dd { background-color:#f5f5f5; }
lib/lessphp/tests/outputs/compile_on_mixin.css ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ body { height:22px; }
2
+ body ul { height:20px; }
3
+ body ul li { height:10px; }
4
+ body ul li div span, body ul li link {
5
+ margin:10px;
6
+ color:red;
7
+ }
8
+ body ul div, body ul p { border:1px; }
9
+ body ul div.hello, body ul p.hello { color:green; }
10
+ body ul div :what, body ul p :what { color:blue; }
11
+ body ul a b { color:blue; }
lib/lessphp/tests/outputs/escape.css ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ border:this is simple;
3
+ border:this;
4
+ border:this is simple;
5
+ border:1232;
6
+ border:world;
7
+ border:onemore;
8
+ border:;
9
+ line-height:eating rice;
10
+ line-height:string cheese;
11
+ line-height:a b c string me d e f;
12
+ }
13
+ .class { filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='image.png'); }
lib/lessphp/tests/outputs/font_family.css ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @font-face {
2
+ font-family:Graublau Sans Web;
3
+ src:url(fonts/GraublauWeb.otf) format("opentype");
4
+ }
5
+ @font-face {
6
+ font-family:Gentium;
7
+ src:url('fonts/Gentium.ttf');
8
+ }
9
+ @font-face {
10
+ font-family:Gentium;
11
+ src:url("fonts/GentiumItalic.ttf");
12
+ font-style:italic;
13
+ }
14
+ h2 {
15
+ font-family:Gentium;
16
+ crazy:maroon;
17
+ }
lib/lessphp/tests/outputs/guards.css ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ dd { color:yellow; }
2
+ b {
3
+ color:red;
4
+ color:blue;
5
+ color:blue;
6
+ }
7
+ img {
8
+ color:green;
9
+ color:teal;
10
+ }
11
+ body {
12
+ color:purple;
13
+ color:silver;
14
+ color:purple;
15
+ }
16
+ div { color:blue; }
17
+ link {
18
+ color:true red;
19
+ color:true #ffffff;
20
+ color:true #fffddd;
21
+ color:true #000000;
22
+ color:true rgba(0,0,0,0.34);
23
+ }
lib/lessphp/tests/outputs/hacks.css ADDED
@@ -0,0 +1 @@
 
1
+ :root .alert-message, :root .btn { border-radius:0 \0; }
lib/lessphp/tests/outputs/import.css ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import url("something.css") media;
2
+ @import url("something.css") media;
3
+ @import url("something.css") media, screen, print;
4
+ b {
5
+ color:maroon;
6
+ padding:16px;
7
+ }
8
+ body { line-height:10em; }
9
+ body div.bright { color:red; }
10
+ body div.sad { color:blue; }
11
+ div b {
12
+ color:fuchsia;
13
+ padding:16px;
14
+ }
lib/lessphp/tests/outputs/keyframes.css ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @keyframes 'bounce' {
2
+ from {
3
+ top:100px;
4
+ animation-timing-function:ease-out;
5
+ }
6
+ 25% {
7
+ top:50px;
8
+ animation-timing-function:ease-in;
9
+ }
10
+ 50% {
11
+ top:100px;
12
+ animation-timing-function:ease-out;
13
+ }
14
+ 75% {
15
+ top:75px;
16
+ animation-timing-function:ease-in;
17
+ }
18
+ to {
19
+ top:100px;
20
+ }
21
+ }
22
+ div {
23
+ animation-name:'diagonal-slide';
24
+ animation-duration:5s;
25
+ animation-iteration-count:10;
26
+ }
27
+ @keyframes 'diagonal-slide' {
28
+ from {
29
+ left:0;
30
+ top:0;
31
+ }
32
+ to {
33
+ left:100px;
34
+ top:100px;
35
+ }
36
+ }
lib/lessphp/tests/outputs/math.css ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .unary { sub:10 -5; }
2
+ .spaces {
3
+ sub:5;
4
+ sub:5;
5
+ add:15;
6
+ add:15;
7
+ div:2;
8
+ mul:50;
9
+ mul:50;
10
+ }
11
+ .supress-division {
12
+ border-radius:10px / 10px;
13
+ border-radius:10px / 10px;
14
+ border-radius:hello(10px / 10px) world;
15
+ font:10px / 30 sans-serif;
16
+ font:10px / 20px sans-serif;
17
+ font:10px / 20px sans-serif;
18
+ border-radius:0 15px 15px 15px / 0 50% 50% 50%;
19
+ }
20
+ .parens {
21
+ sub:5;
22
+ add:15;
23
+ div:2;
24
+ div:2;
25
+ mul:50;
26
+ }
27
+ .keyword-names { height:"hello" 25; }
28
+ .negation {
29
+ hello:-1px;
30
+ hello:-1px;
31
+ hello:-10;
32
+ }
33
+ .test {
34
+ single:5;
35
+ single:10;
36
+ single:10;
37
+ parens:10 -2;
38
+ math:20;
39
+ math:20;
40
+ width:71;
41
+ height:6;
42
+ padding:6px 1em 2px 2;
43
+ padding:8 4 4 4px;
44
+ width:96;
45
+ height:113;
46
+ margin:12;
47
+ }
48
+ .percents {
49
+ color:1000%;
50
+ color:1000%;
51
+ color:100%;
52
+ color:1000px;
53
+ color:1000%;
54
+ color:30%;
55
+ color:10%;
56
+ color:2%;
57
+ }
58
+ .misc {
59
+ x:40px;
60
+ y:40em;
61
+ }
lib/lessphp/tests/outputs/media.css ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @media screen, 3D {
2
+ P { color:green; }
3
+ }
4
+ @media print {
5
+ body { font-size:10pt; }
6
+ }
7
+ @media screen {
8
+ body { font-size:13px; }
9
+ }
10
+ @media screen, print {
11
+ body { line-height:1.2; }
12
+ }
13
+ @media all and (min-width: 0px) {
14
+ body { line-height:1.2; }
15
+ }
16
+ @media all and (min-width: 0) {
17
+ body { line-height:1.2; }
18
+ }
19
+ @media screen and (min-width: 102.5em) and (max-width: 117.9375em),
20
+ screen and (min-width: 150em) {
21
+ body { color:blue; }
22
+ }
23
+ @media screen and (min-height: 110px) {
24
+ body { color:red; }
25
+ }
26
+ @media screen and (height: 100px) and (width: 110px), (size: 120px) {
27
+ body { color:red; }
28
+ }
lib/lessphp/tests/outputs/misc.css ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @charset "utf-8";
2
+ color:"aaa, bbb";
3
+ color:"aaa, bbb";
4
+ .topbar { background:url(/assets/images/test/topbar.png); }
5
+ .hello { test:empty-function("/assets/images/test/",40%,to(#ffffff)); }
6
+ .css3 { background-image:-webkit-gradient(linear,0% 0%,0% 90%,from(#e9a000),to(#a37000)); }
7
+ .test, .world {
8
+ border:1px solid red;
9
+ color:url(http://mage-page.com);
10
+ string:"hello /* this is not a comment */";
11
+ world:"// neither is this";
12
+ string:'hello /* this is not a comment */';
13
+ world:'// neither is this';
14
+ what-ever:100px;
15
+ background:url(/*no comment here*/);
16
+ }
17
+ .urls {
18
+ background:url("http://google.com");
19
+ background:url(http://google.com);
20
+ background:url("http://google.com");
21
+ }
22
+ .cool { color:"aaa, bbb"; }
23
+ .span-17 { float:left; }
24
+ .span-17 { width:660px; }
25
+ .x {
26
+ float:left;
27
+ width:660px;
28
+ }
29
+ .hi pre { color:red; }
30
+ .hi pre { color:blue; }
31
+ .rad pre { color:red; }
32
+ .rad pre { color:blue; }
lib/lessphp/tests/outputs/mixin_functions.css ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ padding:2.0em;
3
+ color:red;
4
+ margin:10px;
5
+ height:12px;
6
+ border-bottom:1px solid green;
7
+ }
8
+ .skip_args {
9
+ margin:22px;
10
+ height:12px;
11
+ padding:10px 20px 30px 88px;
12
+ margin:4px 12px 2px 1px;
13
+ color:red;
14
+ }
lib/lessphp/tests/outputs/mixin_merging.css ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #test1 div {
2
+ color:red;
3
+ height:10px;
4
+ }
5
+ #test1 p { height:10px; }
6
+ #test2 b {
7
+ color:red;
8
+ width:1px;
9
+ }
10
+ #test2 a, #test2 i { width:1px; }
11
+ #test3 a, #test3 i { width:1px; }
12
+ #test3 b {
13
+ width:1px;
14
+ color:red;
15
+ }
16
+ #test4 a {
17
+ color:blue;
18
+ margin:1px;
19
+ }
20
+ #test4 div, #test4 html { color:blue; }
21
+ #test5 img, #test5 strong {
22
+ padding:2px;
23
+ float:right;
24
+ }
25
+ #test6 div a, #test6 span a {
26
+ line-height:10px;
27
+ color:red;
28
+ }
29
+ #test7 div strong {
30
+ margin:1px;
31
+ color:red;
32
+ }
33
+ #test7 div b { color:red; }
34
+ #test7 span strong, #test7 span b { color:red; }
35
+ #test8 a i, #test8 b i {
36
+ background:red;
37
+ color:red;
38
+ }
39
+ #test8 a s, #test8 b s {
40
+ background:red;
41
+ color:blue;
42
+ }
lib/lessphp/tests/outputs/mixins.css ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .bold {
2
+ font-size:20px;
3
+ font-weight:bold;
4
+ }
5
+ body #window {
6
+ border-radius:10px;
7
+ font-size:20px;
8
+ font-weight:bold;
9
+ line-height:30px;
10
+ }
11
+ #bundle .button {
12
+ display:block;
13
+ border:1px solid black;
14
+ background-color:grey;
15
+ }
16
+ #bundle .button:hover { background-color:white; }
17
+ #header a {
18
+ color:orange;
19
+ display:block;
20
+ border:1px solid black;
21
+ background-color:grey;
22
+ }
23
+ #header a:hover { background-color:white; }
24
+ div {
25
+ color:blue;
26
+ hello:world;
27
+ }
28
+ div b { color:blue; }
29
+ body {
30
+ color:blue;
31
+ hello:world;
32
+ }
33
+ body b { color:blue; }
34
+ .hello .world { color:blue; }
35
+ .foobar { color:blue; }
36
+ .eggs {
37
+ foo:1px 2px;
38
+ bar:1px 2px;
39
+ foo:100 land;
40
+ bar:100 land;
41
+ }
42
+ #hello { cool:one two three cool; }
43
+ #world { cool:"world"; }
44
+ #another {
45
+ things:red blue green;
46
+ things:red blue green skip me;
47
+ }
48
+ #day .cool { color:one two three; }
49
+ #day .cool { color:one two three skip me; }
lib/lessphp/tests/outputs/nested.css ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #header { color:black; }
2
+ #header .navigation { font-size:12px; }
3
+ #header .navigation .border .outside { color:blue; }
4
+ #header .logo { width:300px; }
5
+ #header .logo:hover { text-decoration:none; }
6
+ a b ul li { color:green; }
7
+ div .cool { color:green; }
8
+ p .cool span { color:yellow; }
9
+ div another { color:green; }
10
+ p another span { color:yellow; }
11
+ b .something { color:blue; }
12
+ b.something { color:blue; }
13
+ .foo .bar .qux, .foo .baz .qux { display:block; }
14
+ .qux .foo .bar, .qux .foo .baz { display:inline; }
15
+ .qux .foo .bar .biz, .qux .foo .baz .biz { display:none; }
16
+ b hello [x="&yeah"] { color:red; }
lib/lessphp/tests/outputs/nesting.css ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ #header .navigation .border .outside { color:blue; }
2
+ #header .navigation { font-size:12px; }
3
+ #header .logo:hover { text-decoration:none; }
4
+ #header .logo { width:300px; }
5
+ #header { color:black; }
6
+ a b ul li { color:green; }
lib/lessphp/tests/outputs/pattern_matching.css ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .class {
2
+ color:#a2a2a2;
3
+ display:block;
4
+ zero:0;
5
+ one:1;
6
+ two:2;
7
+ three:3;
8
+ }
9
+ .zero {
10
+ zero:0;
11
+ one:1;
12
+ two:2;
13
+ three:3;
14
+ }
15
+ .one {
16
+ zero:0;
17
+ one:1;
18
+ one-req:1;
19
+ two:2;
20
+ three:3;
21
+ }
22
+ .two {
23
+ display:block;
24
+ zero:0;
25
+ one:1;
26
+ two:2;
27
+ three:3;
28
+ }
29
+ .three {
30
+ zero:0;
31
+ one:1;
32
+ two:2;
33
+ three-req:3;
34
+ three:3;
35
+ }
36
+ .left { left:1; }
37
+ .right { right:1; }
38
+ .border-right {
39
+ color:black;
40
+ border-right:4px;
41
+ }
42
+ .border-left {
43
+ color:black;
44
+ border-left:4px;
45
+ }
46
+ .only-right { right:33; }
47
+ .only-left { left:33; }
48
+ .left-right { both:330; }
lib/lessphp/tests/outputs/scopes.css ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ body div other world { height:50; }
2
+ div other world { height:50; }
3
+ pre {
4
+ height:10;
5
+ height:11;
6
+ height:12;
7
+ }
lib/lessphp/tests/outputs/site_demos.css ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ default .underline { border-bottom:1px solid green; }
2
+ default #header {
3
+ color:black;
4
+ border:1px solid #dd44dd;
5
+ }
6
+ default #header .navigation { font-size:12px; }
7
+ default #header .navigation a { border-bottom:1px solid green; }
8
+ default #header .logo { width:300px; }
9
+ default #header .logo:hover { text-decoration:none; }
10
+ variables .variables {
11
+ width:14cm;
12
+ height:24px;
13
+ color:#888888;
14
+ background:#6c94be;
15
+ font-family:"Trebuchet MS", Verdana, sans-serif;
16
+ }
17
+ mixins .bordered {
18
+ border-top:dotted 1px black;
19
+ border-bottom:solid 2px black;
20
+ }
21
+ mixins #menu a {
22
+ color:#111111;
23
+ border-top:dotted 1px black;
24
+ border-bottom:solid 2px black;
25
+ }
26
+ mixins .post a {
27
+ color:red;
28
+ border-top:dotted 1px black;
29
+ border-bottom:solid 2px black;
30
+ }
31
+ nested-rules #header { color:black; }
32
+ nested-rules #header .navigation { font-size:12px; }
33
+ nested-rules #header .logo { width:300px; }
34
+ nested-rules #header .logo:hover { text-decoration:none; }
35
+ namespaces #bundle .button {
36
+ display:block;
37
+ border:1px solid black;
38
+ background-color:grey;
39
+ }
40
+ namespaces #bundle .button:hover { background-color:white; }
41
+ namespaces #header a {
42
+ color:orange;
43
+ display:block;
44
+ border:1px solid black;
45
+ background-color:grey;
46
+ }
47
+ namespaces #header a:hover { background-color:white; }
48
+ mixin-functions body {
49
+ padding:2.0em;
50
+ color:red;
51
+ margin:10px;
52
+ height:12px;
53
+ border-bottom:1px solid green;
54
+ }
lib/lessphp/tests/outputs/variables.css ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ color:44px;
2
+ color:44px;
3
+ .variables {
4
+ width:14cm;
5
+ height:24px;
6
+ margin-top:-20px;
7
+ margin-bottom:30px;
8
+ color:#888899;
9
+ background:#6c94be;
10
+ font-family:"Trebuchet MS", Verdana, sans-serif;
11
+ margin:3px;
12
+ font:10px / 12px serif;
13
+ font:120% / 120% serif;
14
+ }
15
+ .external {
16
+ color:#888888;
17
+ border:1px solid #3326cc;
18
+ background:rgba(23,68,149,0.5);
19
+ padding:4px;
20
+ }
lib/lessphp/tests/sort.php ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ error_reporting(E_ALL);
3
+
4
+ require realpath(dirname(__FILE__)).'/../lessc.inc.php';
5
+
6
+ // sorts the selectors in stylesheet in order to normalize it for comparison
7
+
8
+ $exe = array_shift($argv); // remove filename
9
+
10
+ if (!$fname = array_shift($argv)) {
11
+ $fname = "php://stdin";
12
+ }
13
+
14
+ // also sorts the tags in the block
15
+ function sort_key($block) {
16
+ if (!isset($block->sort_key)) {
17
+ sort($block->tags, SORT_STRING);
18
+ $block->sort_key = implode(",", $block->tags);
19
+ }
20
+
21
+ return $block->sort_key;
22
+ }
23
+
24
+ class sort_css extends lessc {
25
+ function __construct() {
26
+ parent::__construct();
27
+ }
28
+
29
+ // normalize numbers
30
+ function compileValue($value) {
31
+ $ignore = array('list', 'keyword', 'string', 'color', 'function');
32
+ if ($value[0] == 'number' || !in_array($value[0], $ignore)) {
33
+ $value[1] = $value[1] + 0; // convert to either double or int
34
+ }
35
+
36
+ return parent::compileValue($value);
37
+ }
38
+
39
+ function parse_and_sort($str) {
40
+ $root = $this->parseTree($str);
41
+
42
+ $less = $this;
43
+ usort($root->props, function($a, $b) use ($less) {
44
+
45
+ $sort = strcmp(sort_key($a[1]), sort_key($b[1]));
46
+ if ($sort == 0)
47
+ return strcmp($less->compileBlock($a[1]), $less->compileBlock($b[1]));
48
+ return $sort;
49
+ });
50
+
51
+ return $this->compileBlock($root);
52
+ }
53
+ }
54
+
55
+ $sorter = new sort_css;
56
+ echo $sorter->parse_and_sort(file_get_contents($fname));
57
+
lib/lessphp/tests/test.php ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env php
2
+ <?php
3
+ error_reporting(E_ALL);
4
+
5
+ /**
6
+ * Go through all files matching pattern in input directory
7
+ * and compile them, then compare them to paired file in
8
+ * output directory.
9
+ */
10
+ $difftool = 'diff -b -B -t -u';
11
+ $input = array(
12
+ 'dir' => 'inputs',
13
+ 'glob' => '*.less',
14
+ );
15
+
16
+ $output = array(
17
+ 'dir' => 'outputs',
18
+ 'filename' => '%s.css',
19
+ );
20
+
21
+
22
+ $prefix = strtr(realpath(dirname(__FILE__)), '\\', '/');
23
+ require $prefix.'/../lessc.inc.php';
24
+
25
+ $compiler = new lessc();
26
+ $compiler->importDir = array($input['dir'].'/test-imports');
27
+
28
+ $fa = 'Fatal Error: ';
29
+ if (php_sapi_name() != 'cli') {
30
+ exit($fa.$argv[0].' must be run in the command line.');
31
+ }
32
+
33
+ $opts = getopt('hCd::g');
34
+
35
+ if ($opts === false || isset($opts['h'])) {
36
+ echo <<<EOT
37
+ Usage: ./test.php [options] [searchstring]
38
+
39
+ where [options] can be a mix of these:
40
+
41
+ -h Show this help message and exit.
42
+
43
+ -d=[difftool] Show the diff of the actual output vs. the reference when a
44
+ test fails; uses 'diff -b -B -t -u' by default.
45
+
46
+ The test is aborted after the first failure report, unless
47
+ you also specify the '-g' option ('go on').
48
+
49
+ -g Continue executing the other tests when a test fails and
50
+ option '-d' is active.
51
+
52
+ -C Regenerate ('compile') the reference output files from the
53
+ given inputs.
54
+
55
+ WARNING: ONLY USE THIS OPTION WHEN YOU HAVE ASCERTAINED
56
+ THAT lessphp PROCESSES ALL TESTS CORRECTLY!
57
+
58
+ The optional [searchstring] is used to filter the input files: only tests
59
+ which have filename(s) containing the specified searchstring will be
60
+ executed. I.e. the corresponding glob pattern is '*[searchstring]*.less'.
61
+
62
+ The script EXIT CODE is the number of failed tests (with a maximum of 255),
63
+ 0 on success and 1 when this help message is shown. This aids in integrating
64
+ this script in larger (user defined) shell test scripts.
65
+
66
+
67
+ Examples of use:
68
+
69
+ - Test the full test set:
70
+ ./test.php
71
+
72
+ - Run only the mixin tests:
73
+ ./test.php mixin
74
+
75
+ - Use a custom diff tool to show diffs for failing tests
76
+ ./test.php -d=meld
77
+
78
+ EOT;
79
+ exit(1);
80
+ }
81
+
82
+ $input['dir'] = $prefix.'/'.$input['dir'];
83
+ $output['dir'] = $prefix.'/'.$output['dir'];
84
+ if (!is_dir($input['dir']) || !is_dir($output['dir']))
85
+ exit($fa." both input and output directories must exist\n");
86
+
87
+ $exe = array_shift($argv); // remove filename
88
+ // get the first non flag as search string
89
+ $searchString = null;
90
+ foreach ($argv as $a) {
91
+ if (strlen($a) > 0 && $a{0} != '-') {
92
+ $searchString = $a;
93
+ break;
94
+ }
95
+ }
96
+
97
+ $tests = array();
98
+ $matches = glob($input['dir'].'/'.(!is_null($searchString) ? '*'.$searchString : '' ).$input['glob']);
99
+ if ($matches) {
100
+ foreach ($matches as $fname) {
101
+ extract(pathinfo($fname)); // for $filename, from php 5.2
102
+ $tests[] = array(
103
+ 'in' => $fname,
104
+ 'out' => $output['dir'].'/'.sprintf($output['filename'], $filename),
105
+ );
106
+ }
107
+ }
108
+
109
+ $count = count($tests);
110
+ $compiling = isset($opts["C"]);
111
+ $continue_when_test_fails = isset($opts["g"]);
112
+ $showDiff = isset($opts["d"]);
113
+ if ($showDiff && !empty($opts["d"])) {
114
+ $difftool = $opts["d"];
115
+ }
116
+
117
+ echo ($compiling ? "Compiling" : "Running")." $count test".($count == 1 ? '' : 's').":\n";
118
+
119
+ function dump($msgs, $depth = 1, $prefix=" ") {
120
+ if (!is_array($msgs)) $msgs = array($msgs);
121
+ foreach ($msgs as $m) {
122
+ echo str_repeat($prefix, $depth).' - '.$m."\n";
123
+ }
124
+ }
125
+
126
+ $fail_prefix = " ** ";
127
+
128
+ $fail_count = 0;
129
+ $i = 1;
130
+ foreach ($tests as $test) {
131
+ printf(" [Test %04d/%04d] %s -> %s\n", $i, $count, basename($test['in']), basename($test['out']));
132
+
133
+ try {
134
+ ob_start();
135
+ $parsed = trim($compiler->parse(file_get_contents($test['in'])));
136
+ ob_end_clean();
137
+ } catch (exception $e) {
138
+ dump(array(
139
+ "Failed to compile input, reason:",
140
+ $e->getMessage(),
141
+ "Aborting"
142
+ ), 1, $fail_prefix);
143
+ break;
144
+ }
145
+
146
+ if ($compiling) {
147
+ file_put_contents($test['out'], $parsed);
148
+ } else {
149
+ if (!is_file($test['out'])) {
150
+ dump(array(
151
+ "Failed to find output file: $test[out]",
152
+ "Maybe you forgot to compile tests?",
153
+ "Aborting"
154
+ ), 1, $fail_prefix);
155
+ break;
156
+ }
157
+ $expected = trim(file_get_contents($test['out']));
158
+
159
+ // don't care about CRLF vs LF change (DOS/Win vs. UNIX):
160
+ $expected = trim(str_replace("\r\n", "\n", $expected));
161
+ $parsed = trim(str_replace("\r\n", "\n", $parsed));
162
+
163
+ if ($expected != $parsed) {
164
+ $fail_count++;
165
+ if ($showDiff) {
166
+ dump("Failed:", 1, $fail_prefix);
167
+ $tmp = $test['out'].".tmp";
168
+ file_put_contents($tmp, $parsed);
169
+ system($difftool.' '.$test['out'].' '.$tmp);
170
+ unlink($tmp);
171
+
172
+ if (!$continue_when_test_fails) {
173
+ dump("Aborting");
174
+ break;
175
+ } else {
176
+ echo "===========================================================================\n";
177
+ }
178
+ } else {
179
+ dump("Failed, run with -d flag to view diff", 1, $fail_prefix);
180
+ }
181
+ } else {
182
+ dump("Passed");
183
+ }
184
+ }
185
+
186
+ $i++;
187
+ }
188
+
189
+ exit($fail_count > 255 ? 255 : $fail_count);
190
+ ?>
package.xml ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <package>
3
+ <name>Magemaven_Lesscss</name>
4
+ <version>1.0.0</version>
5
+ <stability>stable</stability>
6
+ <license uri="http://www.opensource.org/licenses/academic.php">AFL 3.0</license>
7
+ <channel>community</channel>
8
+ <extends/>
9
+ <summary>On-the-fly LESS compiler for Magento.</summary>
10
+ <description>This extension allows to use LESS style sheets with your themes and extensions. </description>
11
+ <notes>Release 1.0.0</notes>
12
+ <authors><author><name>Sergey Storchay</name><user>magemaven</user><email>r8@r8.com.ua</email></author></authors>
13
+ <date>2012-03-10</date>
14
+ <time>20:46:09</time>
15
+ <contents><target name="mageetc"><dir name="modules"><file name="Magemaven_Lesscss.xml" hash="59826edbc32e9e187850838f18203ab4"/></dir></target><target name="magecommunity"><dir name="Magemaven"><dir name="Lesscss"><dir name="Helper"><file name="Data.php" hash="2baa4fadff35743af715178d438840ca"/></dir><dir name="Model"><dir name="Design"><file name="Package.php" hash="0e70bcb45c8848280400b937995c8314"/></dir></dir><dir name="etc"><file name="config.xml" hash="95529ab5cf05c7a97b3586b123a9f23d"/></dir></dir></dir></target><target name="magelib"><dir name="lessphp"><file name="LICENSE" hash="2887747a1404ae4fb71a95d440d3778f"/><file name="README.md" hash="9bbe64ec457107e15ba6557f4fdaf5c4"/><dir name="docs"><file name="docs.md" hash="aa2ad33b9ae06fd8fa6c010b1f8bc6f9"/></dir><file name="lessc.inc.php" hash="e18e8a4e302f6c04d539b40b3c3b5361"/><file name="lessify" hash="1785dcdc727eed44f85a077fba6bfaf1"/><file name="lessify.inc.php" hash="e0c246fc5d113d6c42417b96f2e27b54"/><file name="package.sh" hash="9ade14bb7ae270bad7a0bcbbb12e2e9d"/><file name="plessc" hash="26b9d803bbaf2f9e71bdb109862b9b3e"/><dir name="tests"><file name="README.md" hash="19ec04a8b6977f54303e672d62dfdb31"/><file name="bootstrap.sh" hash="81f4df43d98a40c55af3111096b62aa2"/><dir name="inputs"><file name="accessors.less.disable" hash="82824d2deea3ed791f55a23febd49d8e"/><file name="arity.less" hash="ad20b38ebd0bbf76a80f2cd1a4ee80d4"/><file name="attributes.less" hash="1cf5c7cacb11aa23f8c871586b713835"/><file name="builtins.less" hash="3c28a52dd49390410fd8c0b439b8d4f8"/><file name="colors.less" hash="6ed187a4df010f2fb75ee070e218bd1d"/><file name="compile_on_mixin.less" hash="2f93cb78ff6b2a14d0996c931c3fb8ed"/><file name="escape.less" hash="f8a400cc23ba493693b18f044d25890e"/><file name="font_family.less" hash="9763398b2eab9c7d5baec1c9928403ab"/><file name="guards.less" hash="e86a1a7dc5d2ac975acbb65bf3523294"/><file name="hacks.less" hash="42279bed222253b0c75c855042e0c706"/><file name="import.less" hash="661572342cc223d3e697d0f79114d0dd"/><file name="keyframes.less" hash="dfd56d45c708b3c77ec074b30deb6713"/><file name="math.less" hash="459f0c25abed3da1c646e6be52aa4856"/><file name="media.less" hash="0bf20f7cf79fcd66adb04e4753bac347"/><file name="misc.less" hash="69fdc55e93a63dd82757a03d2cac05e1"/><file name="mixin_functions.less" hash="f9dcc3bef561739ec001a58511e6cf12"/><file name="mixin_merging.less.disable" hash="2a0badaf78dd87853511405ccb947ab3"/><file name="mixins.less" hash="b2e21c2cd271dbfba7063d4d25b58276"/><file name="nested.less" hash="8472dc031fb756d8b8d03900d86a7458"/><file name="pattern_matching.less" hash="1fc54bff6eaa1b5d250aa709b7c9d418"/><file name="scopes.less" hash="7b8980c641e9af62542f031bb0cd2241"/><file name="site_demos.less" hash="469922c9e8da98807876669ccef98fa2"/><dir name="test-imports"><file name="file1.less" hash="e904720bb066d4efe0c65ead1c056be6"/><file name="file2.less" hash="cccffdf06c67308b854bb47a449abd4a"/></dir><file name="variables.less" hash="3fd7ab6b004cbf6d6990b0cd23d05b2e"/></dir><dir name="outputs"><file name="accessors.css" hash="661e58f3dba3bbcf9f79ffe93aded3f0"/><file name="arity.css" hash="04c43f26134bedd6dfbf0bba9b0dafab"/><file name="attributes.css" hash="c64706f9e55cbc3eb4a28b40574ae699"/><file name="builtins.css" hash="6c8a02eef368f92f586b0751bc6ab67d"/><file name="colors.css" hash="70dc4c3126437b92299240640ad8d5bc"/><file name="compile_on_mixin.css" hash="59a55eac3f3091885d948236a391c9a8"/><file name="escape.css" hash="9d55d81b77e9198da79a767e9b1ac153"/><file name="font_family.css" hash="6fd0efa23d2d889b39cf4ed222eaf8e4"/><file name="guards.css" hash="dcf56478ffd2ee7486c350d1f6243687"/><file name="hacks.css" hash="bcb4ef023033e1633701d45583bec91c"/><file name="import.css" hash="2462b0f69fe8146a279ad697b1a40102"/><file name="keyframes.css" hash="eead49b4fb430c216566d1929f29f344"/><file name="math.css" hash="4e036cbf646fb7244a05f94af7337bb6"/><file name="media.css" hash="c1aa77d87df7a60cbc5e16e132bed221"/><file name="misc.css" hash="732aed909108de12e4243d59c913b53a"/><file name="mixin_functions.css" hash="3d700556618b3160a18f55409b9b274d"/><file name="mixin_merging.css" hash="807f8f33d16f5b64630111bf831901ea"/><file name="mixins.css" hash="2fc39a0dbe8382c29ac14f9ed1bb1aaa"/><file name="nested.css" hash="6760e0c2b2e763eb1ec6a132cbf1babc"/><file name="nesting.css" hash="7864ba80e590ab1f38ffbb4aa911f4bd"/><file name="pattern_matching.css" hash="e02b6721bf72b3e503339617765a70cc"/><file name="scopes.css" hash="d5f054691542227ca8f87ff54add0267"/><file name="site_demos.css" hash="2e7ce6854144f0e5e7e3f201fdf325b2"/><file name="variables.css" hash="041e4331bb542952c891732219f148a9"/></dir><file name="sort.php" hash="36b106c892c42ccd721fb454194b12a9"/><file name="test.php" hash="5ecbfbcfea3d8019788792c28b6e935b"/></dir><file name=".gitignore" hash="7a7cb044e6ccc7bbdbcf997d705c12dd"/></dir></target></contents>
16
+ <compatible/>
17
+ <dependencies><required><php><min>5.2.0</min><max>6.0.0</max></php></required></dependencies>
18
+ </package>