├── .gitignore
├── .phpcs.xml
├── .travis.yml
├── LICENSE
├── Makefile
├── README.md
├── composer.json
├── docs
└── docs.md
├── lessc.inc.php
├── lessify
├── lessify.inc.php
├── package.sh
├── phpunit.xml.dist
├── plessc
└── tests
├── ApiTest.php
├── ErrorHandlingTest.php
├── InputTest.php
├── README.md
├── bootstrap.sh
├── inputs
├── accessors.less.disable
├── arity.less
├── attributes.less
├── builtins.less
├── colors.less
├── compile_on_mixin.less
├── data-uri.less
├── directives.less
├── escape.less
├── font_family.less
├── guards.less
├── hacks.less
├── hi.less
├── ie.less
├── import.less
├── interpolation.less
├── keyframes.less
├── math.less
├── media.less
├── misc.less
├── mixin_functions.less
├── mixin_merging.less.disable
├── mixins.less
├── nested.less
├── pattern_matching.less
├── scopes.less
├── selector_expressions.less
├── site_demos.less
├── test-imports
│ ├── a.less
│ ├── b.less
│ ├── file1.less
│ ├── file2.less
│ ├── file3.less
│ └── inner
│ │ ├── file1.less
│ │ └── file2.less
└── variables.less
├── inputs_lessjs
├── mixins-args.less
├── mixins-named-args.less
└── strings.less
├── outputs
├── accessors.css
├── arity.css
├── attributes.css
├── builtins.css
├── colors.css
├── compile_on_mixin.css
├── data-uri.css
├── directives.css
├── escape.css
├── font_family.css
├── guards.css
├── hacks.css
├── hi.css
├── ie.css
├── import.css
├── interpolation.css
├── keyframes.css
├── math.css
├── media.css
├── misc.css
├── mixin_functions.css
├── mixin_merging.css
├── mixins.css
├── nested.css
├── nesting.css
├── pattern_matching.css
├── scopes.css
├── selector_expressions.css
├── site_demos.css
└── variables.css
├── outputs_lessjs
├── mixins-args.css
├── mixins-named-args.css
└── strings.css
└── sort.php
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 | *~
3 | /*.less
4 | /*.css
5 | tests/bootstrap
6 | tests/tmp
7 | vendor
8 | composer.lock
9 |
--------------------------------------------------------------------------------
/.phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | .
37 |
38 |
39 | vendor
40 |
41 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.6
5 | - hhvm
6 | - 7.0
7 | - 7.1
8 | - 7.2
9 | - 7.3
10 |
11 | sudo: false
12 |
13 | install:
14 | - travis_retry composer install --no-interaction --prefer-source
15 |
16 | script:
17 | - composer test
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | For ease of distribution, lessphp 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) 2014 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.
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 |
661 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 | test:
3 | phpunit --colors tests
4 |
5 | release:
6 | ./package.sh
7 |
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/leafo/lessphp)
2 |
3 | # lessphp v0.5.0
4 | ###
5 |
6 | `lessphp` is a compiler for LESS written in PHP. The documentation is great,
7 | so check it out: .
8 |
9 | Here's a quick tutorial:
10 |
11 | ### How to use in your PHP project
12 |
13 | The only file required is `lessc.inc.php`, so copy that to your include directory.
14 |
15 | The typical flow of **lessphp** is to create a new instance of `lessc`,
16 | configure it how you like, then tell it to compile something using one built in
17 | compile methods.
18 |
19 | The `compile` method compiles a string of LESS code to CSS.
20 |
21 | ```php
22 | compile(".block { padding: 3 + 4px }");
27 | ```
28 |
29 | The `compileFile` method reads and compiles a file. It will either return the
30 | result or write it to the path specified by an optional second argument.
31 |
32 | ```php
33 | compileFile("input.less");
35 | ```
36 |
37 | The `checkedCompile` method is like `compileFile`, but it only compiles if the output
38 | file doesn't exist or it's older than the input file:
39 |
40 | ```php
41 | checkedCompile("input.less", "output.css");
43 | ```
44 |
45 | If there any problem compiling your code, an exception is thrown with a helpful message:
46 |
47 | ```php
48 | compile("invalid LESS } {");
51 | } catch (exception $e) {
52 | echo "fatal error: " . $e->getMessage();
53 | }
54 | ```
55 |
56 | The `lessc` object can be configured through an assortment of instance methods.
57 | Some possible configuration options include [changing the output format][1],
58 | [setting variables from PHP][2], and [controlling the preservation of
59 | comments][3], writing [custom functions][4] and much more. It's all described
60 | in [the documentation][0].
61 |
62 |
63 | [0]: http://leafo.net/lessphp/docs/
64 | [1]: http://leafo.net/lessphp/docs/#output_formatting
65 | [2]: http://leafo.net/lessphp/docs/#setting_variables_from_php
66 | [3]: http://leafo.net/lessphp/docs/#preserving_comments
67 | [4]: http://leafo.net/lessphp/docs/#custom_functions
68 |
69 |
70 | ### How to use from the command line
71 |
72 | An additional script has been included to use the compiler from the command
73 | line. In the simplest invocation, you specify an input file and the compiled
74 | css is written to standard out:
75 |
76 | $ plessc input.less > output.css
77 |
78 | Using the -r flag, you can specify LESS code directly as an argument or, if
79 | the argument is left off, from standard in:
80 |
81 | $ plessc -r "my less code here"
82 |
83 | Finally, by using the -w flag you can watch a specified input file and have it
84 | compile as needed to the output file:
85 |
86 | $ plessc -w input-file output-file
87 |
88 | Errors from watch mode are written to standard out.
89 |
90 | The -f flag sets the [output formatter][1]. For example, to compress the
91 | output run this:
92 |
93 | $ plessc -f=compressed myfile.less
94 |
95 | For more help, run `plessc --help`
96 |
97 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "leafo/lessphp",
3 | "type": "library",
4 | "description": "lessphp is a compiler for LESS written in PHP.",
5 | "homepage": "http://leafo.net/lessphp/",
6 | "license": [
7 | "MIT",
8 | "GPL-3.0"
9 | ],
10 | "authors": [
11 | {
12 | "name": "Leaf Corcoran",
13 | "email": "leafot@gmail.com",
14 | "homepage": "http://leafo.net"
15 | }
16 | ],
17 | "bin": [
18 | "plessc",
19 | "lessify"
20 | ],
21 | "autoload": {
22 | "classmap": ["lessc.inc.php"]
23 | },
24 | "require-dev": {
25 | "phpunit/phpunit": "^5.7.27",
26 | "squizlabs/php_codesniffer": "3.3.2"
27 | },
28 | "scripts": {
29 | "test": [
30 | "phpunit",
31 | "phpcs -p -s"
32 | ],
33 | "fix": "phpcbf"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/docs/docs.md:
--------------------------------------------------------------------------------
1 | title: v0.5.0 documentation
2 | link_to_home: true
3 | --
4 |
5 | Documentation v0.5.0
6 |
7 | $index
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 |
65 | Variables are identified with a name that starts with `@`. To declare a
66 | variable, you create an appropriately named CSS property and assign it a value:
67 |
68 | ```less
69 | @family: "verdana";
70 | @color: red;
71 | body {
72 | @mycolor: red;
73 | font-family: @family;
74 | color: @color;
75 | border-bottom: 1px solid @color;
76 | }
77 | ```
78 |
79 | Variable declarations will not appear in the output. Variables can be declared
80 | in the outer most scope of the file, or anywhere else a CSS property may
81 | appear. They can hold any CSS property value.
82 |
83 | Variables are only visible for use from their current scope, or any enclosed
84 | scopes.
85 |
86 | If you have a string or keyword in a variable, you can reference another
87 | variable by that name by repeating the `@`:
88 |
89 | ```less
90 | @value: 20px;
91 | @value_name: "value";
92 |
93 | width: @@value_name;
94 | ```
95 |
96 | ### Expressions
97 |
98 | Expressions let you combine values and variables in meaningful ways. For
99 | example you can add to a color to make it a different shade. Or divide up the
100 | width of your layout logically. You can even concatenate strings.
101 |
102 | Use the mathematical operators to evaluate an expression:
103 |
104 | ```less
105 | @width: 960px;
106 | .nav {
107 | width: @width / 3;
108 | color: #001 + #abc;
109 | }
110 | .body {
111 | width: 2 * @width / 3;
112 | font-family: "hel" + "vetica";
113 | }
114 | ```
115 |
116 | Parentheses can be used to control the order of evaluation. They can also be
117 | used to force an evaluation for cases where CSS's syntax makes the expression
118 | ambiguous.
119 |
120 | The following property will produce two numbers, instead of doing the
121 | subtraction:
122 |
123 | ```less
124 | margin: 10px -5px;
125 | ```
126 |
127 | To force the subtraction:
128 |
129 | ```less
130 | margin: (10px -5px);
131 | ```
132 |
133 | It is also safe to surround mathematical operators by spaces to ensure that
134 | they are evaluated:
135 |
136 | ```less
137 | margin: 10px - 5px;
138 | ```
139 |
140 | Division has a special quirk. There are certain CSS properties that use the `/`
141 | operator as part of their value's syntax. Namely, the [font][4] shorthand and
142 | [border-radius][3].
143 |
144 | [3]: https://developer.mozilla.org/en/CSS/border-radius
145 | [4]: https://developer.mozilla.org/en/CSS/font
146 |
147 |
148 | Thus, **lessphp** will ignore any division in these properties unless it is
149 | wrapped in parentheses. For example, no division will take place here:
150 |
151 | ```less
152 | .font {
153 | font: 20px/80px "Times New Roman";
154 | }
155 | ```
156 |
157 | In order to force division we must wrap the expression in parentheses:
158 |
159 | ```less
160 | .font {
161 | font: (20px/80px) "Times New Roman";
162 | }
163 | ```
164 |
165 | If you want to write a literal `/` expression without dividing in another
166 | property (or a variable), you can use [string unquoting](#string_unquoting):
167 |
168 | ```less
169 | .var {
170 | @size: ~"20px/80px";
171 | font: @size sans-serif;
172 | }
173 | ```
174 |
175 | ### Nested Blocks
176 |
177 | By nesting blocks we can build up a chain of CSS selectors through scope
178 | instead of repeating them. In addition to reducing repetition, this also helps
179 | logically organize the structure of our CSS.
180 |
181 | ```less
182 | ol.list {
183 | li.special {
184 | border: 1px solid red;
185 | }
186 |
187 | li.plain {
188 | font-weight: bold;
189 | }
190 | }
191 | ```
192 |
193 |
194 | This will produce two blocks, a `ol.list li.special` and `ol.list li.plain`.
195 |
196 | Blocks can be nested as deep as required in order to build a hierarchy of
197 | relationships.
198 |
199 | The `&` operator can be used in a selector to represent its parent's selector.
200 | If the `&` operator is used, then the default action of appending the parent to
201 | the front of the child selector separated by space is not performed.
202 |
203 | ```less
204 | b {
205 | a & {
206 | color: red;
207 | }
208 |
209 | // the following have the same effect
210 |
211 | & i {
212 | color: blue;
213 | }
214 |
215 | i {
216 | color: blue;
217 | }
218 | }
219 | ```
220 |
221 |
222 | Because the `&` operator respects the whitespace around it, we can use it to
223 | control how the child blocks are joined. Consider the differences between the
224 | following:
225 |
226 | ```less
227 | div {
228 | .child-class { color: purple; }
229 |
230 | &.isa-class { color: green; }
231 |
232 | #child-id { height: 200px; }
233 |
234 | div-id { height: 400px; }
235 |
236 | &:hover { color: red; }
237 |
238 | :link { color: blue; }
239 | }
240 | ```
241 |
242 | The `&` operator also works with [mixins](#mixins), which produces interesting results:
243 |
244 | ```less
245 | .within_box_style() {
246 | .box & {
247 | color: blue;
248 | }
249 | }
250 |
251 | #menu {
252 | .within_box_style;
253 | }
254 | ```
255 |
256 | ### Mixins
257 |
258 | Any block can be mixed in just by naming it:
259 |
260 | ```less
261 | .mymixin {
262 | color: blue;
263 | border: 1px solid red;
264 |
265 | .special {
266 | font-weight: bold;
267 | }
268 | }
269 |
270 |
271 | h1 {
272 | font-size: 200px;
273 | .mymixin;
274 | }
275 | ```
276 |
277 | All properties and child blocks are mixed in.
278 |
279 | Mixins can be made parametric, meaning they can take arguments, in order to
280 | enhance their utility. A parametric mixin all by itself is not outputted when
281 | compiled. Its properties will only appear when mixed into another block.
282 |
283 | The canonical example is to create a rounded corners mixin that works across
284 | browsers:
285 |
286 | ```less
287 | .rounded-corners(@radius: 5px) {
288 | border-radius: @radius;
289 | -webkit-border-radius: @radius;
290 | -moz-border-radius: @radius;
291 | }
292 |
293 | .header {
294 | .rounded-corners();
295 | }
296 |
297 | .info {
298 | background: red;
299 | .rounded-corners(14px);
300 | }
301 | ```
302 |
303 | If you have a mixin that doesn't have any arguments, but you don't want it to
304 | show up in the output, give it a blank argument list:
305 |
306 | ```less
307 | .secret() {
308 | font-size: 6000px;
309 | }
310 |
311 | .div {
312 | .secret;
313 | }
314 | ```
315 |
316 | If the mixin doesn't need any arguments, you can leave off the parentheses when
317 | mixing it in, as seen above.
318 |
319 | You can also mixin a block that is nested inside other blocks. You can think of
320 | the outer block as a way of making a scope for your mixins. You just list the
321 | names of the mixins separated by spaces, which describes the path to the mixin
322 | you want to include. Optionally you can separate them by `>`.
323 |
324 | ```less
325 | .my_scope {
326 | .some_color {
327 | color: red;
328 | .inner_block {
329 | text-decoration: underline;
330 | }
331 | }
332 | .bold {
333 | font-weight: bold;
334 | color: blue;
335 | }
336 | }
337 |
338 | .a_block {
339 | .my_scope .some_color;
340 | .my_scope .some_color .inner_block;
341 | }
342 |
343 | .another_block {
344 | // the alternative syntax
345 | .my_scope > .bold;
346 | }
347 | ```
348 |
349 |
350 | #### Mixin Arguments
351 |
352 | When declaring a mixin you can specify default values for each argument. Any
353 | argument left out will be given the default value specified. Here's the
354 | syntax:
355 |
356 | ```less
357 | .mix(@color: red, @height: 20px, @pad: 12px) {
358 | border: 1px solid @color;
359 | height: @height - @pad;
360 | padding: @pad;
361 | }
362 |
363 | .default1 {
364 | .mix();
365 | }
366 |
367 | .default2 {
368 | .mix(blue);
369 | }
370 |
371 | .default3 {
372 | .mix(blue, 40px, 5px);
373 | }
374 | ```
375 |
376 | Additionally, you can also call a mixin using the argument names, this is
377 | useful if you want to replace a specific argument while having all the others
378 | take the default regardless of what position the argument appears in. The
379 | syntax looks something like this:
380 |
381 |
382 | ```lessbasic
383 | div {
384 | .my_mixin(@paddding: 4px); // @color and @height get default values
385 | .my_mixin(@paddding: 4px, @height: 50px); // you can specify them in any order
386 | }
387 | ```
388 |
389 | You can also combine the ordered arguments with the named ones:
390 |
391 | ```lessbasic
392 | div {
393 | // @color is blue, @padding is 4px, @height is default
394 | .my_mixin(blue, @padding: 4px);
395 | }
396 | ```
397 |
398 | Mixin arguments can be delimited with either a `,` or `;`, but only one can be
399 | active at once. This means that each argument is separated by either `,` or
400 | `;`. By default `,` is the delimiter, in all the above examples we used a `,`.
401 |
402 | A problem arises though, sometimes CSS value lists are made up with commas. In
403 | order to be able to pass a comma separated list literal we need to use `;` as
404 | the delimiter. (You don't need to worry about this if your list is stored in a
405 | variable)
406 |
407 | If a `;` appears anywhere in the argument list, then it will be used as the
408 | argument delimiter, and all commas we be used as part of the argument values.
409 |
410 | Here's a basic example:
411 |
412 | ```less
413 | .fancy_mixin(@box_shadow, @color: blue) {
414 | border: 1px solid @color;
415 | box-shadow: @box_shadow;
416 | }
417 |
418 |
419 | div {
420 | // two arguments passed separated by ;
421 | .fancy_mixin(2px 2px, -2px -2px; red);
422 | }
423 |
424 | pre {
425 | // one argument passed, ends in ;
426 | .fancy_mixin(inset 4px 4px, -2px 2px;);
427 | }
428 |
429 | ```
430 |
431 | If we only want to pass a single comma separated value we still need to use
432 | `;`, to do this we stick it on the end as demonstrated above.
433 |
434 |
435 | #### `@arguments` Variable
436 |
437 | Within an mixin there is a special variable named `@arguments` that contains
438 | all the arguments passed to the mixin along with any remaining arguments that
439 | have default values. The value of the variable has all the values separated by
440 | spaces.
441 |
442 | This useful for quickly assigning all the arguments:
443 |
444 | ```less
445 | .box-shadow(@x, @y, @blur, @color) {
446 | box-shadow: @arguments;
447 | -webkit-box-shadow: @arguments;
448 | -moz-box-shadow: @arguments;
449 | }
450 | .menu {
451 | .box-shadow(1px, 1px, 5px, #aaa);
452 | }
453 | ```
454 |
455 | In addition to the arguments passed to the mixin, `@arguments` will also include
456 | remaining default values assigned by the mixin:
457 |
458 |
459 | ```less
460 | .border-mixin(@width, @style: solid, @color: black) {
461 | border: @arguments;
462 | }
463 |
464 | pre {
465 | .border-mixin(4px, dotted);
466 | }
467 |
468 | ```
469 |
470 |
471 | #### Pattern Matching
472 |
473 | When you *mix in* a mixin, all the available mixins of that name in the current
474 | scope are checked to see if they match based on what was passed to the mixin
475 | and how it was declared.
476 |
477 | The simplest case is matching by number of arguments. Only the mixins that
478 | match the number of arguments passed in are used.
479 |
480 | ```less
481 | .simple() { // matches no arguments
482 | height: 10px;
483 | }
484 |
485 | .simple(@a, @b) { // matches two arguments
486 | color: red;
487 | }
488 |
489 | .simple(@a) { // matches one argument
490 | color: blue;
491 | }
492 |
493 | div {
494 | .simple(10);
495 | }
496 |
497 | span {
498 | .simple(10, 20);
499 | }
500 | ```
501 |
502 | Whether an argument has default values is also taken into account when matching
503 | based on number of arguments:
504 |
505 | ```less
506 | // matches one or two arguments
507 | .hello(@a, @b: blue) {
508 | height: @a;
509 | color: @b;
510 | }
511 |
512 | .hello(@a, @b) { // matches only two
513 | width: @a;
514 | border-color: @b;
515 | }
516 |
517 | .hello(@a) { // matches only one
518 | padding: 1em;
519 | }
520 |
521 | div {
522 | .hello(10px);
523 | }
524 |
525 | pre {
526 | .hello(10px, yellow);
527 | }
528 | ```
529 |
530 | Additionally, a *vararg* value can be used to further control how things are
531 | matched. A mixin's argument list can optionally end in the special argument
532 | named `...`. The `...` may match any number of arguments, including 0.
533 |
534 | ```less
535 | // this will match any number of arguments
536 | .first(...) {
537 | color: blue;
538 | }
539 |
540 | // matches at least 1 argument
541 | .second(@arg, ...) {
542 | height: 200px + @arg;
543 | }
544 |
545 | div { .first("some", "args"); }
546 | pre { .second(10px); }
547 | ```
548 |
549 | If you want to capture the values that get captured by the *vararg* you can
550 | give it a variable name by putting it directly before the `...`. This variable
551 | must be the last argument defined. It's value is just like the special
552 | [`@arguments` variable](#arguments_variable), a space separated list.
553 |
554 |
555 | ```less
556 | .hello(@first, @rest...) {
557 | color: @first;
558 | text-shadow: @rest;
559 | }
560 |
561 | span {
562 | .hello(red, 1px, 1px, 0px, white);
563 | }
564 |
565 | ```
566 |
567 | Another way of controlling whether a mixin matches is by specifying a value in
568 | place of an argument name when declaring the mixin:
569 |
570 | ```less
571 | .style(old, @size) {
572 | font: @size serif;
573 | }
574 |
575 | .style(new, @size) {
576 | font: @size sans-serif;
577 | }
578 |
579 | .style(@_, @size) {
580 | letter-spacing: floor(@size / 6px);
581 | }
582 |
583 | em {
584 | @switch: old;
585 | .style(@switch, 15px);
586 | }
587 | ```
588 |
589 | Notice that two of the three mixins were matched. The mixin with a matching
590 | first argument, and the generic mixin that matches two arguments. It's common
591 | to use `@_` as the name of a variable we intend to not use. It has no special
592 | meaning to LESS, just to the reader of the code.
593 |
594 | #### Guards
595 |
596 | Another way of restricting when a mixin is mixed in is by using guards. A guard
597 | is a special expression that is associated with a mixin declaration that is
598 | evaluated during the mixin process. It must evaluate to true before the mixin
599 | can be used.
600 |
601 | We use the `when` keyword to begin describing a list of guard expressions.
602 |
603 | Here's a simple example:
604 |
605 | ```less
606 | .guarded(@arg) when (@arg = hello) {
607 | color: blue;
608 | }
609 |
610 | div {
611 | .guarded(hello); // match
612 | }
613 |
614 | span {
615 | .guarded(world); // no match
616 | }
617 | ```
618 | Only the `div`'s mixin will match in this case, because the guard expression
619 | requires that `@arg` is equal to `hello`.
620 |
621 | We can include many different guard expressions by separating them by commas.
622 | Only one of them needs to match to trigger the mixin:
623 |
624 | ```less
625 | .x(@a, @b) when (@a = hello), (@b = world) {
626 | width: 960px;
627 | }
628 |
629 | div {
630 | .x(hello, bar); // match
631 | }
632 |
633 | span {
634 | .x(foo, world); // match
635 | }
636 |
637 | pre {
638 | .x(foo, bar); // no match
639 | }
640 | ```
641 |
642 | Instead of a comma, we can use `and` keyword to make it so all of the guards
643 | must match in order to trigger the mixin. `and` has higher precedence than the
644 | comma.
645 |
646 | ```less
647 | .y(@a, @b) when (@a = hello) and (@b = world) {
648 | height: 600px;
649 | }
650 |
651 | div {
652 | .y(hello, world); // match
653 | }
654 |
655 | span {
656 | .y(hello, bar); // no match
657 | }
658 | ```
659 |
660 | Commas and `and`s can be mixed and matched.
661 |
662 | You can also negate a guard expression by using `not` in from of the parentheses:
663 |
664 | ```less
665 | .x(@a) when not (@a = hello) {
666 | color: blue;
667 | }
668 |
669 | div {
670 | .x(hello); // no match
671 | }
672 | ```
673 |
674 | The `=` operator is used to check equality between any two values. For numbers
675 | the following comparison operators are also defined:
676 |
677 | `<`, `>`, `=<`, `>=`
678 |
679 | There is also a collection of predicate functions that can be used to test the
680 | type of a value.
681 |
682 | These are `isnumber`, `iscolor`, `iskeyword`, `isstring`, `ispixel`,
683 | `ispercentage` and `isem`.
684 |
685 | ```less
686 | .mix(@a) when (ispercentage(@a)) {
687 | height: 500px * @a;
688 | }
689 | .mix(@a) when (ispixel(@a)) {
690 | height: @a;
691 | }
692 |
693 | div.a {
694 | .mix(50%);
695 | }
696 |
697 | div.a {
698 | .mix(350px);
699 | }
700 | ```
701 |
702 | #### !important
703 |
704 | If you want to apply the `!important` suffix to every property when mixing in a
705 | mixin, just append `!important` to the end of the call to the mixin:
706 |
707 | ```less
708 | .make_bright {
709 | color: red;
710 | font-weight: bold;
711 | }
712 |
713 | .color {
714 | color: green;
715 | }
716 |
717 | body {
718 | .make_bright() !important;
719 | .color();
720 | }
721 |
722 | ```
723 |
724 | ### Selector Expressions
725 |
726 | Sometimes we want to dynamically generate the selector of a block based on some
727 | variable or expression. We can do this by using *selector expressions*. Selector
728 | expressions are CSS selectors that are evaluated in the current scope before
729 | being written out.
730 |
731 | A simple example is a mixin that dynamically creates a selector named after the
732 | mixin's argument:
733 |
734 | ```less
735 | .create-selector(@name) {
736 | @{name} {
737 | color: red;
738 | }
739 | }
740 |
741 | .create-selector(hello);
742 | .create-selector(world);
743 | ```
744 |
745 | The string interpolation syntax works inside of selectors, letting you insert varaibles.
746 |
747 | Here's an interesting example adapted from Twitter Bootstrap. A couple advanced
748 | things are going on. We are using [Guards](#guards) along with a recursive
749 | mixin to work like a loop to generate a series of CSS blocks.
750 |
751 |
752 | ```less
753 | // create our recursive mixin:
754 | .spanX (@index) when (@index > 0) {
755 | .span@{index} {
756 | width: @index * 100px;
757 | }
758 | .spanX(@index - 1);
759 | }
760 | .spanX (0) {}
761 |
762 | // mix it into the global scopee:
763 | .spanX(4);
764 | ```
765 |
766 | ### Import
767 |
768 | Multiple LESS files can be compiled into a single CSS file by using the
769 | `@import` statement. Be careful, the LESS import statement shares syntax with
770 | the CSS import statement. If the file being imported ends in a `.less`
771 | extension, or no extension, then it is treated as a LESS import. Otherwise it
772 | is left alone and outputted directly:
773 |
774 | ```lessbasic
775 | // my_file.less
776 | .some-mixin(@height) {
777 | height: @height;
778 | }
779 |
780 | // main.less
781 | @import "main.less" // will import the file if it can be found
782 | @import "main.css" // will be left alone
783 |
784 | body {
785 | .some-mixin(400px);
786 | }
787 | ```
788 |
789 | All of the following lines are valid ways to import the same file:
790 |
791 | ```lessbasic
792 | @import "file";
793 | @import 'file.less';
794 | @import url("file");
795 | @import url('file');
796 | @import url(file);
797 | ```
798 |
799 | When importing, the `importDir` is searched for files. This can be configured,
800 | see [PHP Interface](#php_interface).
801 |
802 | A file is only imported once. If you try to include the same file multiple
803 | times all the import statements after the first produce no output.
804 |
805 | ### String Interpolation
806 |
807 | String interpolation is a convenient way to insert the value of a variable
808 | right into a string literal. Given some variable named `@var_name`, you just
809 | need to write it as `@{var_name}` from within the string to have its value
810 | inserted:
811 |
812 | ```less
813 | @symbol: ">";
814 | h1:before {
815 | content: "@{symbol}: ";
816 | }
817 |
818 | h2:before {
819 | content: "@{symbol}@{symbol}: ";
820 | }
821 | ```
822 |
823 | There are two kinds of strings, implicit and explicit strings. Explicit strings
824 | are wrapped by double quotes, `"hello I am a string"`, or single quotes `'I am
825 | another string'`. Implicit strings only appear when using `url()`. The text
826 | between the parentheses is considered a string and thus string interpolation is
827 | possible:
828 |
829 | ```less
830 | @path: "files/";
831 | body {
832 | background: url(@{path}my_background.png);
833 | }
834 | ```
835 |
836 | ### String Format Function
837 |
838 | The `%` function can be used to insert values into strings using a *format
839 | string*. It works similar to `printf` seen in other languages. It has the
840 | same purpose as string interpolation above, but gives explicit control over
841 | the output format.
842 |
843 | ```less
844 | @symbol: ">";
845 | h1:before {
846 | content: %("%s: ", @symbol);
847 | }
848 | ```
849 |
850 | The `%` function takes as its first argument the format string, following any
851 | number of addition arguments that are inserted in place of the format
852 | directives.
853 |
854 | A format directive starts with a `%` and is followed by a single character that
855 | is either `a`, `d`, or `s`:
856 |
857 | ```less
858 | strings: %("%a %d %s %a", hi, 1, 'ok', 'cool');
859 | ```
860 |
861 | `%a` and `%d` format the value the same way: they compile the argument to its
862 | CSS value and insert it directly. When used with a string, the quotes are
863 | included in the output. This typically isn't what we want, so we have the `%s`
864 | format directive which strips quotes from strings before inserting them.
865 |
866 | The `%d` directive functions the same as `%a`, but is typically used for numbers
867 | assuming the output format of numbers might change in the future.
868 |
869 | ### String Unquoting
870 |
871 | Sometimes you will need to write proprietary CSS syntax that is unable to be
872 | parsed. As a workaround you can place the code into a string and unquote it.
873 | Unquoting is the process of outputting a string without its surrounding quotes.
874 | There are two ways to unquote a string.
875 |
876 | The `~` operator in front of a string will unquote that string:
877 |
878 | ```less
879 | .class {
880 | // a made up, but problematic vendor specific CSS
881 | filter: ~"Microsoft.AlphaImage(src='image.png')";
882 | }
883 | ```
884 |
885 | If you are working with other types, such as variables, there is a built in
886 | function that let's you unquote any value. It is called `e`.
887 |
888 | ```less
889 | @color: "red";
890 | .class {
891 | color: e(@color);
892 | }
893 | ```
894 |
895 | ### Built In Functions
896 |
897 | **lessphp** has a collection of built in functions:
898 |
899 | * `e(str)` -- returns a string without the surrounding quotes.
900 | See [String Unquoting](#string_unquoting)
901 |
902 | * `floor(number)` -- returns the floor of a numerical input
903 | * `round(number, [precision])` -- returns the rounded value of numerical input with optional precision
904 |
905 | * `lighten(color, percent)` -- lightens `color` by `percent` and returns it
906 | * `darken(color, percent)` -- darkens `color` by `percent` and returns it
907 |
908 | * `saturate(color, percent)` -- saturates `color` by `percent` and returns it
909 | * `desaturate(color, percent)` -- desaturates `color` by `percent` and returns it
910 |
911 | * `fadein(color, percent)` -- makes `color` less transparent by `percent` and returns it
912 | * `fadeout(color, percent)` -- makes `color` more transparent by `percent` and returns it
913 |
914 | * `spin(color, amount)` -- returns a color with `amount` degrees added to hue
915 |
916 | * `fade(color, amount)` -- returns a color with the alpha set to `amount`
917 |
918 | * `hue(color)` -- returns the hue of `color`
919 |
920 | * `saturation(color)` -- returns the saturation of `color`
921 |
922 | * `lightness(color)` -- returns the lightness of `color`
923 |
924 | * `alpha(color)` -- returns the alpha value of `color` or 1.0 if it doesn't have an alpha
925 |
926 | * `percentage(number)` -- converts a floating point number to a percentage, e.g. `0.65` -> `65%`
927 |
928 | * `mix(color1, color1, percent)` -- mixes two colors by percentage where 100%
929 | keeps all of `color1`, and 0% keeps all of `color2`. Will take into account
930 | the alpha of the colors if it exists. See
931 | .
932 |
933 | * `contrast(color, dark, light)` -- if `color` has a lightness value greater
934 | than 50% then `dark` is returned, otherwise return `light`.
935 |
936 | * `extract(list, index)` -- returns the `index`th item from `list`. The list is
937 | `1` indexed, meaning the first item's index is 1, the second is 2, and etc.
938 |
939 | * `pow(base, exp)` -- returns `base` raised to the power of `exp`
940 |
941 | * `pi()` -- returns pi
942 |
943 | * `mod(a,b)` -- returns `a` modulus `b`
944 |
945 | * `tan(a)` -- returns tangent of `a` where `a` is in radians
946 |
947 | * `cos(a)` -- returns cosine of `a` where `a` is in radians
948 |
949 | * `sin(a)` -- returns sine of `a` where `a` is in radians
950 |
951 | * `atan(a)` -- returns arc tangent of `a`
952 |
953 | * `acos(a)` -- returns arc cosine of `a`
954 |
955 | * `asin(a)` -- returns arc sine of `a`
956 |
957 | * `sqrt(a)` -- returns square root of `a`
958 |
959 | * `rgbahex(color)` -- returns a string containing 4 part hex color.
960 |
961 | This is used to convert a CSS color into the hex format that IE's filter
962 | method expects when working with an alpha component.
963 |
964 | ```less
965 | .class {
966 | @start: rgbahex(rgba(25, 34, 23, .5));
967 | @end: rgbahex(rgba(85, 74, 103, .6));
968 | // abridged example
969 | -ms-filter:
970 | e("gradient(start=@{start},end=@{end})");
971 | }
972 | ```
973 |
974 | ## PHP Interface
975 |
976 | When working with **lessphp** from PHP, the typical flow is to create a new
977 | instance of `lessc`, configure it how you like, then tell it to compile
978 | something using one built in compile methods.
979 |
980 | Methods:
981 |
982 | * [`compile($string)`](#compiling[) -- Compile a string
983 |
984 | * [`compileFile($inFile, [$outFile])`](#compiling) -- Compile a file to another or return it
985 |
986 | * [`checkedCompile($inFile, $outFile)`](#compiling) -- Compile a file only if it's newer
987 |
988 | * [`cachedCompile($cacheOrFile, [$force])`](#compiling_automatically) -- Conditionally compile while tracking imports
989 |
990 | * [`setFormatter($formatterName)`](#output_formatting) -- Change how CSS output looks
991 |
992 | * [`setPreserveComments($keepComments)`](#preserving_comments) -- Change if comments are kept in output
993 |
994 | * [`registerFunction($name, $callable)`](#custom_functions) -- Add a custom function
995 |
996 | * [`unregisterFunction($name)`](#custom_functions) -- Remove a registered function
997 |
998 | * [`setVariables($vars)`](#setting_variables_from_php) -- Set a variable from PHP
999 |
1000 | * [`unsetVariable($name)`](#setting_variables_from_php) -- Remove a PHP variable
1001 |
1002 | * [`setImportDir($dirs)`](#import_directory) -- Set the search path for imports
1003 |
1004 | * [`addImportDir($dir)`](#import_directory) -- Append directory to search path for imports
1005 |
1006 |
1007 | ### Compiling
1008 |
1009 | The `compile` method compiles a string of LESS code to CSS.
1010 |
1011 | ```php
1012 | compile(".block { padding: 3 + 4px }");
1017 | ```
1018 |
1019 | The `compileFile` method reads and compiles a file. It will either return the
1020 | result or write it to the path specified by an optional second argument.
1021 |
1022 | ```php
1023 | echo $less->compileFile("input.less");
1024 | ```
1025 |
1026 | The `checkedCompile` method is like `compileFile`, but it only compiles if the output
1027 | file doesn't exist or it's older than the input file:
1028 |
1029 | ```php
1030 | $less->checkedCompile("input.less", "output.css");
1031 | ```
1032 |
1033 | See [Compiling Automatically](#compiling_automatically) for a description of
1034 | the more advanced `cachedCompile` method.
1035 |
1036 | ### Output Formatting
1037 |
1038 | Output formatting controls the indentation of the output CSS. Besides the
1039 | default formatter, two additional ones are included and it's also easy to make
1040 | your own.
1041 |
1042 | To use a formatter, the method `setFormatter` is used. Just
1043 | pass the name of the formatter:
1044 |
1045 | ```php
1046 | $less = new lessc;
1047 |
1048 | $less->setFormatter("compressed");
1049 | echo $less->compile("div { color: lighten(blue, 10%) }");
1050 | ```
1051 |
1052 | In this example, the `compressed` formatter is used. The formatters are:
1053 |
1054 | * `lessjs` *(default)* -- Same style used in LESS for JavaScript
1055 |
1056 | * `compressed` -- Compresses all the unrequired whitespace
1057 |
1058 | * `classic` -- **lessphp**'s original formatter
1059 |
1060 | To revert to the default formatter, call `setFormatter` with a value of `null`.
1061 |
1062 | #### Custom Formatter
1063 |
1064 | The easiest way to customize the formatter is to create your own instance of an
1065 | existing formatter and alter its public properties before passing it off to
1066 | **lessphp**. The `setFormatter` method can also take an instance of a
1067 | formatter.
1068 |
1069 | Each of the formatter names corresponds to a class with `lessc_formatter_`
1070 | prepended in front of it. Here the classic formatter is customized to use tabs
1071 | instead of spaces:
1072 |
1073 |
1074 | ```php
1075 | $formatter = new lessc_formatter_classic;
1076 | $formatter->indentChar = "\t";
1077 |
1078 | $less = new lessc;
1079 | $less->setFormatter($formatter);
1080 | echo $less->compileFile("myfile.less");
1081 | ```
1082 |
1083 | For more information about what can be configured with the formatter consult
1084 | the source code.
1085 |
1086 | ### Preserving Comments
1087 |
1088 | By default, all comments in the source LESS file are stripped when compiling.
1089 | You might want to keep the `/* */` comments in the output though. For
1090 | example, bundling a license in the file.
1091 |
1092 | Enable or disable comment preservation by calling `setPreserveComments`:
1093 |
1094 | ```php
1095 | $less = new lessc;
1096 | $less->setPreserveComments(true);
1097 | echo $less->compile("/* hello! */");
1098 | ```
1099 |
1100 | Comments are disabled by default because there is additional overhead, and more
1101 | often than not they aren't needed.
1102 |
1103 |
1104 | ### Compiling Automatically
1105 |
1106 | Often, you want to only compile a LESS file only if it has been modified since
1107 | last compile. This is very important because compiling is performance intensive
1108 | and you should avoid a recompile if it possible.
1109 |
1110 | The `checkedCompile` compile method will do just that. It will check if the
1111 | input file is newer than the output file, or if the output file doesn't exist
1112 | yet, and compile only then.
1113 |
1114 | ```php
1115 | $less->checkedCompile("input.less", "output.css");
1116 | ```
1117 |
1118 | There's a problem though. `checkedCompile` is very basic, it only checks the
1119 | input file's modification time. It is unaware of any files from `@import`.
1120 |
1121 |
1122 | For this reason we also have `cachedCompile`. It's slightly more complex, but
1123 | gives us the ability to check changes to all files including those imported. It
1124 | takes one argument, either the name of the file we want to compile, or an
1125 | existing *cache object*. Its return value is an updated cache object.
1126 |
1127 | If we don't have a cache object, then we call the function with the name of the
1128 | file to get the initial cache object. If we do have a cache object, then we
1129 | call the function with it. In both cases, an updated cache object is returned.
1130 |
1131 | The cache object keeps track of all the files that must be checked in order to
1132 | determine if a rebuild is required.
1133 |
1134 | The cache object is a plain PHP `array`. It stores the last time it compiled in
1135 | `$cache["updated"]` and output of the compile in `$cache["compiled"]`.
1136 |
1137 | Here we demonstrate creating an new cache object, then using it to see if we
1138 | have a recompiled version available to be written:
1139 |
1140 |
1141 | ```php
1142 | $inputFile = "myfile.less";
1143 | $outputFile = "myfile.css";
1144 |
1145 | $less = new lessc;
1146 |
1147 | // create a new cache object, and compile
1148 | $cache = $less->cachedCompile($inputFile);
1149 |
1150 | file_put_contents($outputFile, $cache["compiled"]);
1151 |
1152 | // the next time we run, write only if it has updated
1153 | $last_updated = $cache["updated"];
1154 | $cache = $less->cachedCompile($cache);
1155 | if ($cache["updated"] > $last_updated) {
1156 | file_put_contents($outputFile, $cache["compiled"]);
1157 | }
1158 |
1159 | ```
1160 |
1161 | In order for the system to fully work, we must save cache object between
1162 | requests. Because it's a plain PHP `array`, it's sufficient to
1163 | [`serialize`](http://php.net/serialize) it and save it the string somewhere
1164 | like a file or in persistent memory.
1165 |
1166 | An example with saving cache object to a file:
1167 |
1168 | ```php
1169 | function autoCompileLess($inputFile, $outputFile) {
1170 | // load the cache
1171 | $cacheFile = $inputFile.".cache";
1172 |
1173 | if (file_exists($cacheFile)) {
1174 | $cache = unserialize(file_get_contents($cacheFile));
1175 | } else {
1176 | $cache = $inputFile;
1177 | }
1178 |
1179 | $less = new lessc;
1180 | $newCache = $less->cachedCompile($cache);
1181 |
1182 | if (!is_array($cache) || $newCache["updated"] > $cache["updated"]) {
1183 | file_put_contents($cacheFile, serialize($newCache));
1184 | file_put_contents($outputFile, $newCache['compiled']);
1185 | }
1186 | }
1187 |
1188 | autoCompileLess('myfile.less', 'myfile.css');
1189 | ```
1190 |
1191 | `cachedCompile` method takes an optional second argument, `$force`. Passing in
1192 | true will cause the input to always be recompiled.
1193 |
1194 | ### Error Handling
1195 |
1196 | All of the compile methods will throw an `Exception` if the parsing fails or
1197 | there is a compile time error. Compile time errors include things like passing
1198 | incorrectly typed values for functions that expect specific things, like the
1199 | color manipulation functions.
1200 |
1201 | ```php
1202 | $less = new lessc;
1203 | try {
1204 | $less->compile("} invalid LESS }}}");
1205 | } catch (Exception $ex) {
1206 | echo "lessphp fatal error: ".$ex->getMessage();
1207 | }
1208 | ```
1209 | ### Setting Variables From PHP
1210 |
1211 | Before compiling any code you can set initial LESS variables from PHP. The
1212 | `setVariables` method lets us do this. It takes an associative array of names
1213 | to values. The values must be strings, and will be parsed into correct CSS
1214 | values.
1215 |
1216 |
1217 | ```php
1218 | $less = new lessc;
1219 |
1220 | $less->setVariables(array(
1221 | "color" => "red",
1222 | "base" => "960px"
1223 | ));
1224 |
1225 | echo $less->compile(".magic { color: @color; width: @base - 200; }");
1226 | ```
1227 |
1228 | If you need to unset a variable, the `unsetVariable` method is available. It
1229 | takes the name of the variable to unset.
1230 |
1231 | ```php
1232 | $less->unsetVariable("color");
1233 | ```
1234 |
1235 | Be aware that the value of the variable is a string containing a CSS value. So
1236 | if you want to pass a LESS string in, you're going to need two sets of quotes.
1237 | One for PHP and one for LESS.
1238 |
1239 |
1240 | ```php
1241 | $less->setVariables(array(
1242 | "url" => "'http://example.com'"
1243 | ));
1244 |
1245 | echo $less->compile("body { background: url("@{url}/bg.png"); }");
1246 | ```
1247 |
1248 | ### Import Directory
1249 |
1250 | When running the `@import` directive, an array of directories called the import
1251 | search path is searched through to find the file being asked for.
1252 |
1253 | By default, when using `compile`, the import search path just contains `""`,
1254 | which is equivalent to the current directory of the script. If `compileFile` is
1255 | used, then the directory of the file being compiled is used as the starting
1256 | import search path.
1257 |
1258 | Two methods are available for configuring the search path.
1259 |
1260 | `setImportDir` will overwrite the search path with its argument. If the value
1261 | isn't an array it will be converted to one.
1262 |
1263 |
1264 | In this example, `@import "colors";` will look for either
1265 | `assets/less/colors.less` or `assets/bootstrap/colors.less` in that order:
1266 |
1267 | ```php
1268 | $less->setImportDir(array("assets/less/", "assets/bootstrap"));
1269 |
1270 | echo $less->compile('@import "colors";');
1271 | ```
1272 |
1273 | `addImportDir` will append a single path to the import search path instead of
1274 | overwriting the whole thing.
1275 |
1276 | ```php
1277 | $less->addImportDir("public/stylesheets");
1278 | ```
1279 |
1280 | ### Custom Functions
1281 |
1282 | **lessphp** has a simple extension interface where you can implement user
1283 | functions that will be exposed in LESS code during the compile. They can be a
1284 | little tricky though because you need to work with the **lessphp** type system.
1285 |
1286 | The two methods we are interested in are `registerFunction` and
1287 | `unregisterFunction`. `registerFunction` takes two arguments, a name and a
1288 | callable value. `unregisterFunction` just takes the name of an existing
1289 | function to remove.
1290 |
1291 | Here's an example that adds a function called `double` that doubles any numeric
1292 | argument:
1293 |
1294 | ```php
1295 | registerFunction("double", "lessphp_double");
1305 |
1306 | // gives us a width of 800px
1307 | echo $less->compile("div { width: double(400px); }");
1308 | ```
1309 |
1310 | The second argument to `registerFunction` is any *callable value* that is
1311 | understood by [`call_user_func`](http://php.net/call_user_func).
1312 |
1313 | If we are using PHP 5.3 or above then we are free to pass a function literal
1314 | like so:
1315 |
1316 | ```php
1317 | $less->registerFunction("double", function($arg) {
1318 | list($type, $value, $unit) = $arg;
1319 | return array($type, $value*2, $unit);
1320 | });
1321 | ```
1322 |
1323 | Now let's talk about the `double` function itself.
1324 |
1325 | Although a little verbose, the implementation gives us some insight on the type
1326 | system. All values in **lessphp** are stored in an array where the 0th element
1327 | is a string representing the type, and the other elements make up the
1328 | associated data for that value.
1329 |
1330 | The best way to get an understanding of the system is to register is dummy
1331 | function which does a `var_dump` on the argument. Try passing the function
1332 | different values from LESS and see what the results are.
1333 |
1334 | The return value of the registered function must also be a **lessphp** type,
1335 | but if it is a string or numeric value, it will automatically be coerced into
1336 | an appropriate typed value. In our example, we reconstruct the value with our
1337 | modifications while making sure that we preserve the original type.
1338 |
1339 | The instance of **lessphp** itself is sent to the registered function as the
1340 | second argument in addition to the arguments array.
1341 |
1342 | ## Command Line Interface
1343 |
1344 | **lessphp** comes with a command line script written in PHP that can be used to
1345 | invoke the compiler from the terminal. On Linux and OSX, all you need to do is
1346 | place `plessc` and `lessc.inc.php` somewhere in your PATH (or you can run it in
1347 | the current directory as well). On windows you'll need a copy of `php.exe` to
1348 | run the file. To compile a file, `input.less` to CSS, run:
1349 |
1350 | ```bash
1351 | $ plessc input.less
1352 | ```
1353 |
1354 | To write to a file, redirect standard out:
1355 |
1356 | ```bash
1357 | $ plessc input.less > output.css
1358 | ```
1359 |
1360 | To compile code directly on the command line:
1361 |
1362 | ```bash
1363 | $ plessc -r "@color: red; body { color: @color; }"
1364 | ```
1365 |
1366 | To watch a file for changes, and compile it as needed, use the `-w` flag:
1367 |
1368 | ```bash
1369 | $ plessc -w input-file output-file
1370 | ```
1371 |
1372 | Errors from watch mode are written to standard out.
1373 |
1374 |
1375 | ## License
1376 |
1377 | Copyright (c) 2012 Leaf Corcoran,
1378 |
1379 | Permission is hereby granted, free of charge, to any person obtaining
1380 | a copy of this software and associated documentation files (the
1381 | "Software"), to deal in the Software without restriction, including
1382 | without limitation the rights to use, copy, modify, merge, publish,
1383 | distribute, sublicense, and/or sell copies of the Software, and to
1384 | permit persons to whom the Software is furnished to do so, subject to
1385 | the following conditions:
1386 |
1387 | The above copyright notice and this permission notice shall be
1388 | included in all copies or substantial portions of the Software.
1389 |
1390 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1391 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1392 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1393 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
1394 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
1395 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
1396 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1397 |
1398 |
1399 | *Also under GPL3 if required, see `LICENSE` file*
1400 |
1401 |
--------------------------------------------------------------------------------
/lessify:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | parse();
19 | } catch (exception $e) {
20 | exit("Fatal error: ".$e->getMessage()."\n");
21 | }
22 |
--------------------------------------------------------------------------------
/lessify.inc.php:
--------------------------------------------------------------------------------
1 |
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 | public $buffer;
25 | public $count;
26 |
27 | public function __construct($str) {
28 | $this->count = 0;
29 | $this->buffer = trim($str);
30 | }
31 |
32 | public function seek($where = null) {
33 | if ($where === null) {
34 | return $this->count;
35 | }
36 | $this->count = $where;
37 | return true;
38 | }
39 |
40 | public function preg_quote($what) {
41 | return preg_quote($what, '/');
42 | }
43 |
44 | public function match($regex, &$out, $eatWhitespace = true) {
45 | $r = '/'.$regex.($eatWhitespace ? '\s*' : '').'/Ais';
46 | if (preg_match($r, $this->buffer, $out, null, $this->count)) {
47 | $this->count += strlen($out[0]);
48 | return true;
49 | }
50 | return false;
51 | }
52 |
53 | public function literal($what, $eatWhitespace = true) {
54 | // this is here mainly prevent notice from { } string accessor
55 | if ($this->count >= strlen($this->buffer)) return false;
56 |
57 | // shortcut on single letter
58 | if (!$eatWhitespace and strlen($what) === 1) {
59 | if ($this->buffer{$this->count} == $what) {
60 | $this->count++;
61 | return true;
62 | }
63 | return false;
64 | }
65 |
66 | return $this->match($this->preg_quote($what), $m, $eatWhitespace);
67 | }
68 |
69 | }
70 |
71 | class tagparse extends easyparse {
72 | static private $combinators = null;
73 | static private $match_opts = null;
74 |
75 | public function parse() {
76 | if (empty(self::$combinators)) {
77 | self::$combinators = '(' . implode('|', array_map(array($this, 'preg_quote'),
78 | array('+', '>', '~'))).')';
79 | self::$match_opts = '(' . implode('|', array_map(array($this, 'preg_quote'),
80 | array('=', '~=', '|=', '$=', '*='))) . ')';
81 | }
82 |
83 | // crush whitespace
84 | $this->buffer = preg_replace('/\s+/', ' ', $this->buffer) . ' ';
85 |
86 | $tags = array();
87 | while ($this->tag($t)) {
88 | $tags[] = $t;
89 | }
90 |
91 | return $tags;
92 | }
93 |
94 | public static function compileString($string) {
95 | list(, $delim, $str) = $string;
96 | $str = str_replace($delim, "\\" . $delim, $str);
97 | $str = str_replace("\n", "\\\n", $str);
98 | return $delim . $str . $delim;
99 | }
100 |
101 | public static function compilePaths($paths) {
102 | return implode(', ', array_map(array('self', 'compilePath'), $paths));
103 | }
104 |
105 | // array of tags
106 | public static function compilePath($path) {
107 | return implode(' ', array_map(array('self', 'compileTag'), $path));
108 | }
109 |
110 |
111 | public static function compileTag($tag) {
112 | ob_start();
113 | if (isset($tag['comb'])) echo $tag['comb'] . " ";
114 | if (isset($tag['front'])) echo $tag['front'];
115 | if (isset($tag['attr'])) {
116 | echo '[' . $tag['attr'];
117 | if (isset($tag['op'])) {
118 | echo $tag['op'] . $tag['op_value'];
119 | }
120 | echo ']';
121 | }
122 | return ob_get_clean();
123 | }
124 |
125 | public function string(&$out) {
126 | $s = $this->seek();
127 |
128 | if ($this->literal('"')) {
129 | $delim = '"';
130 | } elseif ($this->literal("'")) {
131 | $delim = "'";
132 | } else {
133 | return false;
134 | }
135 |
136 | while (true) {
137 | // step through letters looking for either end or escape
138 | $buff = "";
139 | $escapeNext = false;
140 | $finished = false;
141 | for ($i = $this->count; $i < strlen($this->buffer); $i++) {
142 | $char = $this->buffer[$i];
143 | switch ($char) {
144 | case $delim:
145 | if ($escapeNext) {
146 | $buff .= $char;
147 | $escapeNext = false;
148 | break;
149 | }
150 | $finished = true;
151 | break 2;
152 | case "\\":
153 | if ($escapeNext) {
154 | $buff .= $char;
155 | $escapeNext = false;
156 | } else {
157 | $escapeNext = true;
158 | }
159 | break;
160 | case "\n":
161 | if (!$escapeNext) {
162 | break 3;
163 | }
164 |
165 | $buff .= $char;
166 | $escapeNext = false;
167 | break;
168 | default:
169 | if ($escapeNext) {
170 | $buff .= "\\";
171 | $escapeNext = false;
172 | }
173 | $buff .= $char;
174 | }
175 | }
176 | if (!$finished) break;
177 | $out = array('string', $delim, $buff);
178 | $this->seek($i+1);
179 | return true;
180 | }
181 |
182 | $this->seek($s);
183 | return false;
184 | }
185 |
186 | public function tag(&$out) {
187 | $s = $this->seek();
188 | $tag = array();
189 | if ($this->combinator($op)) $tag['comb'] = $op;
190 |
191 | if (!$this->match('(.*?)( |$|\[|'.self::$combinators.')', $match)) {
192 | $this->seek($s);
193 | return false;
194 | }
195 |
196 | if (!empty($match[3])) {
197 | // give back combinator
198 | $this->count-=strlen($match[3]);
199 | }
200 |
201 | if (!empty($match[1])) $tag['front'] = $match[1];
202 |
203 | if ($match[2] == '[') {
204 | if ($this->ident($i)) {
205 | $tag['attr'] = $i;
206 |
207 | if ($this->match(self::$match_opts, $m) && $this->value($v)) {
208 | $tag['op'] = $m[1];
209 | $tag['op_value'] = $v;
210 | }
211 |
212 | if ($this->literal(']')) {
213 | $out = $tag;
214 | return true;
215 | }
216 | }
217 | } elseif (isset($tag['front'])) {
218 | $out = $tag;
219 | return true;
220 | }
221 |
222 | $this->seek($s);
223 | return false;
224 | }
225 |
226 | public function ident(&$out) {
227 | // [-]?{nmstart}{nmchar}*
228 | // nmstart: [_a-z]|{nonascii}|{escape}
229 | // nmchar: [_a-z0-9-]|{nonascii}|{escape}
230 | if ($this->match('(-?[_a-z][_\w]*)', $m)) {
231 | $out = $m[1];
232 | return true;
233 | }
234 | return false;
235 | }
236 |
237 | public function value(&$out) {
238 | if ($this->string($str)) {
239 | $out = $this->compileString($str);
240 | return true;
241 | } elseif ($this->ident($id)) {
242 | $out = $id;
243 | return true;
244 | }
245 | return false;
246 | }
247 |
248 |
249 | public function combinator(&$op) {
250 | if ($this->match(self::$combinators, $m)) {
251 | $op = $m[1];
252 | return true;
253 | }
254 | return false;
255 | }
256 | }
257 |
258 | class nodecounter {
259 | public $count = 0;
260 | public $children = array();
261 |
262 | public $name;
263 | public $child_blocks;
264 | public $the_block;
265 |
266 | public function __construct($name) {
267 | $this->name = $name;
268 | }
269 |
270 | public function dump($stack = null) {
271 | if (is_null($stack)) $stack = array();
272 | $stack[] = $this->getName();
273 | echo implode(' -> ', $stack) . " ($this->count)\n";
274 | foreach ($this->children as $child) {
275 | $child->dump($stack);
276 | }
277 | }
278 |
279 | public static function compileProperties($c, $block) {
280 | foreach ($block as $name => $value) {
281 | if ($c->isProperty($name, $value)) {
282 | echo $c->compileProperty($name, $value) . "\n";
283 | }
284 | }
285 | }
286 |
287 | public function compile($c, $path = null) {
288 | if (is_null($path)) $path = array();
289 | $path[] = $this->name;
290 |
291 | $isVisible = !is_null($this->the_block) || !is_null($this->child_blocks);
292 |
293 | if ($isVisible) {
294 | echo $c->indent(implode(' ', $path) . ' {');
295 | $c->indentLevel++;
296 | $path = array();
297 |
298 | if ($this->the_block) {
299 | $this->compileProperties($c, $this->the_block);
300 | }
301 |
302 | if ($this->child_blocks) {
303 | foreach ($this->child_blocks as $block) {
304 | echo $c->indent(tagparse::compilePaths($block['__tags']).' {');
305 | $c->indentLevel++;
306 | $this->compileProperties($c, $block);
307 | $c->indentLevel--;
308 | echo $c->indent('}');
309 | }
310 | }
311 | }
312 |
313 | // compile child nodes
314 | foreach ($this->children as $node) {
315 | $node->compile($c, $path);
316 | }
317 |
318 | if ($isVisible) {
319 | $c->indentLevel--;
320 | echo $c->indent('}');
321 | }
322 |
323 | }
324 |
325 | public function getName() {
326 | if (is_null($this->name)) return "[root]";
327 | else return $this->name;
328 | }
329 |
330 | public function getNode($name) {
331 | if (!isset($this->children[$name])) {
332 | $this->children[$name] = new nodecounter($name);
333 | }
334 |
335 | return $this->children[$name];
336 | }
337 |
338 | public function findNode($path) {
339 | $current = $this;
340 | for ($i = 0; $i < count($path); $i++) {
341 | $t = tagparse::compileTag($path[$i]);
342 | $current = $current->getNode($t);
343 | }
344 |
345 | return $current;
346 | }
347 |
348 | public function addBlock($path, $block) {
349 | $node = $this->findNode($path);
350 | if (!is_null($node->the_block)) throw new exception("can this happen?");
351 |
352 | unset($block['__tags']);
353 | $node->the_block = $block;
354 | }
355 |
356 | public function addToNode($path, $block) {
357 | $node = $this->findNode($path);
358 | $node->child_blocks[] = $block;
359 | }
360 | }
361 |
362 | /**
363 | * create a less file from a css file by combining blocks where appropriate
364 | */
365 | class lessify extends lessc {
366 | public function dump() {
367 | print_r($this->env);
368 | }
369 |
370 | public function parse($str = null) {
371 | $this->prepareParser($str ? $str : $this->buffer);
372 | while (false !== $this->parseChunk());
373 |
374 | $root = new nodecounter(null);
375 |
376 | // attempt to preserve some of the block order
377 | $order = array();
378 |
379 | $visitedTags = array();
380 | foreach (end($this->env) as $name => $block) {
381 | if (!$this->isBlock($name, $block)) continue;
382 | if (isset($visitedTags[$name])) continue;
383 |
384 | foreach ($block['__tags'] as $t) {
385 | $visitedTags[$t] = true;
386 | }
387 |
388 | // skip those with more than 1
389 | if (count($block['__tags']) == 1) {
390 | $p = new tagparse(end($block['__tags']));
391 | $path = $p->parse();
392 | $root->addBlock($path, $block);
393 | $order[] = array('compressed', $path, $block);
394 | continue;
395 | } else {
396 | $common = null;
397 | $paths = array();
398 | foreach ($block['__tags'] as $rawtag) {
399 | $p = new tagparse($rawtag);
400 | $paths[] = $path = $p->parse();
401 | if (is_null($common)) $common = $path;
402 | else {
403 | $new_common = array();
404 | foreach ($path as $tag) {
405 | $head = array_shift($common);
406 | if ($tag == $head) {
407 | $new_common[] = $head;
408 | } else break;
409 | }
410 | $common = $new_common;
411 | if (empty($common)) {
412 | // nothing in common
413 | break;
414 | }
415 | }
416 | }
417 |
418 | if (!empty($common)) {
419 | $new_paths = array();
420 | foreach ($paths as $p) $new_paths[] = array_slice($p, count($common));
421 | $block['__tags'] = $new_paths;
422 | $root->addToNode($common, $block);
423 | $order[] = array('compressed', $common, $block);
424 | continue;
425 | }
426 |
427 | }
428 |
429 | $order[] = array('none', $block['__tags'], $block);
430 | }
431 |
432 |
433 | $compressed = $root->children;
434 | foreach ($order as $item) {
435 | list($type, $tags, $block) = $item;
436 | if ($type == 'compressed') {
437 | $top = tagparse::compileTag(reset($tags));
438 | if (isset($compressed[$top])) {
439 | $compressed[$top]->compile($this);
440 | unset($compressed[$top]);
441 | }
442 | } else {
443 | echo $this->indent(implode(', ', $tags).' {');
444 | $this->indentLevel++;
445 | nodecounter::compileProperties($this, $block);
446 | $this->indentLevel--;
447 | echo $this->indent('}');
448 | }
449 | }
450 | }
451 | }
452 |
--------------------------------------------------------------------------------
/package.sh:
--------------------------------------------------------------------------------
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 |
23 |
24 | echo
25 | echo "Don't forget to"
26 | echo "* Update the version in lessc.inc.php (two places)"
27 | echo "* Update the version in the README.md"
28 | echo "* Update the version in docs.md (two places)"
29 | echo "* Update the version in LICENSE"
30 | echo "* Update @current_version in site.moon"
31 | echo "* Add entry to feed.moon for changelog"
32 | echo "* Update the -New- area on homepage with date and features"
33 | echo
34 |
35 |
36 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ./tests
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/plessc:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | , 2013
5 |
6 | $exe = array_shift($argv); // remove filename
7 |
8 | $HELP = << 0 && preg_match('/^-([-hvrwncXT]$|[f]=)/', $argv[0])) {
27 | array_shift($argv);
28 | }
29 |
30 | function has() {
31 | global $opts;
32 | foreach (func_get_args() as $arg) {
33 | if (isset($opts[$arg])) return true;
34 | }
35 | return false;
36 | }
37 |
38 | if (has("h", "help")) {
39 | exit($HELP);
40 | }
41 |
42 | error_reporting(E_ALL);
43 | $path = realpath(dirname(__FILE__)).'/';
44 |
45 | require $path."lessc.inc.php";
46 |
47 | $VERSION = lessc::$VERSION;
48 |
49 | $fa = "Fatal Error: ";
50 | function err($msg) {
51 | fwrite(STDERR, $msg."\n");
52 | }
53 |
54 | if (php_sapi_name() != "cli") {
55 | err($fa.$argv[0]." must be run in the command line.");
56 | exit(1);
57 | }
58 |
59 | function make_less($fname = null) {
60 | global $opts;
61 | $l = new lessc($fname);
62 |
63 | if (has("f")) {
64 | $format = $opts["f"];
65 | if ($format != "default") $l->setFormatter($format);
66 | }
67 |
68 | if (has("c")) {
69 | $l->setPreserveComments(true);
70 | }
71 |
72 | return $l;
73 | }
74 |
75 | function process($data, $import = null) {
76 | global $fa;
77 |
78 | $l = make_less();
79 | if ($import) $l->importDir = $import;
80 |
81 | try {
82 | echo $l->parse($data);
83 | exit(0);
84 | } catch (exception $ex) {
85 | err($fa."\n".str_repeat('=', 20)."\n".
86 | $ex->getMessage());
87 | exit(1);
88 | }
89 | }
90 |
91 | if (has("v")) {
92 | exit($VERSION."\n");
93 | }
94 |
95 | if (has("r")) {
96 | if (!empty($argv)) {
97 | $data = $argv[0];
98 | } else {
99 | $data = "";
100 | while (!feof(STDIN)) {
101 | $data .= fread(STDIN, 8192);
102 | }
103 | }
104 | exit(process($data));
105 | }
106 |
107 | if (has("w")) {
108 | // need two files
109 | if (!is_file($in = array_shift($argv)) ||
110 | null == $out = array_shift($argv))
111 | {
112 | err($fa.$exe." -w infile outfile");
113 | exit(1);
114 | }
115 |
116 | echo "Watching ".$in.
117 | (has("n") ? ' with notifications' : '').
118 | ", press Ctrl + c to exit.\n";
119 |
120 | $cache = $in;
121 | $last_action = 0;
122 | while (true) {
123 | clearstatcache();
124 |
125 | // check if anything has changed since last fail
126 | $updated = false;
127 | if (is_array($cache)) {
128 | foreach ($cache['files'] as $fname=>$_) {
129 | if (filemtime($fname) > $last_action) {
130 | $updated = true;
131 | break;
132 | }
133 | }
134 | } else $updated = true;
135 |
136 | // try to compile it
137 | if ($updated) {
138 | $last_action = time();
139 |
140 | try {
141 | $cache = lessc::cexecute($cache);
142 | echo "Writing updated file: ".$out."\n";
143 | if (!file_put_contents($out, $cache['compiled'])) {
144 | err($fa."Could not write to file ".$out);
145 | exit(1);
146 | }
147 | } catch (exception $ex) {
148 | echo "\nFatal Error:\n".str_repeat('=', 20)."\n".
149 | $ex->getMessage()."\n\n";
150 |
151 | if (has("n")) {
152 | `notify-send -u critical "compile failed" "{$ex->getMessage()}"`;
153 | }
154 | }
155 | }
156 |
157 | sleep(1);
158 | }
159 | exit(0);
160 | }
161 |
162 | if (!$fname = array_shift($argv)) {
163 | echo $HELP;
164 | exit(1);
165 | }
166 |
167 | function dumpValue($node, $depth = 0) {
168 | if (is_object($node)) {
169 | $indent = str_repeat(" ", $depth);
170 | $out = array();
171 | foreach ($node->props as $prop) {
172 | $out[] = $indent . dumpValue($prop, $depth + 1);
173 | }
174 | $out = implode("\n", $out);
175 | if (!empty($node->tags)) {
176 | $out = "+ ".implode(", ", $node->tags)."\n".$out;
177 | }
178 | return $out;
179 | } elseif (is_array($node)) {
180 | if (empty($node)) return "[]";
181 | $type = $node[0];
182 | if ($type == "block")
183 | return dumpValue($node[1], $depth);
184 |
185 | $out = array();
186 | foreach ($node as $value) {
187 | $out[] = dumpValue($value, $depth);
188 | }
189 | return "{ ".implode(", ", $out)." }";
190 | } else {
191 | if (is_string($node) && preg_match("/[\s,]/", $node)) {
192 | return '"'.$node.'"';
193 | }
194 | return $node; // normal value
195 | }
196 | }
197 |
198 |
199 | function stripValue($o, $toStrip) {
200 | if (is_array($o) || is_object($o)) {
201 | $isObject = is_object($o);
202 | $o = (array)$o;
203 | foreach ($toStrip as $removeKey) {
204 | if (!empty($o[$removeKey])) {
205 | $o[$removeKey] = "*stripped*";
206 | }
207 | }
208 |
209 | foreach ($o as $k => $v) {
210 | $o[$k] = stripValue($v, $toStrip);
211 | }
212 |
213 | if ($isObject) {
214 | $o = (object)$o;
215 | }
216 | }
217 |
218 | return $o;
219 | }
220 |
221 | function dumpWithoutParent($o, $alsoStrip=array()) {
222 | $toStrip = array_merge(array("parent"), $alsoStrip);
223 | print_r(stripValue($o, $toStrip));
224 | }
225 |
226 | try {
227 | $less = make_less($fname);
228 | if (has("T", "X")) {
229 | $parser = new lessc_parser($less, $fname);
230 | $tree = $parser->parse(file_get_contents($fname));
231 | if (has("X"))
232 | $out = print_r($tree, 1);
233 | else
234 | $out = dumpValue($tree)."\n";
235 | } else {
236 | $out = $less->parse();
237 | }
238 |
239 | if (!$fout = array_shift($argv)) {
240 | echo $out;
241 | } else {
242 | file_put_contents($fout, $out);
243 | }
244 |
245 | } catch (exception $ex) {
246 | err($fa.$ex->getMessage());
247 | exit(1);
248 | }
249 |
--------------------------------------------------------------------------------
/tests/ApiTest.php:
--------------------------------------------------------------------------------
1 | less = new lessc();
8 | $this->less->importDir = array(__DIR__ . "/inputs/test-imports");
9 | }
10 |
11 | public function testPreserveComments() {
12 | $input = <<assertEquals($this->compile($input), trim($outputWithoutComments));
86 | $this->less->setPreserveComments(true);
87 | $this->assertEquals($this->compile($input), trim($outputWithComments));
88 | }
89 |
90 | public function testOldInterface() {
91 | $this->less = new lessc(__DIR__ . "/inputs/hi.less");
92 | $out = $this->less->parse(array("hello" => "10px"));
93 | $this->assertEquals(trim($out), trim('
94 | div:before {
95 | content: "hi!";
96 | }'));
97 |
98 | }
99 |
100 | public function testInjectVars() {
101 | $out = $this->less->parse(".magic { color: @color; width: @base - 200; }",
102 | array(
103 | 'color' => 'red',
104 | 'base' => '960px'
105 | ));
106 |
107 | $this->assertEquals(trim($out), trim("
108 | .magic {
109 | color: red;
110 | width: 760px;
111 | }"));
112 |
113 | }
114 |
115 | public function testDisableImport() {
116 | $this->less->importDisabled = true;
117 | $this->assertEquals(
118 | "/* import disabled */",
119 | $this->compile("@import 'file3';"));
120 | }
121 |
122 | public function testUserFunction() {
123 | $this->less->registerFunction("add-two", function($list) {
124 | list($a, $b) = $list[2];
125 | return $a[1] + $b[1];
126 | });
127 |
128 | $this->assertEquals(
129 | $this->compile("result: add-two(10, 20);"),
130 | "result: 30;");
131 |
132 | return $this->less;
133 | }
134 |
135 | /**
136 | * @depends testUserFunction
137 | */
138 | public function testUnregisterFunction($less) {
139 | $less->unregisterFunction("add-two");
140 |
141 | $this->assertEquals(
142 | $this->compile("result: add-two(10, 20);"),
143 | "result: add-two(10,20);");
144 | }
145 |
146 |
147 |
148 | public function testFormatters() {
149 | $src = "
150 | div, pre {
151 | color: blue;
152 | span, .big, hello.world {
153 | height: 20px;
154 | color:#ffffff + #000;
155 | }
156 | }";
157 |
158 | $this->less->setFormatter("compressed");
159 | $this->assertEquals(
160 | $this->compile($src), "div,pre{color:blue;}div span,div .big,div hello.world,pre span,pre .big,pre hello.world{height:20px;color:#fff;}");
161 |
162 | // TODO: fix the output order of tags
163 | $this->less->setFormatter("lessjs");
164 | $this->assertEquals(
165 | $this->compile($src),
166 | "div,
167 | pre {
168 | color: blue;
169 | }
170 | div span,
171 | div .big,
172 | div hello.world,
173 | pre span,
174 | pre .big,
175 | pre hello.world {
176 | height: 20px;
177 | color: #ffffff;
178 | }");
179 |
180 | $this->less->setFormatter("classic");
181 | $this->assertEquals(
182 | $this->compile($src),
183 | trim("div, pre { color:blue; }
184 | div span, div .big, div hello.world, pre span, pre .big, pre hello.world {
185 | height:20px;
186 | color:#ffffff;
187 | }
188 | "));
189 |
190 | }
191 |
192 | public function compile($str) {
193 | return trim($this->less->parse($str));
194 | }
195 |
196 | }
197 |
--------------------------------------------------------------------------------
/tests/ErrorHandlingTest.php:
--------------------------------------------------------------------------------
1 | less = new lessc();
7 | }
8 |
9 | public function compile() {
10 | $source = join("\n", func_get_args());
11 | return $this->less->compile($source);
12 | }
13 |
14 | /**
15 | * @expectedException Exception
16 | * @expectedExceptionMessage .parametric-mixin is undefined
17 | */
18 | public function testRequiredParametersMissing() {
19 | $this->compile(
20 | '.parametric-mixin (@a, @b) { a: @a; b: @b; }',
21 | '.selector { .parametric-mixin(12px); }'
22 | );
23 | }
24 |
25 | /**
26 | * @expectedException Exception
27 | * @expectedExceptionMessage .parametric-mixin is undefined
28 | */
29 | public function testTooManyParameters() {
30 | $this->compile(
31 | '.parametric-mixin (@a, @b) { a: @a; b: @b; }',
32 | '.selector { .parametric-mixin(12px, 13px, 14px); }'
33 | );
34 | }
35 |
36 | /**
37 | * @expectedException Exception
38 | * @expectedExceptionMessage unrecognised input
39 | */
40 | public function testRequiredArgumentsMissing() {
41 | $this->compile('.selector { rule: e(); }');
42 | }
43 |
44 | /**
45 | * @expectedException Exception
46 | * @expectedExceptionMessage variable @missing is undefined
47 | */
48 | public function testVariableMissing() {
49 | $this->compile('.selector { rule: @missing; }');
50 | }
51 |
52 | /**
53 | * @expectedException Exception
54 | * @expectedExceptionMessage .missing-mixin is undefined
55 | */
56 | public function testMixinMissing() {
57 | $this->compile('.selector { .missing-mixin; }');
58 | }
59 |
60 | /**
61 | * @expectedException Exception
62 | * @expectedExceptionMessage .flipped is undefined
63 | */
64 | public function testGuardUnmatchedValue() {
65 | $this->compile(
66 | '.flipped(@x) when (@x =< 10) { rule: value; }',
67 | '.selector { .flipped(12); }'
68 | );
69 | }
70 |
71 | /**
72 | * @expectedException Exception
73 | * @expectedExceptionMessage .colors-only is undefined
74 | */
75 | public function testGuardUnmatchedType() {
76 | $this->compile(
77 | '.colors-only(@x) when (iscolor(@x)) { rule: value; }',
78 | '.selector { .colors-only("string value"); }'
79 | );
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/tests/InputTest.php:
--------------------------------------------------------------------------------
1 | "outputs",
20 | "inputs_lessjs" => "outputs_lessjs",
21 | );
22 |
23 | public function setUp() {
24 | $this->less = new lessc();
25 | $this->less->importDir = array_map(function($path) {
26 | return __DIR__ . "/" . $path;
27 | }, self::$importDirs);
28 | }
29 |
30 | /**
31 | * @dataProvider fileNameProvider
32 | */
33 | public function testInputFile($inFname) {
34 | if ($pattern = getenv("BUILD")) {
35 | return $this->buildInput($inFname);
36 | }
37 |
38 | $outFname = self::outputNameFor($inFname);
39 |
40 | if (!is_readable($outFname)) {
41 | $this->fail("$outFname is missing, ".
42 | "consider building tests with BUILD=true");
43 | }
44 |
45 | $input = file_get_contents($inFname);
46 | $output = file_get_contents($outFname);
47 |
48 | $this->assertEquals($output, $this->less->parse($input));
49 | }
50 |
51 | public function fileNameProvider() {
52 | return array_map(
53 | function($a) { return array($a); },
54 | self::findInputNames()
55 | );
56 | }
57 |
58 | // only run when env is set
59 | public function buildInput($inFname) {
60 | $css = $this->less->parse(file_get_contents($inFname));
61 | file_put_contents(self::outputNameFor($inFname), $css);
62 | }
63 |
64 | public static function findInputNames($pattern="*.less") {
65 | $files = array();
66 | foreach (self::$testDirs as $inputDir => $outputDir) {
67 | $files = array_merge($files, glob(__DIR__ . "/" . $inputDir . "/" . $pattern));
68 | }
69 |
70 | return array_filter($files, "is_file");
71 | }
72 |
73 | public static function outputNameFor($input) {
74 | $front = _quote(__DIR__ . "/");
75 | $out = preg_replace("/^$front/", "", $input);
76 |
77 | foreach (self::$testDirs as $inputDir => $outputDir) {
78 | $in = _quote($inputDir . "/");
79 | $rewritten = preg_replace("/$in/", $outputDir . "/", $out);
80 | if ($rewritten != $out) {
81 | $out = $rewritten;
82 | break;
83 | }
84 | }
85 |
86 | $out = preg_replace("/.less$/", ".css", $out);
87 |
88 | return __DIR__ . "/" . $out;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/tests/README.md:
--------------------------------------------------------------------------------
1 | lessphp uses [PHPUnit](https://github.com/sebastianbergmann/phpunit/) for its tests
2 |
3 | * `InputTest.php` iterates through all the less files in `inputs/`, compiles
4 | them, then compares the result with the respective file in `outputs/`.
5 |
6 | * `ApiTest.php` tests the behavior of lessphp's public API methods.
7 |
8 | * `ErrorHandlingTest.php` tests that lessphp throws appropriate errors when
9 | given invalid LESS as input.
10 |
11 | From the root you can run `make` to run all the tests.
12 |
13 | ## lessjs tests
14 |
15 | Tests found in `inputs_lessjs` are extracted directly from
16 | [less.js](https://github.com/less/less.js). The following license applies to
17 | those tests: https://github.com/less/less.js/blob/master/LICENSE
18 |
19 | ## bootstrap.sh
20 |
21 | Clones twitter bootsrap, compiles it with lessc and lessphp, cleans up results
22 | with sort.php, and outputs diff. To run it, you need to have git and lessc
23 | installed.
24 |
25 |
--------------------------------------------------------------------------------
/tests/bootstrap.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo "This script clones Twitter Bootstrap, compiles it with lessc and lessphp,"
4 | echo "cleans up results with sort.php, and outputs diff. To run it, you need to"
5 | echo "have git and lessc installed."
6 | echo ""
7 |
8 | if [ -z "$input" ]; then
9 | input="bootstrap/less/bootstrap.less"
10 | fi
11 | dest=$(basename "$input")
12 | dest="${dest%.*}"
13 |
14 | if [ -z "$@" ]; then
15 | diff_tool="diff -b -u -t -B"
16 | else
17 | diff_tool=$@
18 | fi
19 |
20 | mkdir -p tmp
21 |
22 | if [ ! -d 'bootstrap/' ]; then
23 | echo ">> Cloning bootstrap to bootstrap/"
24 | git clone https://github.com/twbs/bootstrap
25 | fi
26 |
27 | echo ">> lessc compilation ($input)"
28 | lessc "$input" "tmp/$dest.lessc.css"
29 |
30 | echo ">> lessphp compilation ($input)"
31 | ../plessc "$input" "tmp/$dest.lessphp.css"
32 | echo ">> Cleanup and convert"
33 |
34 | php sort.php "tmp/$dest.lessc.css" > "tmp/$dest.lessc.clean.css"
35 | php sort.php "tmp/$dest.lessphp.css" > "tmp/$dest.lessphp.clean.css"
36 |
37 | echo ">> Doing diff"
38 | $diff_tool "tmp/$dest.lessc.clean.css" "tmp/$dest.lessphp.clean.css"
39 |
--------------------------------------------------------------------------------
/tests/inputs/accessors.less.disable:
--------------------------------------------------------------------------------
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 |
37 |
--------------------------------------------------------------------------------
/tests/inputs/arity.less:
--------------------------------------------------------------------------------
1 |
2 | // simple arity
3 |
4 | .hello(@a) {
5 | hello: one;
6 | }
7 |
8 | .hello(@a, @b) {
9 | hello: two;
10 | }
11 |
12 | .hello(@a, @b, @c) {
13 | hello: three;
14 | }
15 |
16 |
17 | .world(@a, @b, @c) {
18 | world: three;
19 | }
20 |
21 | .world(@a, @b) {
22 | world: two;
23 | }
24 |
25 | .world(@a) {
26 | world: 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 | foo: two @b;
49 | }
50 |
51 | .foo(@a, @b: cool, @c: yeah) {
52 | foo: three @b @c;
53 | }
54 |
55 |
56 | .baz(@a, @b, @c: yeah) {
57 | baz: three @c;
58 | }
59 |
60 | .baz(@a, @b: cool) {
61 | baz: two @b;
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 |
78 |
--------------------------------------------------------------------------------
/tests/inputs/attributes.less:
--------------------------------------------------------------------------------
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 |
42 |
--------------------------------------------------------------------------------
/tests/inputs/builtins.less:
--------------------------------------------------------------------------------
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 | num-basic: @num + 4;
12 | num-floor: floor(@num) + 4px;
13 | num-ceil: ceil(@num) + 4px;
14 |
15 | @num2: 2 / 3;
16 | num2: @num2;
17 | num2-round: round(@num2);
18 | num2-floor: floor(@num2);
19 | num2-ceil: ceil(@num2);
20 |
21 | round-lit: round(10px / 3);
22 |
23 | rgba1: rgbahex(@color);
24 | rgba2: rgbahex(@color2);
25 | argb: argb(@color2);
26 | }
27 |
28 |
29 | format {
30 | @r: 32;
31 | format: %("rgb(%d, %d, %d)", @r, 128, 64);
32 | format-string: %("hello %s", "world");
33 | format-multiple: %("hello %s %d", "earth", 2);
34 | format-url-encode: %('red is %A', #ff0000);
35 | eformat: e(%("rgb(%d, %d, %d)", @r, 128, 64));
36 | }
37 |
38 |
39 | #functions {
40 | str1: isstring("hello");
41 | str2: isstring(one, two);
42 |
43 | num1: isnumber(2323px);
44 | num2: isnumber(2323);
45 | num3: isnumber(4/5);
46 | num4: isnumber("hello");
47 |
48 | col1: iscolor(red);
49 | col2: iscolor(hello);
50 | col3: iscolor(rgba(0,0,0,0.3));
51 | col4: iscolor(#fff);
52 |
53 | key1: iskeyword(hello);
54 | key2: iskeyword(3D);
55 |
56 | px1: ispixel(10px);
57 | px2: ispixel(10);
58 |
59 | per1: ispercentage(10%);
60 | per2: ispercentage(10);
61 |
62 | em1: isem(10em);
63 | em2: isem(10);
64 |
65 | ex1: extract(1 2 3 4, 2);
66 | ex2: extract(1 2, 1);
67 | ex3: extract(1, 1);
68 |
69 | @list: 1,2,3,4;
70 |
71 | ex4: extract(@list, 2);
72 |
73 | pow: pow(2,4);
74 | pi: pi();
75 | mod: mod(14,10);
76 |
77 | tan: tan(1);
78 | cos: cos(1);
79 | sin: sin(1);
80 |
81 | atan: atan(1);
82 | acos: acos(1);
83 | asin: asin(1);
84 |
85 | sqrt: sqrt(8);
86 | }
87 |
88 |
89 | #unit {
90 | @unit: "em";
91 | unit-lit: unit(10px);
92 | unit-arg: unit(10px, "s");
93 | unit-arg2: unit(10px, @unit);
94 | unit-math: unit(0.07407s) * 100%;
95 | }
96 |
97 |
--------------------------------------------------------------------------------
/tests/inputs/colors.less:
--------------------------------------------------------------------------------
1 |
2 | body {
3 | color:rgb(red(#f00), red(#0F0), red(#00f));
4 | color:rgb(red(#f00), green(#0F0), blue(#00f));
5 | color:rgb(red(#0ff), green(#f0f), blue(#ff0));
6 |
7 | color: hsl(34, 50%, 40%);
8 | color: hsla(34, 50%, 40%, 0.3);
9 |
10 | lighten1: lighten(#efefef, 10%);
11 | lighten2: lighten(rgb(23, 53, 231), 22%);
12 | lighten3: lighten(rgba(212, 103, 88, 0.5), 10%);
13 |
14 | darken1: darken(#efefef, 10%);
15 | darken2: darken(rgb(23, 53, 231), 22%);
16 | darken3: darken(rgba(23, 53, 231, 0.5), 10%);
17 |
18 | saturate1: saturate(#efefef, 10%);
19 | saturate2: saturate(rgb(23, 53, 231), 22%);
20 | saturate3: saturate(rgba(23, 53, 231, 0.5), 10%);
21 |
22 | desaturate1: desaturate(#efefef, 10%);
23 | desaturate2: desaturate(rgb(23, 53, 231), 22%);
24 | desaturate3: desaturate(rgba(23, 53, 231, 0.5), 10%);
25 |
26 | spin1: spin(#efefef, 12);
27 | spin2: spin(rgb(23, 53, 231), 15);
28 | spin3: spin(rgba(23, 53, 231, 0.5), 19);
29 |
30 | spin2: spin(#efefef, -12);
31 | spin3: spin(rgb(23, 53, 231), -15);
32 | spin4: spin(rgba(23, 53, 231, 0.5), -19);
33 |
34 | one1: fadein(#abcdef, 10%);
35 | one2: fadeout(#abcdef, -10%);
36 |
37 | two1: fadeout(#029f23, 10%);
38 | two2: fadein(#029f23, -10%);
39 |
40 | tint1: tint(#e0e0e0);
41 | tint2: tint(#e0e0e0, 40%);
42 |
43 | shade1: shade(#e0e0e0);
44 | shade2: shade(#e0e0e0, 40%);
45 |
46 | three1: fadein(rgba(1,2,3, 0.5), 10%);
47 | three2: fadeout(rgba(1,2,3, 0.5), -10%);
48 |
49 | four1: fadeout(rgba(1,2,3, 0), 10%);
50 | four2: fadein(rgba(1,2,3, 0), -10%);
51 |
52 | hue: hue(rgb(34,20,40));
53 | sat: saturation(rgb(34,20,40));
54 | lit: lightness(rgb(34,20,40));
55 |
56 | @old: #34fa03;
57 | @new: hsl(hue(@old), 45%, 90%);
58 | what: @new;
59 |
60 | zero1: saturate(#123456, -100%);
61 | zero2: saturate(#123456, 100%);
62 | zero3: saturate(#000000, 100%);
63 | zero4: saturate(#ffffff, 100%);
64 |
65 | zero5: lighten(#123456, -100%);
66 | zero6: lighten(#123456, 100%);
67 | zero7: lighten(#000000, 100%);
68 | zero8: lighten(#ffffff, 100%);
69 |
70 | zero9: spin(#123456, -100);
71 | zeroa: spin(#123456, 100);
72 | zerob: spin(#000000, 100);
73 | zeroc: spin(#ffffff, 100);
74 | }
75 |
76 |
77 | alpha {
78 | // g: alpha(red);
79 | g1: alpha(rgba(0,0,0,0));
80 | g2: alpha(rgb(155,55,0));
81 | }
82 |
83 | fade {
84 | f1: fade(red, 50%);
85 | f2: fade(#fff, 20%);
86 | f3: fade(rgba(34,23,64,0.4), 50%);
87 | }
88 |
89 | @a: rgb(255,255,255);
90 | @b: rgb(0,0,0);
91 |
92 | .mix {
93 | color1: mix(@a, @b, 50%);
94 | color2: mix(@a, @b);
95 | color3: mix(rgba(5,3,1,0.3), rgba(6,3,2, 0.8), 50%);
96 | }
97 |
98 | .contrast {
99 | color1: contrast(#000, red, blue);
100 | color2: contrast(#fff, red, blue);
101 | }
102 |
103 | .luma {
104 | color: luma(rgb(100, 200, 30));
105 | }
106 |
107 | .percent {
108 | per: percentage(0.5);
109 | }
110 |
111 | // color keywords
112 |
113 | .colorz {
114 | color1: whitesmoke - 10;
115 | color2: spin(red, 34);
116 | color3: spin(blah);
117 | }
118 |
119 |
120 |
121 | // purposfuly whacky to match less.js
122 |
123 | @color: #fcf8e3;
124 |
125 | body {
126 | start: @color;
127 | spin: spin(@color, -10); // #fcf4e3
128 | chained: darken(spin(@color, -10), 3%); // gives #fbeed5, should be #fbefd5
129 | direct: darken(#fcf4e3, 3%); // #fbefd5
130 | }
131 |
132 | // spin around
133 | pre {
134 | @errorBackground: #f2dede;
135 | spin: spin(@errorBackground, -10);
136 | }
137 |
138 | dd {
139 | @white: #fff;
140 | background-color: mix(@white, darken(@white, 10%), 60%);
141 | }
142 |
143 | // math
144 |
145 | .ops {
146 | c1: red * 0.3;
147 | c2: green / 2;
148 | c3: purple % 7;
149 | c4: 4 * salmon;
150 | c5: 1 + salmon;
151 |
152 | c6: 132 / red;
153 | c7: 132 - red;
154 | c8: 132- red;
155 | }
156 |
157 | .transparent {
158 | r: red(transparent);
159 | g: green(transparent);
160 | b: blue(transparent);
161 | a: alpha(transparent);
162 | }
163 |
164 |
--------------------------------------------------------------------------------
/tests/inputs/compile_on_mixin.less:
--------------------------------------------------------------------------------
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 | }
40 |
--------------------------------------------------------------------------------
/tests/inputs/data-uri.less:
--------------------------------------------------------------------------------
1 | .small {
2 | background: data-uri("../hi.less");
3 | }
4 |
5 | .large {
6 | background: data-uri('../../../lessc.inc.php');
7 | }
8 |
--------------------------------------------------------------------------------
/tests/inputs/directives.less:
--------------------------------------------------------------------------------
1 |
2 | @hello: "utf-8";
3 | @charset @hello;
4 |
5 | @-moz-document url-prefix(){
6 | div {
7 | color: red;
8 | }
9 | }
10 |
11 | @page :left { margin-left: 4cm; }
12 | @page :right { margin-left: 3cm; }
13 | @page { margin: 2cm }
14 |
15 | @-ms-viewport {
16 | width: device-width;
17 | }
18 | @-moz-viewport {
19 | width: device-width;
20 | }
21 | @-o-viewport {
22 | width: device-width;
23 | }
24 | @viewport {
25 | width: device-width;
26 | }
27 |
28 |
29 |
--------------------------------------------------------------------------------
/tests/inputs/escape.less:
--------------------------------------------------------------------------------
1 |
2 | body {
3 | @hello: "world";
4 | e1: e("this is simple");
5 | e2: e("this is simple", "cool lad");
6 | e3: e(1232);
7 | e4: e(@hello);
8 | e5: e("one" + 'more');
9 |
10 | t1: ~"eating rice";
11 | t2: ~"string cheese";
12 | t3: a b c ~"string me" d e f;
13 | t4: ~"string @{hello}";
14 | }
15 |
16 | .class {
17 | filter: ~"progid:DXImageTransform.Microsoft.AlphaImageLoader(src='image.png')";
18 | }
19 |
--------------------------------------------------------------------------------
/tests/inputs/font_family.less:
--------------------------------------------------------------------------------
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 |
29 |
--------------------------------------------------------------------------------
/tests/inputs/guards.less:
--------------------------------------------------------------------------------
1 |
2 | .simple(@hi) when (@hi) {
3 | simple: yellow;
4 | }
5 |
6 |
7 | .something(@hi) when (@hi = cool) {
8 | something: red;
9 | }
10 |
11 | .another(@x) when (@x > 10) {
12 | another: green;
13 | }
14 |
15 |
16 | .flipped(@x) when (@x =< 10) {
17 | flipped: teal;
18 | }
19 |
20 | .yeah(@arg) when (isnumber(@arg)) {
21 | yeah-number: purple @arg;
22 | }
23 |
24 |
25 | .yeah(@arg) when (ispixel(@arg)) {
26 | yeah-pixel: silver;
27 | }
28 |
29 |
30 | .hello(@arg) when not (@arg) {
31 | hello: orange;
32 | }
33 |
34 | dd {
35 | .simple(true);
36 | }
37 |
38 | b {
39 | .something(cool);
40 | .something(birthday);
41 | }
42 |
43 | img {
44 | .another(12);
45 | .flipped(2);
46 | }
47 |
48 | body {
49 | .yeah(232px);
50 | .yeah(232);
51 | }
52 |
53 | .something(@x) when (@x) and (@y), not (@x = what) {
54 | something-complex: blue @x;
55 | }
56 |
57 | div {
58 | @y: true;
59 | .something(true);
60 |
61 | }
62 |
63 | .coloras(@g) when (iscolor(@g)) {
64 | color: true @g;
65 | }
66 |
67 | link {
68 | .coloras(red);
69 | .coloras(#fff);
70 | .coloras(#fffddd);
71 | .coloras(rgb(0,0,0));
72 | .coloras(rgba(0,0,0, .34));
73 | }
74 |
75 |
--------------------------------------------------------------------------------
/tests/inputs/hacks.less:
--------------------------------------------------------------------------------
1 | // css hacks
2 |
3 | :root .alert-message, :root .btn {
4 | border-radius: 0 \0;
5 | }
6 |
7 |
--------------------------------------------------------------------------------
/tests/inputs/hi.less:
--------------------------------------------------------------------------------
1 |
2 | div:before {
3 | content: "hi!";
4 | }
5 |
6 |
--------------------------------------------------------------------------------
/tests/inputs/ie.less:
--------------------------------------------------------------------------------
1 |
2 | foo {
3 | filter: progid:DXImageTransform.Microsoft.gradient(GradientType=1, startColorstr=#c0ff3300, endColorstr=#ff000000);
4 | filter:progid:DXImageTransform.Microsoft.gradient(GradientType=1, startColorstr=#c0ff3300, endColorstr=#ff000001);
5 | }
6 |
7 |
8 | foo {
9 | filter: alpha(opacity=20);
10 | filter: alpha(opacity=20, enabled=true);
11 | filter: blaznicate(foo=bar, baz=bang bip, bart=#fa4600);
12 | }
13 |
--------------------------------------------------------------------------------
/tests/inputs/import.less:
--------------------------------------------------------------------------------
1 |
2 | @import 'file1.less'; // file found and imported
3 |
4 | @import "not-found";
5 |
6 | @import "something.css" media;
7 | @import url("something.css") media;
8 | @import url(something.css) media, screen, print;
9 |
10 | @color: maroon;
11 |
12 | @import url(file2); // found and imported
13 |
14 | body {
15 | line-height: 10em;
16 | @colors;
17 | }
18 |
19 | div {
20 | @color: fuchsia;
21 | @import "file2";
22 | }
23 |
24 |
25 | .mixin-import() {
26 | @import "file3";
27 | }
28 |
29 | .one {
30 | .mixin-import();
31 | color: blue;
32 | }
33 |
34 | .two {
35 | .mixin-import();
36 | }
37 |
38 |
39 | #merge-import-mixins {
40 | @import "a";
41 | @import "b";
42 | div { .just-a-class; }
43 | }
44 |
45 |
46 | @import "inner/file1";
47 |
48 |
49 | // test bubbling variables up from imports, while preserving order
50 |
51 | pre {
52 | color: @someValue;
53 | }
54 |
55 | @import "file3";
56 |
57 |
--------------------------------------------------------------------------------
/tests/inputs/interpolation.less:
--------------------------------------------------------------------------------
1 |
2 | @cool-hello: "yes";
3 | @cool-yes: "okay";
4 | @var: "hello";
5 |
6 | div {
7 | interp1: ~"@{cool-hello}";
8 | interp2: ~"@{cool-@{var}}";
9 | interp3: ~"@{cool-@{cool-@{var}}}";
10 | }
11 |
12 | // interpolation in selectors
13 |
14 | @hello: 10;
15 | @world: "yeah";
16 |
17 | @{hello}@{world} {
18 | color: blue;
19 | }
20 |
21 | @{hello} {
22 | color: blue;
23 | }
24 |
25 | hello world @{hello} {
26 | color: red;
27 | }
28 |
29 | #@{world} {
30 | color: "hello @{hello}";
31 | }
32 |
33 |
34 | @num: 3;
35 |
36 | [prop],
37 | [prop="value@{num}"],
38 | [prop*="val@{num}"],
39 | [|prop~="val@{num}"],
40 | [*|prop$="val@{num}"],
41 | [ns|prop^="val@{num}"],
42 | [@{num}^="val@{num}"],
43 | [@{num}=@{num}],
44 | [@{num}] {
45 | attributes: yes;
46 | }
47 |
48 |
--------------------------------------------------------------------------------
/tests/inputs/keyframes.less:
--------------------------------------------------------------------------------
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 | @-webkit-keyframes flowouttoleft {
28 | 0% { -webkit-transform: translateX(0) scale(1); }
29 | 60%, 70% { -webkit-transform: translateX(0) scale(.7); }
30 | 100% { -webkit-transform: translateX(-100%) scale(.7); }
31 | }
32 |
33 | div {
34 | animation-name: 'diagonal-slide';
35 | animation-duration: 5s;
36 | animation-iteration-count: 10;
37 | }
38 |
39 | @keyframes 'diagonal-slide' {
40 |
41 | from {
42 | left: 0;
43 | top: 0;
44 | }
45 |
46 | to {
47 | left: 100px;
48 | top: 100px;
49 | }
50 |
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/tests/inputs/math.less:
--------------------------------------------------------------------------------
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 | sub1: 10-5;
18 | sub2: 10 - 5;
19 |
20 | add1: 10+5;
21 | add2: 10 + 5;
22 |
23 | // div: 10/5; // this wont work, read on
24 | div: 10 / 5;
25 |
26 | mul1: 10*5;
27 | mul2: 10 * 5;
28 | }
29 |
30 | // these properties have divison not in parenthases
31 | .supress-division {
32 | border-radius: 10px / 10px;
33 | border-radius: 10px/12px;
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/22px 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 | div1: (10 /5);
51 | div2: (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 | neg1: -(1px);
66 | neg2: 0-(1px);
67 |
68 | @something: 10;
69 | neg3: -@something;
70 | }
71 |
72 |
73 | // and now here are the tests
74 |
75 | .test {
76 | single1: (5);
77 | single2: 5+(5);
78 | single3: (5)+((5));
79 |
80 | parens: (5 +(5)) -2;
81 | // parens: ((5 +(5)) -2); // FAILS - fixme
82 |
83 | math1: (5 + 5)*(2 / 1);
84 | math2: (5+5)*(2/1);
85 |
86 | complex1: 2 * (4 * (2 + (1 + 6))) - 1;
87 | complex2: ((2+3)*(2+3) / (9-4)) + 1;
88 | complex3: (2px + 4px) 1em 2px 2;
89 |
90 | @var: (2 * 2);
91 | var1: (2 * @var) 4 4 (@var * 1px);
92 | var2: (@var * @var) * 6;
93 | var3: 4 * (5 + 5) / 2 - (@var * 2);
94 |
95 | complex4: (7 * 7) + (8 * 8);
96 | }
97 |
98 | .percents {
99 | p1: 100 * 10%;
100 | p2: 10% * 100;
101 | p3: 10% * 10%;
102 |
103 | p4: 100px * 10%; // lessjs makes this px
104 | p5: 10% * 100px; // lessjs makes this %
105 |
106 | p6: 20% + 10%;
107 | p7: 20% - 10%;
108 |
109 | p8: 20% / 10%;
110 | }
111 |
112 | .misc {
113 | x: 10px * 4em;
114 | y: 10 * 4em;
115 | }
116 |
117 |
118 | .cond {
119 | c1: 10 < 10;
120 | c2: 10 >= 10;
121 | }
122 |
123 |
--------------------------------------------------------------------------------
/tests/inputs/media.less:
--------------------------------------------------------------------------------
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 |
39 |
40 | // media bubbling
41 |
42 | @media test {
43 | div {
44 | height: 20px;
45 | @media (hello) {
46 | color: red;
47 |
48 | pre {
49 | color: orange;
50 | }
51 | }
52 | }
53 | }
54 |
55 | // should not cross boundary
56 | @media yeah {
57 | @page {
58 | @media cool {
59 | color: red;
60 | }
61 | }
62 | }
63 |
64 | // variable in query
65 | @mobile: ~"(max-width: 599px)";
66 | @media @mobile {
67 | .helloworld { color: blue }
68 | }
69 |
--------------------------------------------------------------------------------
/tests/inputs/misc.less:
--------------------------------------------------------------------------------
1 |
2 | @color: #fff;
3 | @base_path: "/assets/images/";
4 | @images: @base_path + "test/";
5 | .topbar { background: url(@{images}topbar.png); }
6 | .hello { test: empty-function(@images, 40%, to(@color)); }
7 |
8 | .css3 {
9 | background-image: -webkit-gradient(linear, 0% 0%, 0% 90%,
10 | from(#E9A000), to(#A37000));
11 | }
12 |
13 |
14 | /**
15 |
16 | Here is a block comment
17 |
18 | **/
19 |
20 |
21 | // this is a comment
22 |
23 | .test, /*hello*/.world {
24 | border: 1px solid red; // world
25 | /* another property */
26 | color: url(http://mage-page.com);
27 | string: "hello /* this is not a comment */";
28 | world: "// neither is this";
29 | string: 'hello /* this is not a comment */' /*what if this is a comment */;
30 | world: '// neither is this' // hell world;
31 | ;
32 | what-/*something?*/ever: 100px;
33 | background: url(/*no comment here*/);
34 | }
35 |
36 |
37 | .urls {
38 | @var: "http://google.com";
39 | background1: url(@var);
40 | background2: url(@{var});
41 | background3: url("@{var}");
42 | }
43 |
44 | .mix(@arg) { color: @arg; }
45 | @aaa: aaa;
46 | @bbb: bbb;
47 | // make sure the opening selector isn't too greedy
48 | .cool {.mix("@{aaa}, @{bbb}")}
49 | .cool();
50 |
51 |
52 |
53 | // merging of mixins
54 | .span-17 { float: left; }
55 | .span-17 { width: 660px; }
56 |
57 | .x {.span-17;}
58 |
59 | .hi {
60 | pre {
61 | color: red;
62 | }
63 | }
64 |
65 | .hi {
66 | pre {
67 | color: blue;
68 | }
69 | }
70 |
71 | .rad {
72 | .hi;
73 | }
74 |
75 |
76 | hello {
77 | numbers: 1.0 0.1 .1 1.;
78 | numbers: 1.0s 0.1s .1s 1.s;
79 | numbers: -1.0s -0.1s -.1s -1.s;
80 | numbers: -1.0 -0.1 -.1 -1.;
81 | }
82 |
83 |
84 | #string {
85 | hello: 'what\'s going on here';
86 | hello: 'blah blag @{ blah blah';
87 |
88 | join: 3434 + "hello";
89 | join: 3434 + hello;
90 | }
91 |
92 |
93 | .duplicates {
94 | hello: world;
95 | hello: "world";
96 | hello: world;
97 | hello: "what";
98 | hello: world;
99 | hello: "world";
100 | }
101 |
--------------------------------------------------------------------------------
/tests/inputs/mixin_functions.less:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/tests/inputs/mixin_merging.less.disable:
--------------------------------------------------------------------------------
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 |
101 |
--------------------------------------------------------------------------------
/tests/inputs/mixins.less:
--------------------------------------------------------------------------------
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 | // arguments
65 |
66 | .spam(@something: 100, @dad: land) {
67 | @wow: 23434;
68 | foo: @arguments;
69 | bar: @arguments;
70 | }
71 |
72 | .eggs {
73 | .spam(1px, 2px);
74 | .spam();
75 | }
76 |
77 | .first(@one, @two, @three, @four: cool) {
78 | cool: @arguments;
79 | }
80 |
81 | #hello {
82 | .first(one, two, three);
83 | }
84 |
85 | #hello-important {
86 | .first(one, two, three) !important;
87 | }
88 |
89 | .rad(@name) {
90 | cool: @arguments;
91 | }
92 |
93 | #world {
94 | @hello: "world";
95 | .rad("@{hello}");
96 | }
97 |
98 | .second(@x, @y:skip, @z: me) {
99 | things: @arguments;
100 | }
101 |
102 | #another {
103 | .second(red, blue, green);
104 | .second(red blue green);
105 | }
106 |
107 |
108 | .another(@x, @y:skip, @z:me) {
109 | .cool {
110 | color: @arguments;
111 | }
112 | }
113 |
114 | #day {
115 | .another(one,two, three);
116 | .another(one two three);
117 | }
118 |
119 |
120 | .to-be-important() {
121 | color: red;
122 | @color: red;
123 | height: 20px;
124 |
125 | pre {
126 | color: @color;
127 | }
128 | }
129 |
130 | .mix-suffix {
131 | .to-be-important() !important;
132 | }
133 |
134 |
135 |
136 |
137 | #search-all {
138 | .red() {
139 | color:#f00 !important;
140 | }
141 | }
142 |
143 | #search-all {
144 | .green() {
145 | color: #0f0 !important;
146 | }
147 | }
148 |
149 | .search-test {
150 | #search-all > .red();
151 | #search-all > .green();
152 | }
153 |
154 |
155 | // mixin self without infinite loop
156 | .cowboy() {
157 | color: blue;
158 | }
159 |
160 | .cowboy {
161 | .cowboy;
162 | }
163 |
164 |
165 | // semicolon
166 |
167 | .semi1(@color: red, blue, green;) {
168 | color: @color;
169 | }
170 |
171 | .semi2(@color: red, blue, green; dad) {
172 | color: @color;
173 | }
174 |
175 | .semi3(hello; world; piss) {
176 | hello: world;
177 | }
178 |
179 |
180 |
181 | // self referencing skipping
182 |
183 | .nav-divider(@color: red){
184 | padding: 10px;
185 | }
186 |
187 | .nav {
188 | .nav-divider {
189 | .nav-divider();
190 | }
191 | }
192 |
193 | .nav-divider {
194 | .nav-divider();
195 | }
196 |
197 |
198 |
--------------------------------------------------------------------------------
/tests/inputs/nested.less:
--------------------------------------------------------------------------------
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 |
61 |
--------------------------------------------------------------------------------
/tests/inputs/pattern_matching.less:
--------------------------------------------------------------------------------
1 |
2 | .demo (light, @color) {
3 | color: lighten(@color, 10%);
4 | }
5 | .demo (@_, @color) {
6 | display: block;
7 | }
8 |
9 | @switch: light;
10 |
11 | .class {
12 | .demo(@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 |
114 | .hola(hello, @hello...) {
115 | color: blue;
116 | }
117 |
118 | #hola {
119 | .hola(hello, world);
120 | }
121 |
122 | .resty(@hello, @world, @the_rest...) {
123 | padding: @hello @world;
124 | rest: @the_rest;
125 | }
126 |
127 | .defaults(@aa, @bb:e343, @cc: "heah", ...) {
128 | height: @aa;
129 | }
130 |
131 | #defaults_1 {
132 | .defaults(one);
133 | .defaults(two, one);
134 | .defaults(three, two, one);
135 | .defaults(four, three, two, one);
136 | }
137 |
138 |
139 | .thing() { color: green; }
140 | .thing(...) { color: blue; }
141 | .thing { color: red; }
142 |
143 | #aa {
144 | .thing();
145 | }
146 |
147 | #bb {
148 | .thing;
149 | }
150 |
151 |
152 | #cc {
153 | .thing(1);
154 | }
155 |
156 |
157 |
158 |
--------------------------------------------------------------------------------
/tests/inputs/scopes.less:
--------------------------------------------------------------------------------
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 |
41 |
--------------------------------------------------------------------------------
/tests/inputs/selector_expressions.less:
--------------------------------------------------------------------------------
1 |
2 | @color: blue;
3 |
4 | something @{color}, world {
5 | color: blue;
6 | }
7 |
8 | .div {
9 | @color: red;
10 | (3434) {
11 | height: 100px;
12 | }
13 |
14 | cool @{color} {
15 | height: 4000px;
16 | }
17 | }
18 |
19 | .heck(@a) { color: @a+10 }
20 |
21 | .spanX (@index) when (@index > 0) {
22 | .span@{index} { .heck(@index) }
23 | .spanX(@index - 1);
24 | }
25 | .spanX (0) {}
26 |
27 | .spanX (5);
28 |
29 |
30 |
--------------------------------------------------------------------------------
/tests/inputs/site_demos.less:
--------------------------------------------------------------------------------
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 |
121 |
--------------------------------------------------------------------------------
/tests/inputs/test-imports/a.less:
--------------------------------------------------------------------------------
1 | .just-a-class { background: red; }
2 |
3 | .some-mixin() {
4 | height: 200px;
5 | }
6 |
7 |
--------------------------------------------------------------------------------
/tests/inputs/test-imports/b.less:
--------------------------------------------------------------------------------
1 | .just-a-class { background: blue; }
2 |
3 | .hello {
4 | .some-mixin();
5 | }
6 |
7 |
8 | @media cool {
9 | color: red;
10 | .some-mixin();
11 | }
12 |
13 |
--------------------------------------------------------------------------------
/tests/inputs/test-imports/file1.less:
--------------------------------------------------------------------------------
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 |
17 |
--------------------------------------------------------------------------------
/tests/inputs/test-imports/file2.less:
--------------------------------------------------------------------------------
1 |
2 | b {
3 | color: @color;
4 | padding: 16px;
5 | }
6 |
7 |
--------------------------------------------------------------------------------
/tests/inputs/test-imports/file3.less:
--------------------------------------------------------------------------------
1 |
2 | h2 {
3 | background: url("../images/logo.png") no-repeat;
4 | }
5 |
6 | @someValue: hello-from-file-3;
7 |
8 |
--------------------------------------------------------------------------------
/tests/inputs/test-imports/inner/file1.less:
--------------------------------------------------------------------------------
1 |
2 | .inner {
3 | content: "inner/file1.less"
4 | }
5 |
6 | @import "file2"; // should get the one in inner
7 |
--------------------------------------------------------------------------------
/tests/inputs/test-imports/inner/file2.less:
--------------------------------------------------------------------------------
1 |
2 | .inner {
3 | content: "inner/file2.less"
4 | }
5 |
--------------------------------------------------------------------------------
/tests/inputs/variables.less:
--------------------------------------------------------------------------------
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 | }
36 |
37 | @hello: 44px;
38 | @something: "hello";
39 | @cool: something;
40 |
41 | outer1: @@something;
42 | outer2: @@@cool;
43 |
44 |
45 |
--------------------------------------------------------------------------------
/tests/inputs_lessjs/mixins-args.less:
--------------------------------------------------------------------------------
1 | .mixin (@a: 1px, @b: 50%) {
2 | width: (@a * 5);
3 | height: (@b - 1%);
4 | }
5 |
6 | .mixina (@style, @width, @color: black) {
7 | border: @width @style @color;
8 | }
9 |
10 | .mixiny
11 | (@a: 0, @b: 0) {
12 | margin: @a;
13 | padding: @b;
14 | }
15 |
16 | .hidden() {
17 | color: transparent; // asd
18 | }
19 |
20 | #hidden {
21 | .hidden;
22 | }
23 |
24 | #hidden1 {
25 | .hidden();
26 | }
27 |
28 | .two-args {
29 | color: blue;
30 | .mixin(2px, 100%);
31 | .mixina(dotted, 2px);
32 | }
33 |
34 | .one-arg {
35 | .mixin(3px);
36 | }
37 |
38 | .no-parens {
39 | .mixin;
40 | }
41 |
42 | .no-args {
43 | .mixin();
44 | }
45 |
46 | .var-args {
47 | @var: 9;
48 | .mixin(@var, (@var * 2));
49 | }
50 |
51 | .multi-mix {
52 | .mixin(2px, 30%);
53 | .mixiny(4, 5);
54 | }
55 |
56 | .maxa(@arg1: 10, @arg2: #f00) {
57 | padding: (@arg1 * 2px);
58 | color: @arg2;
59 | }
60 |
61 | body {
62 | .maxa(15);
63 | }
64 |
65 | @glob: 5;
66 | .global-mixin(@a:2) {
67 | width: (@glob + @a);
68 | }
69 |
70 | .scope-mix {
71 | .global-mixin(3);
72 | }
73 |
74 | .nested-ruleset (@width: 200px) {
75 | width: @width;
76 | .column { margin: @width; }
77 | }
78 | .content {
79 | .nested-ruleset(600px);
80 | }
81 |
82 | //
83 |
84 | .same-var-name2(@radius) {
85 | radius: @radius;
86 | }
87 | .same-var-name(@radius) {
88 | .same-var-name2(@radius);
89 | }
90 | #same-var-name {
91 | .same-var-name(5px);
92 | }
93 |
94 | //
95 |
96 | .var-inside () {
97 | @var: 10px;
98 | width: @var;
99 | }
100 | #var-inside { .var-inside; }
101 |
102 | .mixin-arguments (@width: 0px, ...) {
103 | border: @arguments;
104 | width: @width;
105 | }
106 |
107 | .arguments {
108 | .mixin-arguments(1px, solid, black);
109 | }
110 | .arguments2 {
111 | .mixin-arguments();
112 | }
113 | .arguments3 {
114 | .mixin-arguments;
115 | }
116 |
117 | .mixin-arguments2 (@width, @rest...) {
118 | border: @arguments;
119 | rest: @rest;
120 | width: @width;
121 | }
122 | .arguments4 {
123 | .mixin-arguments2(0, 1, 2, 3, 4);
124 | }
125 |
126 | // Edge cases
127 |
128 | .edge-case {
129 | .mixin-arguments("{");
130 | }
131 |
132 | // Division vs. Literal Slash
133 | .border-radius(@r: 2px/5px) {
134 | border-radius: @r;
135 | }
136 | .slash-vs-math {
137 | .border-radius();
138 | .border-radius(5px/10px);
139 | .border-radius((3px * 2));
140 | }
141 | // semi-colon vs comma for delimiting
142 |
143 | .mixin-takes-one(@a) {
144 | one: @a;
145 | }
146 |
147 | .mixin-takes-two(@a; @b) {
148 | one: @a;
149 | two: @b;
150 | }
151 |
152 | .comma-vs-semi-colon {
153 | .mixin-takes-two(@a : a; @b : b, c);
154 | .mixin-takes-two(@a : d, e; @b : f);
155 | .mixin-takes-one(@a: g);
156 | .mixin-takes-one(@a : h;);
157 | .mixin-takes-one(i);
158 | .mixin-takes-one(j;);
159 | .mixin-takes-two(k, l);
160 | .mixin-takes-one(m, n;);
161 | .mixin-takes-two(o, p; q);
162 | .mixin-takes-two(r, s; t;);
163 | }
164 |
165 | .mixin-conflict(@a:defA, @b:defB, @c:defC) {
166 | three: @a, @b, @c;
167 | }
168 |
169 | .mixin-conflict(@a:defA, @b:defB, @c:defC, @d:defD) {
170 | four: @a, @b, @c, @d;
171 | }
172 |
173 | #named-conflict {
174 | .mixin-conflict(11, 12, 13, @a:a);
175 | .mixin-conflict(@a:a, 21, 22, 23);
176 | }
177 | @a: 3px;
178 | .mixin-default-arg(@a: 1px, @b: @a, @c: @b) {
179 | defaults: 1px 1px 1px;
180 | defaults: 2px 2px 2px;
181 | }
182 |
183 | .test-mixin-default-arg {
184 | .mixin-default-arg();
185 | .mixin-default-arg(2px);
186 | }
187 |
188 | .mixin-comma-default1(@color; @padding; @margin: 2, 2, 2, 2) {
189 | margin: @margin;
190 | }
191 | .selector {
192 | .mixin-comma-default1(#33acfe; 4);
193 | }
194 | .mixin-comma-default2(@margin: 2, 2, 2, 2;) {
195 | margin: @margin;
196 | }
197 | .selector2 {
198 | .mixin-comma-default2();
199 | }
200 | .mixin-comma-default3(@margin: 2, 2, 2, 2) {
201 | margin: @margin;
202 | }
203 | .selector3 {
204 | .mixin-comma-default3(4,2,2,2);
205 | }
206 |
--------------------------------------------------------------------------------
/tests/inputs_lessjs/mixins-named-args.less:
--------------------------------------------------------------------------------
1 | .mixin (@a: 1px, @b: 50%) {
2 | width: (@a * 5);
3 | height: (@b - 1%);
4 | args: @arguments;
5 | }
6 | .mixin (@a: 1px, @b: 50%) when (@b > 75%){
7 | text-align: center;
8 | }
9 |
10 | .named-arg {
11 | color: blue;
12 | .mixin(@b: 100%);
13 | }
14 |
15 | .class {
16 | @var: 20%;
17 | .mixin(@b: @var);
18 | }
19 |
20 | .all-args-wrong-args {
21 | .mixin(@b: 10%, @a: 2px);
22 | }
23 |
24 | .mixin2 (@a: 1px, @b: 50%, @c: 50) {
25 | width: (@a * 5);
26 | height: (@b - 1%);
27 | color: (#000000 + @c);
28 | }
29 |
30 | .named-args2 {
31 | .mixin2(3px, @c: 100);
32 | }
33 |
34 | .named-args3 {
35 | .mixin2(@b: 30%, @c: #123456);
36 | }
37 |
--------------------------------------------------------------------------------
/tests/inputs_lessjs/strings.less:
--------------------------------------------------------------------------------
1 | #strings {
2 | background-image: url("http://son-of-a-banana.com");
3 | quotes: "~" "~";
4 | content: "#*%:&^,)!.(~*})";
5 | empty: "";
6 | brackets: "{" "}";
7 | escapes: "\"hello\" \\world";
8 | escapes2: "\"llo";
9 | }
10 | #comments {
11 | content: "/* hello */ // not-so-secret";
12 | }
13 | #single-quote {
14 | quotes: "'" "'";
15 | content: '""#!&""';
16 | empty: '';
17 | semi-colon: ';';
18 | }
19 | #escaped {
20 | filter: ~"DX.Transform.MS.BS.filter(opacity=50)";
21 | }
22 | #one-line { image: url(http://tooks.com) }
23 | #crazy { image: url(http://), "}", url("http://}") }
24 | #interpolation {
25 | @var: '/dev';
26 | url: "http://lesscss.org@{var}/image.jpg";
27 |
28 | @var2: 256;
29 | url2: "http://lesscss.org/image-@{var2}.jpg";
30 |
31 | @var3: #456;
32 | url3: "http://lesscss.org@{var3}";
33 |
34 | @var4: hello;
35 | url4: "http://lesscss.org/@{var4}";
36 |
37 | @var5: 54.4px;
38 | url5: "http://lesscss.org/@{var5}";
39 | }
40 |
41 | // multiple calls with string interpolation
42 |
43 | .mix-mul (@a: green) {
44 | color: ~"@{a}";
45 | }
46 | .mix-mul-class {
47 | .mix-mul(blue);
48 | .mix-mul(red);
49 | .mix-mul(black);
50 | .mix-mul(orange);
51 | }
52 |
--------------------------------------------------------------------------------
/tests/outputs/accessors.css:
--------------------------------------------------------------------------------
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; }
15 |
--------------------------------------------------------------------------------
/tests/outputs/arity.css:
--------------------------------------------------------------------------------
1 | .one {
2 | hello: one;
3 | world: one;
4 | }
5 | .two {
6 | hello: two;
7 | world: two;
8 | }
9 | .three {
10 | hello: three;
11 | world: three;
12 | }
13 | .multi-foo {
14 | foo: two cool;
15 | foo: three cool yeah;
16 | foo: two 1;
17 | foo: three 1 yeah;
18 | foo: three 1 1;
19 | }
20 | .multi-baz {
21 | baz: two cool;
22 | baz: three yeah;
23 | baz: two 1;
24 | baz: three 1;
25 | }
26 |
--------------------------------------------------------------------------------
/tests/outputs/attributes.css:
--------------------------------------------------------------------------------
1 | * {
2 | color: blue;
3 | }
4 | E {
5 | color: blue;
6 | }
7 | E[foo] {
8 | color: blue;
9 | }
10 | [foo] {
11 | color: blue;
12 | }
13 | [foo] .helloWorld {
14 | color: blue;
15 | }
16 | [foo].helloWorld {
17 | color: blue;
18 | }
19 | E[foo="barbar"] {
20 | color: blue;
21 | }
22 | E[foo~="hello#$@%@$#^"] {
23 | color: blue;
24 | }
25 | E[foo^="color: green;"] {
26 | color: blue;
27 | }
28 | E[foo$="239023"] {
29 | color: blue;
30 | }
31 | E[foo*="29302"] {
32 | color: blue;
33 | }
34 | E[foo|="239032"] {
35 | color: blue;
36 | }
37 | E:root {
38 | color: blue;
39 | }
40 | E:nth-child(odd) {
41 | color: blue;
42 | }
43 | E:nth-child(2n+1) {
44 | color: blue;
45 | }
46 | E:nth-child(5) {
47 | color: blue;
48 | }
49 | E:nth-last-child(-n+2) {
50 | color: blue;
51 | }
52 | E:nth-of-type(2n) {
53 | color: blue;
54 | }
55 | E:nth-last-of-type(n) {
56 | color: blue;
57 | }
58 | E:first-child {
59 | color: blue;
60 | }
61 | E:last-child {
62 | color: blue;
63 | }
64 | E:first-of-type {
65 | color: blue;
66 | }
67 | E:last-of-type {
68 | color: blue;
69 | }
70 | E:only-child {
71 | color: blue;
72 | }
73 | E:only-of-type {
74 | color: blue;
75 | }
76 | E:empty {
77 | color: blue;
78 | }
79 | E:lang(en) {
80 | color: blue;
81 | }
82 | E::first-line {
83 | color: blue;
84 | }
85 | E::before {
86 | color: blue;
87 | }
88 | E#id {
89 | color: blue;
90 | }
91 | E:not(:link) {
92 | color: blue;
93 | }
94 | E F {
95 | color: blue;
96 | }
97 | E > F {
98 | color: blue;
99 | }
100 | E + F {
101 | color: blue;
102 | }
103 | E ~ F {
104 | color: blue;
105 | }
106 |
--------------------------------------------------------------------------------
/tests/outputs/builtins.css:
--------------------------------------------------------------------------------
1 | body {
2 | color: "hello world";
3 | num-basic: 5.1666666666667;
4 | num-floor: 5px;
5 | num-ceil: 6px;
6 | num2: 0.66666666666667;
7 | num2-round: 1;
8 | num2-floor: 0;
9 | num2-ceil: 1;
10 | round-lit: 3px;
11 | rgba1: #ff112233;
12 | rgba2: #992c3742;
13 | argb: #992c3742;
14 | }
15 | format {
16 | format: "rgb(32, 128, 64)";
17 | format-string: "hello world";
18 | format-multiple: "hello earth 2";
19 | format-url-encode: 'red is %A';
20 | eformat: rgb(32, 128, 64);
21 | }
22 | #functions {
23 | str1: true;
24 | str2: false;
25 | num1: true;
26 | num2: true;
27 | num3: true;
28 | num4: false;
29 | col1: true;
30 | col2: false;
31 | col3: true;
32 | col4: true;
33 | key1: true;
34 | key2: false;
35 | px1: true;
36 | px2: false;
37 | per1: true;
38 | per2: false;
39 | em1: true;
40 | em2: false;
41 | ex1: 2;
42 | ex2: 1;
43 | ex3: extract(1,1);
44 | ex4: 2;
45 | pow: 16;
46 | pi: 3.1415926535898;
47 | mod: 4;
48 | tan: 1.5574077246549;
49 | cos: 0.54030230586814;
50 | sin: 0.8414709848079;
51 | atan: 0.78539816339745rad;
52 | acos: 0rad;
53 | asin: 1.5707963267949rad;
54 | sqrt: 2.8284271247462;
55 | }
56 | #unit {
57 | unit-lit: 10;
58 | unit-arg: 10s;
59 | unit-arg2: 10em;
60 | unit-math: 7.407%;
61 | }
62 |
--------------------------------------------------------------------------------
/tests/outputs/colors.css:
--------------------------------------------------------------------------------
1 | body {
2 | color: #ff0000;
3 | color: #ffffff;
4 | color: #000000;
5 | color: #996d33;
6 | color: rgba(153,109,51,0.3);
7 | lighten1: #ffffff;
8 | lighten2: #7c8df2;
9 | lighten3: rgba(222,140,129,0.5);
10 | darken1: #d6d6d6;
11 | darken2: #0d1e81;
12 | darken3: rgba(18,42,185,0.5);
13 | saturate1: #f1eded;
14 | saturate2: #0025fe;
15 | saturate3: rgba(10,44,244,0.5);
16 | desaturate1: #efefef;
17 | desaturate2: #3349cb;
18 | desaturate3: rgba(36,62,218,0.5);
19 | spin1: #efefef;
20 | spin2: #2d17e7;
21 | spin3: rgba(59,23,231,0.5);
22 | spin2: #efefef;
23 | spin3: #1769e7;
24 | spin4: rgba(23,119,231,0.5);
25 | one1: #abcdef;
26 | one2: #abcdef;
27 | two1: rgba(2,159,35,0.9);
28 | two2: rgba(2,159,35,0.9);
29 | tint1: #f0f0f0;
30 | tint2: #ececec;
31 | shade1: #707070;
32 | shade2: #868686;
33 | three1: rgba(1,2,3,0.6);
34 | three2: rgba(1,2,3,0.6);
35 | four1: rgba(1,2,3,0);
36 | four2: rgba(1,2,3,0);
37 | hue: 282;
38 | sat: 33;
39 | lit: 12;
40 | what: #dff1da;
41 | zero1: #343434;
42 | zero2: #003468;
43 | zero3: #000000;
44 | zero4: #ffffff;
45 | zero5: #000000;
46 | zero6: #ffffff;
47 | zero7: #ffffff;
48 | zero8: #ffffff;
49 | zero9: #1d5612;
50 | zeroa: #56124b;
51 | zerob: #000000;
52 | zeroc: #ffffff;
53 | }
54 | alpha {
55 | g1: 0;
56 | g2: 1;
57 | }
58 | fade {
59 | f1: rgba(255,0,0,0.5);
60 | f2: rgba(255,255,255,0.2);
61 | f3: rgba(34,23,64,0.5);
62 | }
63 | .mix {
64 | color1: #808080;
65 | color2: #808080;
66 | color3: rgba(6,3,2,-0.25);
67 | }
68 | .contrast {
69 | color1: #ff0000;
70 | color2: #0000ff;
71 | }
72 | .luma {
73 | color: 44.11161568%;
74 | }
75 | .percent {
76 | per: 50%;
77 | }
78 | .colorz {
79 | color1: #ebebeb;
80 | color2: #ff9100;
81 | color3: #000000;
82 | }
83 | body {
84 | start: #fcf8e3;
85 | spin: #fcf4e3;
86 | chained: #fbeed5;
87 | direct: #fbefd5;
88 | }
89 | pre {
90 | spin: #f2dee1;
91 | }
92 | dd {
93 | background-color: #f5f5f5;
94 | }
95 | .ops {
96 | c1: #4d0000;
97 | c2: #004000;
98 | c3: #020002;
99 | c4: #ffffff;
100 | c5: #fb8173;
101 | c6: 132 / #ff0000;
102 | c7: 132 - #ff0000;
103 | c8: 132- #ff0000;
104 | }
105 | .transparent {
106 | r: 0;
107 | g: 0;
108 | b: 0;
109 | a: 0;
110 | }
111 |
--------------------------------------------------------------------------------
/tests/outputs/compile_on_mixin.css:
--------------------------------------------------------------------------------
1 | body {
2 | height: 22px;
3 | }
4 | body ul {
5 | height: 20px;
6 | }
7 | body ul li {
8 | height: 10px;
9 | }
10 | body ul li div span,
11 | body ul li link {
12 | margin: 10px;
13 | color: red;
14 | }
15 | body ul div,
16 | body ul p {
17 | border: 1px;
18 | }
19 | body ul div.hello,
20 | body ul p.hello {
21 | color: green;
22 | }
23 | body ul div :what,
24 | body ul p :what {
25 | color: blue;
26 | }
27 | body ul a b {
28 | color: blue;
29 | }
30 |
--------------------------------------------------------------------------------
/tests/outputs/data-uri.css:
--------------------------------------------------------------------------------
1 | .small {
2 | background: url("data:text/plain;base64,CmRpdjpiZWZvcmUgewoJY29udGVudDogImhpISI7Cn0KCg==");
3 | }
4 | .large {
5 | background: url("../../../lessc.inc.php");
6 | }
7 |
--------------------------------------------------------------------------------
/tests/outputs/directives.css:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 | @-moz-document url-prefix() {
3 | div {
4 | color: red;
5 | }
6 | }
7 | @page :left {
8 | margin-left: 4cm;
9 | }
10 | @page :right {
11 | margin-left: 3cm;
12 | }
13 | @page {
14 | margin: 2cm;
15 | }
16 | @-ms-viewport {
17 | width: device-width;
18 | }
19 | @-moz-viewport {
20 | width: device-width;
21 | }
22 | @-o-viewport {
23 | width: device-width;
24 | }
25 | @viewport {
26 | width: device-width;
27 | }
28 |
--------------------------------------------------------------------------------
/tests/outputs/escape.css:
--------------------------------------------------------------------------------
1 | body {
2 | e1: this is simple;
3 | e2: this is simple;
4 | e3: 1232;
5 | e4: world;
6 | e5: onemore;
7 | t1: eating rice;
8 | t2: string cheese;
9 | t3: a b c string me d e f;
10 | t4: string world;
11 | }
12 | .class {
13 | filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='image.png');
14 | }
15 |
--------------------------------------------------------------------------------
/tests/outputs/font_family.css:
--------------------------------------------------------------------------------
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 | }
18 |
--------------------------------------------------------------------------------
/tests/outputs/guards.css:
--------------------------------------------------------------------------------
1 | dd {
2 | simple: yellow;
3 | }
4 | b {
5 | something: red;
6 | something-complex: blue cool;
7 | something-complex: blue birthday;
8 | }
9 | img {
10 | another: green;
11 | flipped: teal;
12 | }
13 | body {
14 | yeah-number: purple 232px;
15 | yeah-pixel: silver;
16 | yeah-number: purple 232;
17 | }
18 | div {
19 | something-complex: blue true;
20 | }
21 | link {
22 | color: true red;
23 | color: true #fff;
24 | color: true #fffddd;
25 | color: true #000000;
26 | color: true rgba(0,0,0,0.34);
27 | }
28 |
--------------------------------------------------------------------------------
/tests/outputs/hacks.css:
--------------------------------------------------------------------------------
1 | :root .alert-message,
2 | :root .btn {
3 | border-radius: 0 \0;
4 | }
5 |
--------------------------------------------------------------------------------
/tests/outputs/hi.css:
--------------------------------------------------------------------------------
1 | div:before {
2 | content: "hi!";
3 | }
4 |
--------------------------------------------------------------------------------
/tests/outputs/ie.css:
--------------------------------------------------------------------------------
1 | foo {
2 | filter: progid:DXImageTransform.Microsoft.gradient(GradientType=1,startColorstr=#c0ff3300,endColorstr=#ff000000);
3 | filter: progid:DXImageTransform.Microsoft.gradient(GradientType=1,startColorstr=#c0ff3300,endColorstr=#ff000001);
4 | }
5 | foo {
6 | filter: alpha(opacity=20);
7 | filter: alpha(opacity=20,enabled=true);
8 | filter: blaznicate(foo=bar,baz=bang bip,bart=#fa4600);
9 | }
10 |
--------------------------------------------------------------------------------
/tests/outputs/import.css:
--------------------------------------------------------------------------------
1 | @import "not-found";
2 | @import "something.css" media;
3 | @import url("something.css") media;
4 | @import url(something.css) media, screen, print;
5 | b {
6 | color: maroon;
7 | padding: 16px;
8 | }
9 | body {
10 | line-height: 10em;
11 | }
12 | body div.bright {
13 | color: red;
14 | }
15 | body div.sad {
16 | color: blue;
17 | }
18 | .one {
19 | color: blue;
20 | }
21 | #merge-import-mixins .just-a-class {
22 | background: red;
23 | }
24 | #merge-import-mixins .just-a-class {
25 | background: blue;
26 | }
27 | #merge-import-mixins .hello {
28 | height: 200px;
29 | }
30 | @media cool {
31 | #merge-import-mixins {
32 | color: red;
33 | height: 200px;
34 | }
35 | }
36 | #merge-import-mixins div {
37 | background: red;
38 | background: blue;
39 | }
40 | .inner {
41 | content: "inner/file1.less";
42 | }
43 | .inner {
44 | content: "inner/file2.less";
45 | }
46 | pre {
47 | color: hello-from-file-3;
48 | }
49 | h2 {
50 | background: url("../images/logo.png") no-repeat;
51 | }
52 |
--------------------------------------------------------------------------------
/tests/outputs/interpolation.css:
--------------------------------------------------------------------------------
1 | div {
2 | interp1: yes;
3 | interp2: yes;
4 | interp3: okay;
5 | }
6 | 10"yeah" {
7 | color: blue;
8 | }
9 | 10 {
10 | color: blue;
11 | }
12 | hello world 10 {
13 | color: red;
14 | }
15 | #"yeah" {
16 | color: "hello 10";
17 | }
18 | [prop],
19 | [prop="value3"],
20 | [prop*="val3"],
21 | [|prop~="val3"],
22 | [*|prop$="val3"],
23 | [ns|prop^="val3"],
24 | [3^="val3"],
25 | [3=3],
26 | [3] {
27 | attributes: yes;
28 | }
29 |
--------------------------------------------------------------------------------
/tests/outputs/keyframes.css:
--------------------------------------------------------------------------------
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 | @-webkit-keyframes flowouttoleft {
23 | 0% {
24 | -webkit-transform: translateX(0) scale(1);
25 | }
26 | 60%,
27 | 70% {
28 | -webkit-transform: translateX(0) scale(.7);
29 | }
30 | 100% {
31 | -webkit-transform: translateX(-100%) scale(.7);
32 | }
33 | }
34 | div {
35 | animation-name: 'diagonal-slide';
36 | animation-duration: 5s;
37 | animation-iteration-count: 10;
38 | }
39 | @keyframes 'diagonal-slide' {
40 | from {
41 | left: 0;
42 | top: 0;
43 | }
44 | to {
45 | left: 100px;
46 | top: 100px;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/tests/outputs/math.css:
--------------------------------------------------------------------------------
1 | .unary {
2 | sub: 10 -5;
3 | }
4 | .spaces {
5 | sub1: 5;
6 | sub2: 5;
7 | add1: 15;
8 | add2: 15;
9 | div: 2;
10 | mul1: 50;
11 | mul2: 50;
12 | }
13 | .supress-division {
14 | border-radius: 10px/10px;
15 | border-radius: 10px/12px;
16 | border-radius: hello(10px/10px) world;
17 | font: 10px/30 sans-serif;
18 | font: 10px/20px sans-serif;
19 | font: 10px/22px sans-serif;
20 | border-radius: 0 15px 15px 15px/0 50% 50% 50%;
21 | }
22 | .parens {
23 | sub: 5;
24 | add: 15;
25 | div1: 2;
26 | div2: 2;
27 | mul: 50;
28 | }
29 | .keyword-names {
30 | height: "hello" 25;
31 | }
32 | .negation {
33 | neg1: -1px;
34 | neg2: -1px;
35 | neg3: -10;
36 | }
37 | .test {
38 | single1: 5;
39 | single2: 10;
40 | single3: 10;
41 | parens: 10 -2;
42 | math1: 20;
43 | math2: 20;
44 | complex1: 71;
45 | complex2: 6;
46 | complex3: 6px 1em 2px 2;
47 | var1: 8 4 4 4px;
48 | var2: 96;
49 | var3: 12;
50 | complex4: 113;
51 | }
52 | .percents {
53 | p1: 1000%;
54 | p2: 1000%;
55 | p3: 100%;
56 | p4: 1000px;
57 | p5: 1000%;
58 | p6: 30%;
59 | p7: 10%;
60 | p8: 2%;
61 | }
62 | .misc {
63 | x: 40px;
64 | y: 40em;
65 | }
66 | .cond {
67 | c1: false;
68 | c2: true;
69 | }
70 |
--------------------------------------------------------------------------------
/tests/outputs/media.css:
--------------------------------------------------------------------------------
1 | @media screen,3D {
2 | P {
3 | color: green;
4 | }
5 | }
6 | @media print {
7 | body {
8 | font-size: 10pt;
9 | }
10 | }
11 | @media screen {
12 | body {
13 | font-size: 13px;
14 | }
15 | }
16 | @media screen,print {
17 | body {
18 | line-height: 1.2;
19 | }
20 | }
21 | @media all and (min-width: 0px) {
22 | body {
23 | line-height: 1.2;
24 | }
25 | }
26 | @media all and (min-width: 0) {
27 | body {
28 | line-height: 1.2;
29 | }
30 | }
31 | @media screen and (min-width: 102.5em) and (max-width: 117.9375em),screen and (min-width: 150em) {
32 | body {
33 | color: blue;
34 | }
35 | }
36 | @media screen and (min-height: 110px) {
37 | body {
38 | color: red;
39 | }
40 | }
41 | @media screen and (height: 100px) and (width: 110px),(size: 120px) {
42 | body {
43 | color: red;
44 | }
45 | }
46 | @media test {
47 | div {
48 | height: 20px;
49 | }
50 | }
51 | @media test and (hello) {
52 | div {
53 | color: red;
54 | }
55 | div pre {
56 | color: orange;
57 | }
58 | }
59 | @media yeah {
60 | @page {
61 | @media cool {
62 | color: red;
63 | }
64 | }
65 | }
66 | @media (max-width: 599px) {
67 | .helloworld {
68 | color: blue;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/tests/outputs/misc.css:
--------------------------------------------------------------------------------
1 | color: "aaa, bbb";
2 | .topbar {
3 | background: url(/assets/images/test/topbar.png);
4 | }
5 | .hello {
6 | test: empty-function("/assets/images/test/",40%,to(#fff));
7 | }
8 | .css3 {
9 | background-image: -webkit-gradient(linear,0% 0%,0% 90%,from(#E9A000),to(#A37000));
10 | }
11 | .test,
12 | .world {
13 | border: 1px solid red;
14 | color: url(http://mage-page.com);
15 | string: "hello /* this is not a comment */";
16 | world: "// neither is this";
17 | string: 'hello /* this is not a comment */';
18 | world: '// neither is this';
19 | what-ever: 100px;
20 | background: url(/*no comment here*/);
21 | }
22 | .urls {
23 | background1: url("http://google.com");
24 | background2: url(http://google.com);
25 | background3: url("http://google.com");
26 | }
27 | .cool {
28 | color: "aaa, bbb";
29 | }
30 | .span-17 {
31 | float: left;
32 | }
33 | .span-17 {
34 | width: 660px;
35 | }
36 | .x {
37 | float: left;
38 | width: 660px;
39 | }
40 | .hi pre {
41 | color: red;
42 | }
43 | .hi pre {
44 | color: blue;
45 | }
46 | .rad pre {
47 | color: red;
48 | }
49 | .rad pre {
50 | color: blue;
51 | }
52 | hello {
53 | numbers: 1.0 0.1 .1 1.;
54 | numbers: 1.0s 0.1s .1s 1.s;
55 | numbers: -1s -0.1s -0.1s -1s;
56 | numbers: -1 -0.1 -0.1 -1;
57 | }
58 | #string {
59 | hello: 'what\'s going on here';
60 | hello: 'blah blag @{ blah blah';
61 | join: "3434hello";
62 | join: 3434hello;
63 | }
64 | .duplicates {
65 | hello: world;
66 | hello: "world";
67 | hello: "what";
68 | }
69 |
--------------------------------------------------------------------------------
/tests/outputs/mixin_functions.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 2.0em;
3 | color: red;
4 | margin: 10px;
5 | height: 12px;
6 | border-bottom: 1px solid green;
7 | }
8 |
--------------------------------------------------------------------------------
/tests/outputs/mixin_merging.css:
--------------------------------------------------------------------------------
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 | }
43 |
--------------------------------------------------------------------------------
/tests/outputs/mixins.css:
--------------------------------------------------------------------------------
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 {
17 | background-color: white;
18 | }
19 | #header a {
20 | color: orange;
21 | display: block;
22 | border: 1px solid black;
23 | background-color: grey;
24 | }
25 | #header a:hover {
26 | background-color: white;
27 | }
28 | div {
29 | color: blue;
30 | hello: world;
31 | }
32 | div b {
33 | color: blue;
34 | }
35 | body {
36 | color: blue;
37 | hello: world;
38 | }
39 | body b {
40 | color: blue;
41 | }
42 | .hello .world {
43 | color: blue;
44 | }
45 | .foobar {
46 | color: blue;
47 | }
48 | .eggs {
49 | foo: 1px 2px;
50 | bar: 1px 2px;
51 | foo: 100 land;
52 | bar: 100 land;
53 | }
54 | #hello {
55 | cool: one two three cool;
56 | }
57 | #hello-important {
58 | cool: one two three cool !important;
59 | }
60 | #world {
61 | cool: "world";
62 | }
63 | #another {
64 | things: red blue green;
65 | things: red blue green skip me;
66 | }
67 | #day .cool {
68 | color: one two three;
69 | }
70 | #day .cool {
71 | color: one two three skip me;
72 | }
73 | .mix-suffix {
74 | color: red !important;
75 | height: 20px !important;
76 | }
77 | .mix-suffix pre {
78 | color: red;
79 | }
80 | .search-test {
81 | color: #f00 !important;
82 | color: #0f0 !important;
83 | }
84 | .cowboy {
85 | color: blue;
86 | }
87 | .nav .nav-divider {
88 | padding: 10px;
89 | }
90 | .nav-divider {
91 | padding: 10px;
92 | }
93 |
--------------------------------------------------------------------------------
/tests/outputs/nested.css:
--------------------------------------------------------------------------------
1 | #header {
2 | color: black;
3 | }
4 | #header .navigation {
5 | font-size: 12px;
6 | }
7 | #header .navigation .border .outside {
8 | color: blue;
9 | }
10 | #header .logo {
11 | width: 300px;
12 | }
13 | #header .logo:hover {
14 | text-decoration: none;
15 | }
16 | a b ul li {
17 | color: green;
18 | }
19 | div .cool {
20 | color: green;
21 | }
22 | p .cool span {
23 | color: yellow;
24 | }
25 | div another {
26 | color: green;
27 | }
28 | p another span {
29 | color: yellow;
30 | }
31 | b .something {
32 | color: blue;
33 | }
34 | b.something {
35 | color: blue;
36 | }
37 | .foo .bar .qux,
38 | .foo .baz .qux {
39 | display: block;
40 | }
41 | .qux .foo .bar,
42 | .qux .foo .baz {
43 | display: inline;
44 | }
45 | .qux .foo .bar .biz,
46 | .qux .foo .baz .biz {
47 | display: none;
48 | }
49 | b hello [x="&yeah"] {
50 | color: red;
51 | }
52 |
--------------------------------------------------------------------------------
/tests/outputs/nesting.css:
--------------------------------------------------------------------------------
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; }
7 |
--------------------------------------------------------------------------------
/tests/outputs/pattern_matching.css:
--------------------------------------------------------------------------------
1 | .class {
2 | color: #a2a2a2;
3 | display: block;
4 | }
5 | .zero {
6 | zero: 0;
7 | one: 1;
8 | two: 2;
9 | three: 3;
10 | }
11 | .one {
12 | one: 1;
13 | one-req: 1;
14 | two: 2;
15 | three: 3;
16 | }
17 | .two {
18 | two: 2;
19 | three: 3;
20 | }
21 | .three {
22 | three-req: 3;
23 | three: 3;
24 | }
25 | .left {
26 | left: 1;
27 | }
28 | .right {
29 | right: 1;
30 | }
31 | .border-right {
32 | color: black;
33 | border-right: 4px;
34 | }
35 | .border-left {
36 | color: black;
37 | border-left: 4px;
38 | }
39 | .only-right {
40 | right: 33;
41 | }
42 | .only-left {
43 | left: 33;
44 | }
45 | .left-right {
46 | both: 330;
47 | }
48 | #hola {
49 | color: blue;
50 | }
51 | #defaults_1 {
52 | height: one;
53 | height: two;
54 | height: three;
55 | height: four;
56 | }
57 | .thing {
58 | color: red;
59 | }
60 | #aa {
61 | color: green;
62 | color: blue;
63 | color: red;
64 | }
65 | #bb {
66 | color: green;
67 | color: blue;
68 | color: red;
69 | }
70 | #cc {
71 | color: blue;
72 | }
73 |
--------------------------------------------------------------------------------
/tests/outputs/scopes.css:
--------------------------------------------------------------------------------
1 | body div other world {
2 | height: 50;
3 | }
4 | div other world {
5 | height: 50;
6 | }
7 | pre {
8 | height: 10;
9 | height: 11;
10 | height: 12;
11 | }
12 |
--------------------------------------------------------------------------------
/tests/outputs/selector_expressions.css:
--------------------------------------------------------------------------------
1 | something blue,
2 | world {
3 | color: blue;
4 | }
5 | .div (3434) {
6 | height: 100px;
7 | }
8 | .div cool red {
9 | height: 4000px;
10 | }
11 | .span5 {
12 | color: 15;
13 | }
14 | .span4 {
15 | color: 14;
16 | }
17 | .span3 {
18 | color: 13;
19 | }
20 | .span2 {
21 | color: 12;
22 | }
23 | .span1 {
24 | color: 11;
25 | }
26 |
--------------------------------------------------------------------------------
/tests/outputs/site_demos.css:
--------------------------------------------------------------------------------
1 | default .underline {
2 | border-bottom: 1px solid green;
3 | }
4 | default #header {
5 | color: black;
6 | border: 1px solid #dd44dd;
7 | }
8 | default #header .navigation {
9 | font-size: 12px;
10 | }
11 | default #header .navigation a {
12 | border-bottom: 1px solid green;
13 | }
14 | default #header .logo {
15 | width: 300px;
16 | }
17 | default #header .logo:hover {
18 | text-decoration: none;
19 | }
20 | variables .variables {
21 | width: 14cm;
22 | height: 24px;
23 | color: #888;
24 | background: #6c94be;
25 | font-family: "Trebuchet MS", Verdana, sans-serif;
26 | }
27 | mixins .bordered {
28 | border-top: dotted 1px black;
29 | border-bottom: solid 2px black;
30 | }
31 | mixins #menu a {
32 | color: #111;
33 | border-top: dotted 1px black;
34 | border-bottom: solid 2px black;
35 | }
36 | mixins .post a {
37 | color: red;
38 | border-top: dotted 1px black;
39 | border-bottom: solid 2px black;
40 | }
41 | nested-rules #header {
42 | color: black;
43 | }
44 | nested-rules #header .navigation {
45 | font-size: 12px;
46 | }
47 | nested-rules #header .logo {
48 | width: 300px;
49 | }
50 | nested-rules #header .logo:hover {
51 | text-decoration: none;
52 | }
53 | namespaces #bundle .button {
54 | display: block;
55 | border: 1px solid black;
56 | background-color: grey;
57 | }
58 | namespaces #bundle .button:hover {
59 | background-color: white;
60 | }
61 | namespaces #header a {
62 | color: orange;
63 | display: block;
64 | border: 1px solid black;
65 | background-color: grey;
66 | }
67 | namespaces #header a:hover {
68 | background-color: white;
69 | }
70 | mixin-functions body {
71 | padding: 2.0em;
72 | color: red;
73 | margin: 10px;
74 | height: 12px;
75 | border-bottom: 1px solid green;
76 | }
77 |
--------------------------------------------------------------------------------
/tests/outputs/variables.css:
--------------------------------------------------------------------------------
1 | outer1: 44px;
2 | outer2: 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: #888;
17 | border: 1px solid #3326cc;
18 | background: rgba(23,68,149,0.5);
19 | }
20 |
--------------------------------------------------------------------------------
/tests/outputs_lessjs/mixins-args.css:
--------------------------------------------------------------------------------
1 | #hidden {
2 | color: transparent;
3 | }
4 | #hidden1 {
5 | color: transparent;
6 | }
7 | .two-args {
8 | color: blue;
9 | width: 10px;
10 | height: 99%;
11 | border: 2px dotted black;
12 | }
13 | .one-arg {
14 | width: 15px;
15 | height: 49%;
16 | }
17 | .no-parens {
18 | width: 5px;
19 | height: 49%;
20 | }
21 | .no-args {
22 | width: 5px;
23 | height: 49%;
24 | }
25 | .var-args {
26 | width: 45;
27 | height: 17%;
28 | }
29 | .multi-mix {
30 | width: 10px;
31 | height: 29%;
32 | margin: 4;
33 | padding: 5;
34 | }
35 | body {
36 | padding: 30px;
37 | color: #f00;
38 | }
39 | .scope-mix {
40 | width: 8;
41 | }
42 | .content {
43 | width: 600px;
44 | }
45 | .content .column {
46 | margin: 600px;
47 | }
48 | #same-var-name {
49 | radius: 5px;
50 | }
51 | #var-inside {
52 | width: 10px;
53 | }
54 | .arguments {
55 | border: 1px solid black;
56 | width: 1px;
57 | }
58 | .arguments2 {
59 | border: 0px;
60 | width: 0px;
61 | }
62 | .arguments3 {
63 | border: 0px;
64 | width: 0px;
65 | }
66 | .arguments4 {
67 | border: 0 1 2 3 4;
68 | rest: 1 2 3 4;
69 | width: 0;
70 | }
71 | .edge-case {
72 | border: "{";
73 | width: "{";
74 | }
75 | .slash-vs-math {
76 | border-radius: 0.4px;
77 | border-radius: 0.5px;
78 | border-radius: 6px;
79 | }
80 | .comma-vs-semi-colon {
81 | one: a;
82 | two: b, c;
83 | one: d, e;
84 | two: f;
85 | one: g;
86 | one: h;
87 | one: i;
88 | one: j;
89 | one: k;
90 | two: l;
91 | one: m, n;
92 | one: o, p;
93 | two: q;
94 | one: r, s;
95 | two: t;
96 | }
97 | #named-conflict {
98 | four: a, 11, 12, 13;
99 | four: a, 21, 22, 23;
100 | }
101 | .test-mixin-default-arg {
102 | defaults: 1px 1px 1px;
103 | defaults: 2px 2px 2px;
104 | }
105 | .selector {
106 | margin: 2, 2, 2, 2;
107 | }
108 | .selector2 {
109 | margin: 2, 2, 2, 2;
110 | }
111 | .selector3 {
112 | margin: 4;
113 | }
114 |
--------------------------------------------------------------------------------
/tests/outputs_lessjs/mixins-named-args.css:
--------------------------------------------------------------------------------
1 | .named-arg {
2 | color: blue;
3 | width: 5px;
4 | height: 99%;
5 | args: 1px 100%;
6 | text-align: center;
7 | }
8 | .class {
9 | width: 5px;
10 | height: 19%;
11 | args: 1px 20%;
12 | }
13 | .all-args-wrong-args {
14 | width: 10px;
15 | height: 9%;
16 | args: 2px 10%;
17 | }
18 | .named-args2 {
19 | width: 15px;
20 | height: 49%;
21 | color: #646464;
22 | }
23 | .named-args3 {
24 | width: 5px;
25 | height: 29%;
26 | color: #123456;
27 | }
28 |
--------------------------------------------------------------------------------
/tests/outputs_lessjs/strings.css:
--------------------------------------------------------------------------------
1 | #strings {
2 | background-image: url("http://son-of-a-banana.com");
3 | quotes: "~" "~";
4 | content: "#*%:&^,)!.(~*})";
5 | empty: "";
6 | brackets: "{" "}";
7 | escapes: "\"hello\" \\world";
8 | escapes2: "\"llo";
9 | }
10 | #comments {
11 | content: "/* hello */ // not-so-secret";
12 | }
13 | #single-quote {
14 | quotes: "'" "'";
15 | content: '""#!&""';
16 | empty: '';
17 | semi-colon: ';';
18 | }
19 | #escaped {
20 | filter: DX.Transform.MS.BS.filter(opacity=50);
21 | }
22 | #one-line {
23 | image: url(http://tooks.com);
24 | }
25 | #crazy {
26 | image: url(http://), "}", url("http://}");
27 | }
28 | #interpolation {
29 | url: "http://lesscss.org/dev/image.jpg";
30 | url2: "http://lesscss.org/image-256.jpg";
31 | url3: "http://lesscss.org#445566";
32 | url4: "http://lesscss.org/hello";
33 | url5: "http://lesscss.org/54.4px";
34 | }
35 | .mix-mul-class {
36 | color: blue;
37 | color: red;
38 | color: black;
39 | color: orange;
40 | }
41 |
--------------------------------------------------------------------------------
/tests/sort.php:
--------------------------------------------------------------------------------
1 | coerceColor($value);
20 | }
21 |
22 | return parent::compileValue($value);
23 | }
24 | }
25 |
26 | class SortingFormatter extends lessc_formatter_lessjs {
27 | public function sortKey($block) {
28 | if (!isset($block->sortKey)) {
29 | sort($block->selectors, SORT_STRING);
30 | $block->sortKey = implode(",", $block->selectors);
31 | }
32 |
33 | return $block->sortKey;
34 | }
35 |
36 | public function sortBlock($block) {
37 | usort($block->children, function($a, $b) {
38 | $sort = strcmp($this->sortKey($a), $this->sortKey($b));
39 | if ($sort == 0) {
40 | // TODO
41 | }
42 | return $sort;
43 | });
44 |
45 | }
46 |
47 | public function block($block) {
48 | $this->sortBlock($block);
49 | return parent::block($block);
50 | }
51 |
52 | }
53 |
54 | $less = new lesscNormalized();
55 | $less->setFormatter(new SortingFormatter);
56 | echo $less->parse(file_get_contents($fname));
57 |
--------------------------------------------------------------------------------