├── .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 | [![Build Status](https://travis-ci.org/leafo/lessphp.svg?branch=master)](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 | --------------------------------------------------------------------------------