Use less.php instead of lesserphp
This commit is contained in:
parent
e7335d7f77
commit
0ebc1387f4
@ -87,7 +87,7 @@ This source code includes:
|
||||
|
||||
* [phpqrcode](https://github.com/t0k4rt/phpqrcode) to generate QR codes
|
||||
* [Ubuntu font for the Web](https://github.com/earaujoassis/ubuntu-fontface)
|
||||
* [lesserphp](https://github.com/MarcusSchwarz/lesserphp) to compile [Less](http://lesscss.org)
|
||||
* [less.php](https://github.com/wikimedia/less.php) to compile [Less](http://lesscss.org)
|
||||
|
||||
## License
|
||||
|
||||
|
@ -87,7 +87,7 @@ Ce code source inclus :
|
||||
|
||||
* [phpqrcode](https://github.com/t0k4rt/phpqrcode) pour générer les codes QR
|
||||
* [La police Ubuntu pour le Web](https://github.com/earaujoassis/ubuntu-fontface)
|
||||
* [lesserphp](https://github.com/MarcusSchwarz/lesserphp) pour compiler le [Less](http://lesscss.org)
|
||||
* [less.php](https://github.com/wikimedia/less.php) pour compiler le [Less](http://lesscss.org)
|
||||
|
||||
## Licence
|
||||
|
||||
|
@ -101,7 +101,7 @@ if (badQuery()) {
|
||||
// Then delete it
|
||||
unlink("temp/style.min.css");
|
||||
|
||||
require "lesserphp/lessc.inc.php";
|
||||
require "less.php/lessc.inc.php";
|
||||
$less = new lessc;
|
||||
$less->setVariables($variablesTheme); // Make these colors available in style.less
|
||||
$less->setFormatter("compressed");
|
||||
|
70
less.php/CHANGES.md
Normal file
70
less.php/CHANGES.md
Normal file
@ -0,0 +1,70 @@
|
||||
# 3.1.0
|
||||
- [All Changes](https://github.com/wikimedia/less.php/compare/v3.0.0...v3.1.0)
|
||||
* PHP 8.0 support: Drop use of curly braces for sub-string eval (James D. Forrester)
|
||||
* Make `Directive::__construct` $rules arg optional (fix PHP 7.4 warning) (Sam Reed)
|
||||
* ProcessExtends: Improve performance by using a map for selectors and parents (Andrey Legayev)
|
||||
* build: Run CI tests on PHP 8.0 too (James D. Forrester)
|
||||
* code: Fix PSR12.Properties.ConstantVisibility.NotFound (Sam Reed)
|
||||
|
||||
# 3.0.0
|
||||
- [All Changes](https://github.com/wikimedia/less.php/compare/v2.0.0...v3.0.0)
|
||||
- Raise PHP requirement from 7.1 to 7.2.9 (James Forrester)
|
||||
- build: Upgrade phpunit to ^8.5 and make pass (James Forrester)
|
||||
- build: Install php-parallel-lint (James Forrester)
|
||||
- build: Install minus-x and make pass (James Forrester)
|
||||
|
||||
# 2.0.0
|
||||
- [All Changes](https://github.com/wikimedia/less.php/compare/1.8.2...v2.0.0)
|
||||
- Relax PHP requirement down to 7.1, from 7.2.9 (Franz Liedke)
|
||||
- Reflect recent breaking changes properly with the semantic versioning (James Forrester)
|
||||
|
||||
# 1.8.2
|
||||
- [All Changes](https://github.com/wikimedia/less.php/compare/1.8.1...1.8.2)
|
||||
- Require PHP 7.2.9+, up from 5.3+ (James Forrester)
|
||||
- Release: Update Version.php with the current release ID (COBadger)
|
||||
- Fix access array offset on value of type null (Michele Locati)
|
||||
- Fixed test suite on PHP 7.4 (Sergei Morozov)
|
||||
- docs: Fix 1.8.1 "All changes" link (Timo Tijhof)
|
||||
|
||||
# 1.8.1
|
||||
- [All Changes](https://github.com/wikimedia/less.php/compare/v1.8.0...1.8.1)
|
||||
- Another PHP 7.3 compatibility tweak
|
||||
|
||||
# 1.8.0
|
||||
- [All Changes](https://github.com/Asenar/less.php/compare/v1.7.0.13...v1.8.0)
|
||||
- Wikimedia fork
|
||||
- Supports up to PHP 7.3
|
||||
- No longer tested against PHP 5, though it's still remains allowed in `composer.json` for HHVM compatibility
|
||||
- Switched to [semantic versioning](https://semver.org/), hence version numbers now use 3 digits
|
||||
|
||||
# 1.7.0.13
|
||||
- [All Changes](https://github.com/Asenar/less.php/compare/v1.7.0.12...v1.7.0.13)
|
||||
- Fix composer.json (PSR-4 was invalid)
|
||||
|
||||
# 1.7.0.12
|
||||
- [All Changes](https://github.com/Asenar/less.php/compare/v1.7.0.11...v1.7.0.12)
|
||||
- set bin/lessc bit executable
|
||||
- Add 'gettingVariables' method in Less_Parser
|
||||
|
||||
# 1.7.0.11
|
||||
- [All Changes](https://github.com/Asenar/less.php/compare/v1.7.0.10...v1.7.0.11)
|
||||
- Fix realpath issue (windows)
|
||||
- Set Less_Tree_Call property back to public ( Fix 258 266 267 issues from oyejorge/less.php)
|
||||
|
||||
# 1.7.0.10
|
||||
|
||||
- [All Changes](https://github.com/oyejorge/less.php/compare/v1.7.0.9...v1.7.10)
|
||||
- Add indentation option
|
||||
- Add 'optional' modifier for @import
|
||||
- fix $color in Exception messages
|
||||
- don't use set_time_limit when running cli
|
||||
- take relative-url into account when building the cache filename
|
||||
- urlArgs should be string no array()
|
||||
- add bug-report fixtures [#6dc898f](https://github.com/oyejorge/less.php/commit/6dc898f5d75b447464906bdf19d79c2e19d95e33)
|
||||
- fix #269, missing on NameValue type [#a8dac63](https://github.com/oyejorge/less.php/commit/a8dac63d93fb941c54fb78b12588abf635747c1b)
|
||||
|
||||
# 1.7.0.9
|
||||
|
||||
- [All Changes](https://github.com/oyejorge/less.php/compare/v1.7.0.8...v1.7.0.9)
|
||||
- Remove space at beginning of Version.php
|
||||
- Revert require() paths in test interface
|
178
less.php/LICENSE
Normal file
178
less.php/LICENSE
Normal file
@ -0,0 +1,178 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
315
less.php/README.md
Normal file
315
less.php/README.md
Normal file
@ -0,0 +1,315 @@
|
||||
[![Continuous Integration](https://github.com/wikimedia/less.php/workflows/PHP%20Test/badge.svg)](https://github.com/wikimedia/less.php/actions)
|
||||
|
||||
[Less.php](http://lessphp.typesettercms.com)
|
||||
========
|
||||
|
||||
This is the Wikimedia fork of a PHP port of the official LESS processor <http://lesscss.org>.
|
||||
|
||||
* [About](#about)
|
||||
* [Installation](#installation)
|
||||
* [Basic Use](#basic-use)
|
||||
* [Caching](#caching)
|
||||
* [Source Maps](#source-maps)
|
||||
* [Command Line](#command-line)
|
||||
* [Integration with other projects](#integration-with-other-projects)
|
||||
* [Transitioning from Leafo/lessphp](#transitioning-from-leafolessphp)
|
||||
* [Credits](#credits)
|
||||
|
||||
|
||||
|
||||
About
|
||||
---
|
||||
The code structure of less.php mirrors that of the official processor which helps us ensure compatibility and allows for easy maintenance.
|
||||
|
||||
Please note, there are a few unsupported LESS features:
|
||||
|
||||
- Evaluation of JavaScript expressions within back-ticks (for obvious reasons).
|
||||
- Definition of custom functions.
|
||||
|
||||
|
||||
Installation
|
||||
---
|
||||
|
||||
You can install the library with Composer or manually.
|
||||
|
||||
#### Composer
|
||||
|
||||
1. [Install Composer](https://getcomposer.org/download/)
|
||||
2. Run `composer require wikimedia/less.php`
|
||||
|
||||
#### Manually From Release
|
||||
|
||||
Step 1. [Download a release](https://github.com/wikimedia/less.php/releases) and upload the PHP files to your server.
|
||||
|
||||
Step 2. Include the library:
|
||||
|
||||
```php
|
||||
require_once '[path to less.php]/lib/Less/Autoloader.php';
|
||||
Less_Autoloader::register();
|
||||
```
|
||||
|
||||
Basic Use
|
||||
---
|
||||
|
||||
#### Parsing Strings
|
||||
|
||||
```php
|
||||
$parser = new Less_Parser();
|
||||
$parser->parse( '@color: #4D926F; #header { color: @color; } h2 { color: @color; }' );
|
||||
$css = $parser->getCss();
|
||||
```
|
||||
|
||||
|
||||
#### Parsing LESS Files
|
||||
The parseFile() function takes two arguments:
|
||||
|
||||
1. The absolute path of the .less file to be parsed
|
||||
2. The url root to prepend to any relative image or @import urls in the .less file.
|
||||
|
||||
```php
|
||||
$parser = new Less_Parser();
|
||||
$parser->parseFile( '/var/www/mysite/bootstrap.less', 'http://example.com/mysite/' );
|
||||
$css = $parser->getCss();
|
||||
```
|
||||
|
||||
|
||||
#### Handling Invalid LESS
|
||||
An exception will be thrown if the compiler encounters invalid LESS.
|
||||
|
||||
```php
|
||||
try{
|
||||
$parser = new Less_Parser();
|
||||
$parser->parseFile( '/var/www/mysite/bootstrap.less', 'http://example.com/mysite/' );
|
||||
$css = $parser->getCss();
|
||||
}catch(Exception $e){
|
||||
$error_message = $e->getMessage();
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### Parsing Multiple Sources
|
||||
less.php can parse multiple sources to generate a single CSS file.
|
||||
|
||||
```php
|
||||
$parser = new Less_Parser();
|
||||
$parser->parseFile( '/var/www/mysite/bootstrap.less', '/mysite/' );
|
||||
$parser->parse( '@color: #4D926F; #header { color: @color; } h2 { color: @color; }' );
|
||||
$css = $parser->getCss();
|
||||
```
|
||||
|
||||
#### Getting Info About The Parsed Files
|
||||
less.php can tell you which .less files were imported and parsed.
|
||||
|
||||
```php
|
||||
$parser = new Less_Parser();
|
||||
$parser->parseFile( '/var/www/mysite/bootstrap.less', '/mysite/' );
|
||||
$css = $parser->getCss();
|
||||
$imported_files = $parser->allParsedFiles();
|
||||
```
|
||||
|
||||
|
||||
#### Compressing Output
|
||||
You can tell less.php to remove comments and whitespace to generate minimized CSS files.
|
||||
|
||||
```php
|
||||
$options = array( 'compress'=>true );
|
||||
$parser = new Less_Parser( $options );
|
||||
$parser->parseFile( '/var/www/mysite/bootstrap.less', '/mysite/' );
|
||||
$css = $parser->getCss();
|
||||
```
|
||||
|
||||
#### Getting Variables
|
||||
You can use the getVariables() method to get an all variables defined and
|
||||
their value in a php associative array. Note that LESS has to be previously
|
||||
compiled.
|
||||
```php
|
||||
$parser = new Less_Parser;
|
||||
$parser->parseFile( '/var/www/mysite/bootstrap.less');
|
||||
$css = $parser->getCss();
|
||||
$variables = $parser->getVariables();
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### Setting Variables
|
||||
You can use the ModifyVars() method to customize your CSS if you have variables stored in PHP associative arrays.
|
||||
|
||||
```php
|
||||
$parser = new Less_Parser();
|
||||
$parser->parseFile( '/var/www/mysite/bootstrap.less', '/mysite/' );
|
||||
$parser->ModifyVars( array('font-size-base'=>'16px') );
|
||||
$css = $parser->getCss();
|
||||
```
|
||||
|
||||
|
||||
#### Import Directories
|
||||
By default, less.php will look for @imports in the directory of the file passed to parseFile().
|
||||
If you're using parse() or if @imports reside in different directories, you can tell less.php where to look.
|
||||
|
||||
```php
|
||||
$directories = array( '/var/www/mysite/bootstrap/' => '/mysite/bootstrap/' );
|
||||
$parser = new Less_Parser();
|
||||
$parser->SetImportDirs( $directories );
|
||||
$parser->parseFile( '/var/www/mysite/theme.less', '/mysite/' );
|
||||
$css = $parser->getCss();
|
||||
```
|
||||
|
||||
|
||||
Caching
|
||||
---
|
||||
Compiling LESS code into CSS is a time consuming process, caching your results is highly recommended.
|
||||
|
||||
|
||||
#### Caching CSS
|
||||
Use the Less_Cache class to save and reuse the results of compiled LESS files.
|
||||
This method will check the modified time and size of each LESS file (including imported files) and regenerate a new CSS file when changes are found.
|
||||
Note: When changes are found, this method will return a different file name for the new cached content.
|
||||
|
||||
```php
|
||||
$less_files = array( '/var/www/mysite/bootstrap.less' => '/mysite/' );
|
||||
$options = array( 'cache_dir' => '/var/www/writable_folder' );
|
||||
$css_file_name = Less_Cache::Get( $less_files, $options );
|
||||
$compiled = file_get_contents( '/var/www/writable_folder/'.$css_file_name );
|
||||
```
|
||||
|
||||
#### Caching CSS With Variables
|
||||
Passing options to Less_Cache::Get()
|
||||
|
||||
```php
|
||||
$less_files = array( '/var/www/mysite/bootstrap.less' => '/mysite/' );
|
||||
$options = array( 'cache_dir' => '/var/www/writable_folder' );
|
||||
$variables = array( 'width' => '100px' );
|
||||
$css_file_name = Less_Cache::Get( $less_files, $options, $variables );
|
||||
$compiled = file_get_contents( '/var/www/writable_folder/'.$css_file_name );
|
||||
```
|
||||
|
||||
|
||||
#### Parser Caching
|
||||
less.php will save serialized parser data for each .less file if a writable folder is passed to the SetCacheDir() method.
|
||||
Note: This feature only caches intermediate parsing results to improve the performance of repeated CSS generation.
|
||||
Your application should cache any CSS generated by less.php.
|
||||
|
||||
```php
|
||||
$options = array('cache_dir'=>'/var/www/writable_folder');
|
||||
$parser = new Less_Parser( $options );
|
||||
$parser->parseFile( '/var/www/mysite/bootstrap.less', '/mysite/' );
|
||||
$css = $parser->getCss();
|
||||
```
|
||||
|
||||
You can specify the caching technique used by changing the ```cache_method``` option. Supported methods are:
|
||||
* ```php```: Creates valid PHP files which can be included without any changes (default method).
|
||||
* ```var_export```: Like "php", but using PHP's ```var_export()``` function without any optimizations.
|
||||
It's recommended to use "php" instead.
|
||||
* ```serialize```: Faster, but pretty memory-intense.
|
||||
* ```callback```: Use custom callback functions to implement your own caching method. Give the "cache_callback_get" and
|
||||
"cache_callback_set" options with callables (see PHP's ```call_user_func()``` and ```is_callable()``` functions). less.php
|
||||
will pass the parser object (class ```Less_Parser```), the path to the parsed .less file ("/some/path/to/file.less") and
|
||||
an identifier that will change every time the .less file is modified. The ```get``` callback must return the ruleset
|
||||
(an array with ```Less_Tree``` objects) provided as fourth parameter of the ```set``` callback. If something goes wrong,
|
||||
return ```NULL``` (cache doesn't exist) or ```FALSE```.
|
||||
|
||||
|
||||
|
||||
Source Maps
|
||||
---
|
||||
Less.php supports v3 sourcemaps
|
||||
|
||||
#### Inline
|
||||
The sourcemap will be appended to the generated CSS file.
|
||||
|
||||
```php
|
||||
$options = array( 'sourceMap' => true );
|
||||
$parser = new Less_Parser($options);
|
||||
$parser->parseFile( '/var/www/mysite/bootstrap.less', '/mysite/' );
|
||||
$css = $parser->getCss();
|
||||
```
|
||||
|
||||
#### Saving to Map File
|
||||
|
||||
```php
|
||||
$options = array(
|
||||
'sourceMap' => true,
|
||||
'sourceMapWriteTo' => '/var/www/mysite/writable_folder/filename.map',
|
||||
'sourceMapURL' => '/mysite/writable_folder/filename.map',
|
||||
);
|
||||
$parser = new Less_Parser($options);
|
||||
$parser->parseFile( '/var/www/mysite/bootstrap.less', '/mysite/' );
|
||||
$css = $parser->getCss();
|
||||
```
|
||||
|
||||
|
||||
Command line
|
||||
---
|
||||
An additional script has been included to use the compiler from the command line.
|
||||
In the simplest invocation, you specify an input file and the compiled CSS is written to standard out:
|
||||
|
||||
```
|
||||
$ lessc input.less > output.css
|
||||
```
|
||||
|
||||
By using the -w flag you can watch a specified input file and have it compile as needed to the output file:
|
||||
|
||||
```
|
||||
$ lessc -w input.less output.css
|
||||
```
|
||||
|
||||
Errors from watch mode are written to standard out.
|
||||
|
||||
For more help, run `lessc --help`
|
||||
|
||||
|
||||
Integration with other projects
|
||||
---
|
||||
|
||||
#### Drupal 7
|
||||
|
||||
This library can be used as drop-in replacement of lessphp to work with [Drupal 7 less module](https://drupal.org/project/less).
|
||||
|
||||
How to install:
|
||||
|
||||
1. [Download the less.php source code](https://github.com/wikimedia/less.php/archive/master.zip) and unzip it so that 'lessc.inc.php' is located at 'sites/all/libraries/lessphp/lessc.inc.php'.
|
||||
2. Download and install [Drupal 7 less module](https://drupal.org/project/less) as usual.
|
||||
3. That's it :)
|
||||
|
||||
#### JBST WordPress theme
|
||||
|
||||
JBST has a built-in LESS compiler based on lessphp. Customize your WordPress theme with LESS.
|
||||
|
||||
How to use / install:
|
||||
|
||||
1. [Download the latest release](https://github.com/bassjobsen/jamedo-bootstrap-start-theme) copy the files to your {wordpress/}wp-content/themes folder and activate it.
|
||||
2. Find the compiler under Appearance > LESS Compiler in your WordPress dashboard
|
||||
3. Enter your LESS code in the text area and press (re)compile
|
||||
|
||||
Use the built-in compiler to:
|
||||
- set any [Bootstrap](http://getbootstrap.com/customize/) variable or use Bootstrap's mixins:
|
||||
-`@navbar-default-color: blue;`
|
||||
- create a custom button: `.btn-custom {
|
||||
.button-variant(white; red; blue);
|
||||
}`
|
||||
- set any built-in LESS variable: for example `@footer_bg_color: black;` sets the background color of the footer to black
|
||||
- use built-in mixins: - add a custom font: `.include-custom-font(@family: arial,@font-path, @path: @custom-font-dir, @weight: normal, @style: normal);`
|
||||
|
||||
The compiler can also be downloaded as [plugin](http://wordpress.org/plugins/wp-less-to-css/)
|
||||
|
||||
#### WordPress
|
||||
|
||||
This simple plugin will simply make the library available to other plugins and themes and can be used as a dependency using the [TGM Library](http://tgmpluginactivation.com/)
|
||||
|
||||
How to install:
|
||||
|
||||
1. Install the plugin from your WordPress Dashboard: http://wordpress.org/plugins/lessphp/
|
||||
2. That's it :)
|
||||
|
||||
|
||||
Transitioning from Leafo/lessphp
|
||||
---
|
||||
Projects looking for an easy transition from leafo/lessphp can use the lessc.inc.php adapter. To use, [Download the less.php source code](https://github.com/wikimedia/less.php/archive/master.zip) and unzip the files into your project so that the new 'lessc.inc.php' replaces the existing 'lessc.inc.php'.
|
||||
|
||||
Note, the 'setPreserveComments' will no longer have any effect on the compiled LESS.
|
||||
|
||||
Credits
|
||||
---
|
||||
less.php was originally ported to PHP by [Matt Agar](https://github.com/agar) and then updated by [Martin Jantošovič](https://github.com/Mordred). This Wikimedia-maintained fork was split off from [Josh Schmidt's version](https://github.com/oyejorge/less.php).
|
||||
|
191
less.php/bin/lessc
Executable file
191
less.php/bin/lessc
Executable file
@ -0,0 +1,191 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
require_once dirname(__FILE__) . '/../lib/Less/Autoloader.php';
|
||||
Less_Autoloader::register();
|
||||
|
||||
// Create our environment
|
||||
$env = array('compress' => false, 'relativeUrls' => false);
|
||||
$silent = false;
|
||||
$watch = false;
|
||||
$rootpath = '';
|
||||
|
||||
// Check for arguments
|
||||
array_shift($argv);
|
||||
if (!count($argv)) {
|
||||
$argv[] = '-h';
|
||||
}
|
||||
|
||||
// parse arguments
|
||||
foreach ($argv as $key => $arg) {
|
||||
if (preg_match('/^--?([a-z][0-9a-z-]*)(?:=([^\s]+))?$/i', $arg, $matches)) {
|
||||
$option = $matches[1];
|
||||
$value = isset($matches[2]) ? $matches[2] : false;
|
||||
unset($argv[$key]);
|
||||
|
||||
switch ($option) {
|
||||
case 'h':
|
||||
case 'help':
|
||||
echo <<<EOD
|
||||
Usage: lessc [options] sources [destination]
|
||||
|
||||
-h, --help Print help (this message) and exit.
|
||||
-s, --silent Suppress output of error messages.
|
||||
-v, --version Print version number and exit.
|
||||
-x, --compress Compress output by removing some whitespaces.
|
||||
--include-path=PATHS Set include paths. Separated by `:'. Use `;' on Windows.
|
||||
--strict-imports Force evaluation of imports.
|
||||
-sm=on|off Turn on or off strict math, where in strict mode, math
|
||||
--strict-math=on|off requires brackets. This option may default to on and then
|
||||
be removed in the future.
|
||||
-su=on|off Allow mixed units, e.g. 1px+1em or 1px*1px which have units
|
||||
--strict-units=on|off that cannot be represented.
|
||||
-ru, --relative-urls re-write relative urls to the base less file.
|
||||
-rp, --rootpath=URL Set rootpath for url rewriting in relative imports and urls.
|
||||
Works with or without the relative-urls option.
|
||||
-w, --watch Watch input files for changes.
|
||||
|
||||
|
||||
EOD;
|
||||
exit;
|
||||
case 's':
|
||||
case 'silent':
|
||||
$silent = true;
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
case 'watch':
|
||||
$watch = true;
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
case 'version':
|
||||
echo "lessc " . Less_Version::version . " (less.php)\n\n";
|
||||
exit;
|
||||
|
||||
case 'rp':
|
||||
case 'rootpath':
|
||||
$rootpath = $value;
|
||||
break;
|
||||
|
||||
|
||||
//parser options
|
||||
case 'compress':
|
||||
$env['compress'] = true;
|
||||
break;
|
||||
|
||||
case 'ru':
|
||||
case 'relative-urls':
|
||||
$env['relativeUrls'] = true;
|
||||
break;
|
||||
|
||||
case 'su':
|
||||
case 'strict-units':
|
||||
$env['strictUnits'] = ($value === 'on');
|
||||
break;
|
||||
|
||||
case 'sm':
|
||||
case 'strict-math':
|
||||
$env['strictMath'] = ($value === 'on');
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
case 'include-path':
|
||||
$env['import_dirs'] = preg_split('#;|\:#', $value);
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($argv) > 1) {
|
||||
$output = array_pop($argv);
|
||||
$inputs = $argv;
|
||||
}
|
||||
else {
|
||||
$inputs = $argv;
|
||||
$output = false;
|
||||
}
|
||||
|
||||
if (!count($inputs)) {
|
||||
echo("lessc: no input files\n");
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($watch) {
|
||||
if (!$output) {
|
||||
echo("lessc: you must specify the output file if --watch is given\n");
|
||||
exit;
|
||||
}
|
||||
|
||||
$lastAction = 0;
|
||||
|
||||
echo("lessc: watching input files\n");
|
||||
|
||||
while (1) {
|
||||
clearstatcache();
|
||||
|
||||
$updated = false;
|
||||
foreach ($inputs as $input) {
|
||||
if ($input == '-') {
|
||||
if (count($inputs) == 1) {
|
||||
echo("lessc: during watching files is not possible to watch stdin\n");
|
||||
exit;
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (filemtime($input) > $lastAction) {
|
||||
$updated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($updated) {
|
||||
$lastAction = time();
|
||||
$parser = new Less_Parser($env);
|
||||
foreach ($inputs as $input) {
|
||||
try {
|
||||
$parser->parseFile($input, $rootpath);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
echo("lessc: " . $e->getMessage() . " \n");
|
||||
continue; // Invalid processing
|
||||
}
|
||||
}
|
||||
|
||||
file_put_contents($output, $parser->getCss());
|
||||
echo("lessc: output file recompiled\n");
|
||||
}
|
||||
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$parser = new Less_Parser($env);
|
||||
foreach ($inputs as $input) {
|
||||
if ($input == '-') {
|
||||
$content = file_get_contents('php://stdin');
|
||||
$parser->parse($content);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
$parser->parseFile($input);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
if (!$silent) {
|
||||
echo("lessc: " . ((string)$e) . " \n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($output) {
|
||||
file_put_contents($output, $parser->getCss());
|
||||
}
|
||||
else {
|
||||
echo $parser->getCss();
|
||||
}
|
||||
}
|
49
less.php/composer.json
Normal file
49
less.php/composer.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "wikimedia/less.php",
|
||||
"description": "PHP port of the Javascript version of LESS http://lesscss.org (Originally maintained by Josh Schmidt)",
|
||||
"keywords": [ "less", "css", "php", "stylesheet", "less.js", "lesscss" ],
|
||||
"license": "Apache-2.0",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Josh Schmidt",
|
||||
"homepage": "https://github.com/oyejorge"
|
||||
},
|
||||
{
|
||||
"name": "Matt Agar",
|
||||
"homepage": "https://github.com/agar"
|
||||
},
|
||||
{
|
||||
"name": "Martin Jantošovič",
|
||||
"homepage": "https://github.com/Mordred"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.2.9"
|
||||
},
|
||||
"require-dev": {
|
||||
"mediawiki/mediawiki-codesniffer": "34.0.0",
|
||||
"mediawiki/minus-x": "1.0.0",
|
||||
"php-parallel-lint/php-console-highlighter": "0.5.0",
|
||||
"php-parallel-lint/php-parallel-lint": "1.2.0",
|
||||
"phpunit/phpunit": "^8.5"
|
||||
},
|
||||
"scripts": {
|
||||
"test": [
|
||||
"parallel-lint . --exclude vendor",
|
||||
"phpcs -sp",
|
||||
"phpunit",
|
||||
"minus-x check ."
|
||||
],
|
||||
"fix": [
|
||||
"minus-x fix .",
|
||||
"phpcbf"
|
||||
]
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": { "Less": "lib/" },
|
||||
"classmap": ["lessc.inc.php"]
|
||||
},
|
||||
"bin": [
|
||||
"bin/lessc"
|
||||
]
|
||||
}
|
274
less.php/lessc.inc.php
Normal file
274
less.php/lessc.inc.php
Normal file
@ -0,0 +1,274 @@
|
||||
<?php
|
||||
/**
|
||||
* This file provides the part of lessphp API (https://github.com/leafo/lessphp)
|
||||
* to be a drop-in replacement for following products:
|
||||
* - Drupal 7, by the less module v3.0+ (https://drupal.org/project/less)
|
||||
* - Symfony 2
|
||||
*/
|
||||
|
||||
// Register autoloader for non-composer installations
|
||||
if ( !class_exists( 'Less_Parser' ) ) {
|
||||
require_once __DIR__ . '/lib/Less/Autoloader.php';
|
||||
Less_Autoloader::register();
|
||||
}
|
||||
|
||||
class lessc {
|
||||
|
||||
static public $VERSION = Less_Version::less_version;
|
||||
|
||||
public $importDir = '';
|
||||
protected $allParsedFiles = array();
|
||||
protected $libFunctions = array();
|
||||
protected $registeredVars = array();
|
||||
private $formatterName;
|
||||
private $options = array();
|
||||
|
||||
public function __construct( $lessc = null, $sourceName = null ) {
|
||||
}
|
||||
|
||||
public function setImportDir( $dirs ) {
|
||||
$this->importDir = (array)$dirs;
|
||||
}
|
||||
|
||||
public function addImportDir( $dir ) {
|
||||
$this->importDir = (array)$this->importDir;
|
||||
$this->importDir[] = $dir;
|
||||
}
|
||||
|
||||
public function setFormatter( $name ) {
|
||||
$this->formatterName = $name;
|
||||
}
|
||||
|
||||
public function setPreserveComments( $preserve ) {
|
||||
}
|
||||
|
||||
public function registerFunction( $name, $func ) {
|
||||
$this->libFunctions[$name] = $func;
|
||||
}
|
||||
|
||||
public function unregisterFunction( $name ) {
|
||||
unset( $this->libFunctions[$name] );
|
||||
}
|
||||
|
||||
public function setVariables( $variables ) {
|
||||
foreach ( $variables as $name => $value ) {
|
||||
$this->setVariable( $name, $value );
|
||||
}
|
||||
}
|
||||
|
||||
public function setVariable( $name, $value ) {
|
||||
$this->registeredVars[$name] = $value;
|
||||
}
|
||||
|
||||
public function unsetVariable( $name ) {
|
||||
unset( $this->registeredVars[$name] );
|
||||
}
|
||||
|
||||
public function setOptions( $options ) {
|
||||
foreach ( $options as $name => $value ) {
|
||||
$this->setOption( $name, $value );
|
||||
}
|
||||
}
|
||||
|
||||
public function setOption( $name, $value ) {
|
||||
$this->options[$name] = $value;
|
||||
}
|
||||
|
||||
public function parse( $buffer, $presets = array() ) {
|
||||
$this->setVariables( $presets );
|
||||
|
||||
$parser = new Less_Parser( $this->getOptions() );
|
||||
$parser->setImportDirs( $this->getImportDirs() );
|
||||
foreach ( $this->libFunctions as $name => $func ) {
|
||||
$parser->registerFunction( $name, $func );
|
||||
}
|
||||
$parser->parse( $buffer );
|
||||
if ( count( $this->registeredVars ) ) {
|
||||
$parser->ModifyVars( $this->registeredVars );
|
||||
}
|
||||
|
||||
return $parser->getCss();
|
||||
}
|
||||
|
||||
protected function getOptions() {
|
||||
$options = array( 'relativeUrls' => false );
|
||||
switch ( $this->formatterName ) {
|
||||
case 'compressed':
|
||||
$options['compress'] = true;
|
||||
break;
|
||||
}
|
||||
if ( is_array( $this->options ) ) {
|
||||
$options = array_merge( $options, $this->options );
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
protected function getImportDirs() {
|
||||
$dirs_ = (array)$this->importDir;
|
||||
$dirs = array();
|
||||
foreach ( $dirs_ as $dir ) {
|
||||
$dirs[$dir] = '';
|
||||
}
|
||||
return $dirs;
|
||||
}
|
||||
|
||||
public function compile( $string, $name = null ) {
|
||||
$oldImport = $this->importDir;
|
||||
$this->importDir = (array)$this->importDir;
|
||||
|
||||
$this->allParsedFiles = array();
|
||||
|
||||
$parser = new Less_Parser( $this->getOptions() );
|
||||
$parser->SetImportDirs( $this->getImportDirs() );
|
||||
if ( count( $this->registeredVars ) ) {
|
||||
$parser->ModifyVars( $this->registeredVars );
|
||||
}
|
||||
foreach ( $this->libFunctions as $name => $func ) {
|
||||
$parser->registerFunction( $name, $func );
|
||||
}
|
||||
$parser->parse( $string );
|
||||
$out = $parser->getCss();
|
||||
|
||||
$parsed = Less_Parser::AllParsedFiles();
|
||||
foreach ( $parsed as $file ) {
|
||||
$this->addParsedFile( $file );
|
||||
}
|
||||
|
||||
$this->importDir = $oldImport;
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function compileFile( $fname, $outFname = null ) {
|
||||
if ( !is_readable( $fname ) ) {
|
||||
throw new Exception( 'load error: failed to find '.$fname );
|
||||
}
|
||||
|
||||
$pi = pathinfo( $fname );
|
||||
|
||||
$oldImport = $this->importDir;
|
||||
|
||||
$this->importDir = (array)$this->importDir;
|
||||
$this->importDir[] = Less_Parser::AbsPath( $pi['dirname'] ).'/';
|
||||
|
||||
$this->allParsedFiles = array();
|
||||
$this->addParsedFile( $fname );
|
||||
|
||||
$parser = new Less_Parser( $this->getOptions() );
|
||||
$parser->SetImportDirs( $this->getImportDirs() );
|
||||
if ( count( $this->registeredVars ) ) {
|
||||
$parser->ModifyVars( $this->registeredVars );
|
||||
}
|
||||
foreach ( $this->libFunctions as $name => $func ) {
|
||||
$parser->registerFunction( $name, $func );
|
||||
}
|
||||
$parser->parseFile( $fname );
|
||||
$out = $parser->getCss();
|
||||
|
||||
$parsed = Less_Parser::AllParsedFiles();
|
||||
foreach ( $parsed as $file ) {
|
||||
$this->addParsedFile( $file );
|
||||
}
|
||||
|
||||
$this->importDir = $oldImport;
|
||||
|
||||
if ( $outFname !== null ) {
|
||||
return file_put_contents( $outFname, $out );
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function checkedCompile( $in, $out ) {
|
||||
if ( !is_file( $out ) || filemtime( $in ) > filemtime( $out ) ) {
|
||||
$this->compileFile( $in, $out );
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute lessphp on a .less file or a lessphp cache structure
|
||||
*
|
||||
* The lessphp cache structure contains information about a specific
|
||||
* less file having been parsed. It can be used as a hint for future
|
||||
* calls to determine whether or not a rebuild is required.
|
||||
*
|
||||
* The cache structure contains two important keys that may be used
|
||||
* externally:
|
||||
*
|
||||
* compiled: The final compiled CSS
|
||||
* updated: The time (in seconds) the CSS was last compiled
|
||||
*
|
||||
* The cache structure is a plain-ol' PHP associative array and can
|
||||
* be serialized and unserialized without a hitch.
|
||||
*
|
||||
* @param mixed $in Input
|
||||
* @param bool $force Force rebuild?
|
||||
* @return array lessphp cache structure
|
||||
*/
|
||||
public function cachedCompile( $in, $force = false ) {
|
||||
// assume no root
|
||||
$root = null;
|
||||
|
||||
if ( is_string( $in ) ) {
|
||||
$root = $in;
|
||||
} elseif ( is_array( $in ) and isset( $in['root'] ) ) {
|
||||
if ( $force or !isset( $in['files'] ) ) {
|
||||
// If we are forcing a recompile or if for some reason the
|
||||
// structure does not contain any file information we should
|
||||
// specify the root to trigger a rebuild.
|
||||
$root = $in['root'];
|
||||
} elseif ( isset( $in['files'] ) and is_array( $in['files'] ) ) {
|
||||
foreach ( $in['files'] as $fname => $ftime ) {
|
||||
if ( !file_exists( $fname ) or filemtime( $fname ) > $ftime ) {
|
||||
// One of the files we knew about previously has changed
|
||||
// so we should look at our incoming root again.
|
||||
$root = $in['root'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO: Throw an exception? We got neither a string nor something
|
||||
// that looks like a compatible lessphp cache structure.
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( $root !== null ) {
|
||||
// If we have a root value which means we should rebuild.
|
||||
$out = array();
|
||||
$out['root'] = $root;
|
||||
$out['compiled'] = $this->compileFile( $root );
|
||||
$out['files'] = $this->allParsedFiles();
|
||||
$out['updated'] = time();
|
||||
return $out;
|
||||
} else {
|
||||
// No changes, pass back the structure
|
||||
// we were given initially.
|
||||
return $in;
|
||||
}
|
||||
}
|
||||
|
||||
public function ccompile( $in, $out, $less = null ) {
|
||||
if ( $less === null ) {
|
||||
$less = new self;
|
||||
}
|
||||
return $less->checkedCompile( $in, $out );
|
||||
}
|
||||
|
||||
public static function cexecute( $in, $force = false, $less = null ) {
|
||||
if ( $less === null ) {
|
||||
$less = new self;
|
||||
}
|
||||
return $less->cachedCompile( $in, $force );
|
||||
}
|
||||
|
||||
public function allParsedFiles() {
|
||||
return $this->allParsedFiles;
|
||||
}
|
||||
|
||||
protected function addParsedFile( $file ) {
|
||||
$this->allParsedFiles[Less_Parser::AbsPath( $file )] = filemtime( $file );
|
||||
}
|
||||
}
|
2
less.php/lib/Less/.easymin/ignore_prefixes
Normal file
2
less.php/lib/Less/.easymin/ignore_prefixes
Normal file
@ -0,0 +1,2 @@
|
||||
.easymin
|
||||
Autoloader.php
|
77
less.php/lib/Less/Autoloader.php
Normal file
77
less.php/lib/Less/Autoloader.php
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Autoloader
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage autoload
|
||||
*/
|
||||
class Less_Autoloader {
|
||||
|
||||
/**
|
||||
* Registered flag
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $registered = false;
|
||||
|
||||
/**
|
||||
* Library directory
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $libDir;
|
||||
|
||||
/**
|
||||
* Register the autoloader in the spl autoloader
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception If there was an error in registration
|
||||
*/
|
||||
public static function register() {
|
||||
if ( self::$registered ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$libDir = dirname( __FILE__ );
|
||||
|
||||
if ( false === spl_autoload_register( array( 'Less_Autoloader', 'loadClass' ) ) ) {
|
||||
throw new Exception( 'Unable to register Less_Autoloader::loadClass as an autoloading method.' );
|
||||
}
|
||||
|
||||
self::$registered = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the autoloader
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function unregister() {
|
||||
spl_autoload_unregister( array( 'Less_Autoloader', 'loadClass' ) );
|
||||
self::$registered = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the class
|
||||
*
|
||||
* @param string $className The class to load
|
||||
*/
|
||||
public static function loadClass( $className ) {
|
||||
// handle only package classes
|
||||
if ( strpos( $className, 'Less_' ) !== 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$className = substr( $className, 5 );
|
||||
$fileName = self::$libDir . DIRECTORY_SEPARATOR . str_replace( '_', DIRECTORY_SEPARATOR, $className ) . '.php';
|
||||
|
||||
if ( file_exists( $fileName ) ) {
|
||||
require $fileName;
|
||||
return true;
|
||||
} else {
|
||||
throw new Exception( 'file not loadable '.$fileName );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
293
less.php/lib/Less/Cache.php
Normal file
293
less.php/lib/Less/Cache.php
Normal file
@ -0,0 +1,293 @@
|
||||
<?php
|
||||
|
||||
require_once dirname( __FILE__ ).'/Version.php';
|
||||
|
||||
/**
|
||||
* Utility for handling the generation and caching of css files
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage cache
|
||||
*
|
||||
*/
|
||||
class Less_Cache {
|
||||
|
||||
// directory less.php can use for storing data
|
||||
public static $cache_dir = false;
|
||||
|
||||
// prefix for the storing data
|
||||
public static $prefix = 'lessphp_';
|
||||
|
||||
// prefix for the storing vars
|
||||
public static $prefix_vars = 'lessphpvars_';
|
||||
|
||||
// specifies the number of seconds after which data created by less.php will be seen as 'garbage' and potentially cleaned up
|
||||
public static $gc_lifetime = 604800;
|
||||
|
||||
/**
|
||||
* Save and reuse the results of compiled less files.
|
||||
* The first call to Get() will generate css and save it.
|
||||
* Subsequent calls to Get() with the same arguments will return the same css filename
|
||||
*
|
||||
* @param array $less_files Array of .less files to compile
|
||||
* @param array $parser_options Array of compiler options
|
||||
* @param array $modify_vars Array of variables
|
||||
* @return string Name of the css file
|
||||
*/
|
||||
public static function Get( $less_files, $parser_options = array(), $modify_vars = array() ) {
|
||||
// check $cache_dir
|
||||
if ( isset( $parser_options['cache_dir'] ) ) {
|
||||
Less_Cache::$cache_dir = $parser_options['cache_dir'];
|
||||
}
|
||||
|
||||
if ( empty( Less_Cache::$cache_dir ) ) {
|
||||
throw new Exception( 'cache_dir not set' );
|
||||
}
|
||||
|
||||
if ( isset( $parser_options['prefix'] ) ) {
|
||||
Less_Cache::$prefix = $parser_options['prefix'];
|
||||
}
|
||||
|
||||
if ( empty( Less_Cache::$prefix ) ) {
|
||||
throw new Exception( 'prefix not set' );
|
||||
}
|
||||
|
||||
if ( isset( $parser_options['prefix_vars'] ) ) {
|
||||
Less_Cache::$prefix_vars = $parser_options['prefix_vars'];
|
||||
}
|
||||
|
||||
if ( empty( Less_Cache::$prefix_vars ) ) {
|
||||
throw new Exception( 'prefix_vars not set' );
|
||||
}
|
||||
|
||||
self::CheckCacheDir();
|
||||
$less_files = (array)$less_files;
|
||||
|
||||
// create a file for variables
|
||||
if ( !empty( $modify_vars ) ) {
|
||||
$lessvars = Less_Parser::serializeVars( $modify_vars );
|
||||
$vars_file = Less_Cache::$cache_dir . Less_Cache::$prefix_vars . sha1( $lessvars ) . '.less';
|
||||
|
||||
if ( !file_exists( $vars_file ) ) {
|
||||
file_put_contents( $vars_file, $lessvars );
|
||||
}
|
||||
|
||||
$less_files += array( $vars_file => '/' );
|
||||
}
|
||||
|
||||
// generate name for compiled css file
|
||||
$hash = md5( json_encode( $less_files ) );
|
||||
$list_file = Less_Cache::$cache_dir . Less_Cache::$prefix . $hash . '.list';
|
||||
|
||||
// check cached content
|
||||
if ( !isset( $parser_options['use_cache'] ) || $parser_options['use_cache'] === true ) {
|
||||
if ( file_exists( $list_file ) ) {
|
||||
|
||||
self::ListFiles( $list_file, $list, $cached_name );
|
||||
$compiled_name = self::CompiledName( $list, $hash );
|
||||
|
||||
// if $cached_name is the same as the $compiled name, don't regenerate
|
||||
if ( !$cached_name || $cached_name === $compiled_name ) {
|
||||
|
||||
$output_file = self::OutputFile( $compiled_name, $parser_options );
|
||||
|
||||
if ( $output_file && file_exists( $output_file ) ) {
|
||||
@touch( $list_file );
|
||||
return basename( $output_file ); // for backwards compatibility, we just return the name of the file
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$compiled = self::Cache( $less_files, $parser_options );
|
||||
if ( !$compiled ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$compiled_name = self::CompiledName( $less_files, $hash );
|
||||
$output_file = self::OutputFile( $compiled_name, $parser_options );
|
||||
|
||||
// save the file list
|
||||
$list = $less_files;
|
||||
$list[] = $compiled_name;
|
||||
$cache = implode( "\n", $list );
|
||||
file_put_contents( $list_file, $cache );
|
||||
|
||||
// save the css
|
||||
file_put_contents( $output_file, $compiled );
|
||||
|
||||
// clean up
|
||||
self::CleanCache();
|
||||
|
||||
return basename( $output_file );
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the compiler to regenerate the cached css file
|
||||
*
|
||||
* @param array $less_files Array of .less files to compile
|
||||
* @param array $parser_options Array of compiler options
|
||||
* @param array $modify_vars Array of variables
|
||||
* @return string Name of the css file
|
||||
*/
|
||||
public static function Regen( $less_files, $parser_options = array(), $modify_vars = array() ) {
|
||||
$parser_options['use_cache'] = false;
|
||||
return self::Get( $less_files, $parser_options, $modify_vars );
|
||||
}
|
||||
|
||||
public static function Cache( &$less_files, $parser_options = array() ) {
|
||||
// get less.php if it exists
|
||||
$file = dirname( __FILE__ ) . '/Less.php';
|
||||
if ( file_exists( $file ) && !class_exists( 'Less_Parser' ) ) {
|
||||
require_once $file;
|
||||
}
|
||||
|
||||
$parser_options['cache_dir'] = Less_Cache::$cache_dir;
|
||||
$parser = new Less_Parser( $parser_options );
|
||||
|
||||
// combine files
|
||||
foreach ( $less_files as $file_path => $uri_or_less ) {
|
||||
|
||||
// treat as less markup if there are newline characters
|
||||
if ( strpos( $uri_or_less, "\n" ) !== false ) {
|
||||
$parser->Parse( $uri_or_less );
|
||||
continue;
|
||||
}
|
||||
|
||||
$parser->ParseFile( $file_path, $uri_or_less );
|
||||
}
|
||||
|
||||
$compiled = $parser->getCss();
|
||||
|
||||
$less_files = $parser->allParsedFiles();
|
||||
|
||||
return $compiled;
|
||||
}
|
||||
|
||||
private static function OutputFile( $compiled_name, $parser_options ) {
|
||||
// custom output file
|
||||
if ( !empty( $parser_options['output'] ) ) {
|
||||
|
||||
// relative to cache directory?
|
||||
if ( preg_match( '#[\\\\/]#', $parser_options['output'] ) ) {
|
||||
return $parser_options['output'];
|
||||
}
|
||||
|
||||
return Less_Cache::$cache_dir.$parser_options['output'];
|
||||
}
|
||||
|
||||
return Less_Cache::$cache_dir.$compiled_name;
|
||||
}
|
||||
|
||||
private static function CompiledName( $files, $extrahash ) {
|
||||
// save the file list
|
||||
$temp = array( Less_Version::cache_version );
|
||||
foreach ( $files as $file ) {
|
||||
$temp[] = filemtime( $file )."\t".filesize( $file )."\t".$file;
|
||||
}
|
||||
|
||||
return Less_Cache::$prefix.sha1( json_encode( $temp ).$extrahash ).'.css';
|
||||
}
|
||||
|
||||
public static function SetCacheDir( $dir ) {
|
||||
Less_Cache::$cache_dir = $dir;
|
||||
self::CheckCacheDir();
|
||||
}
|
||||
|
||||
public static function CheckCacheDir() {
|
||||
Less_Cache::$cache_dir = str_replace( '\\', '/', Less_Cache::$cache_dir );
|
||||
Less_Cache::$cache_dir = rtrim( Less_Cache::$cache_dir, '/' ).'/';
|
||||
|
||||
if ( !file_exists( Less_Cache::$cache_dir ) ) {
|
||||
if ( !mkdir( Less_Cache::$cache_dir ) ) {
|
||||
throw new Less_Exception_Parser( 'Less.php cache directory couldn\'t be created: '.Less_Cache::$cache_dir );
|
||||
}
|
||||
|
||||
} elseif ( !is_dir( Less_Cache::$cache_dir ) ) {
|
||||
throw new Less_Exception_Parser( 'Less.php cache directory doesn\'t exist: '.Less_Cache::$cache_dir );
|
||||
|
||||
} elseif ( !is_writable( Less_Cache::$cache_dir ) ) {
|
||||
throw new Less_Exception_Parser( 'Less.php cache directory isn\'t writable: '.Less_Cache::$cache_dir );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete unused less.php files
|
||||
*
|
||||
*/
|
||||
public static function CleanCache() {
|
||||
static $clean = false;
|
||||
|
||||
if ( $clean || empty( Less_Cache::$cache_dir ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$clean = true;
|
||||
|
||||
// only remove files with extensions created by less.php
|
||||
// css files removed based on the list files
|
||||
$remove_types = array( 'lesscache' => 1,'list' => 1,'less' => 1,'map' => 1 );
|
||||
|
||||
$files = scandir( Less_Cache::$cache_dir );
|
||||
if ( !$files ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$check_time = time() - self::$gc_lifetime;
|
||||
foreach ( $files as $file ) {
|
||||
|
||||
// don't delete if the file wasn't created with less.php
|
||||
if ( strpos( $file, Less_Cache::$prefix ) !== 0 ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parts = explode( '.', $file );
|
||||
$type = array_pop( $parts );
|
||||
|
||||
if ( !isset( $remove_types[$type] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$full_path = Less_Cache::$cache_dir . $file;
|
||||
$mtime = filemtime( $full_path );
|
||||
|
||||
// don't delete if it's a relatively new file
|
||||
if ( $mtime > $check_time ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// delete the list file and associated css file
|
||||
if ( $type === 'list' ) {
|
||||
self::ListFiles( $full_path, $list, $css_file_name );
|
||||
if ( $css_file_name ) {
|
||||
$css_file = Less_Cache::$cache_dir . $css_file_name;
|
||||
if ( file_exists( $css_file ) ) {
|
||||
unlink( $css_file );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unlink( $full_path );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of less files and generated css file from a list file
|
||||
*
|
||||
*/
|
||||
static function ListFiles( $list_file, &$list, &$css_file_name ) {
|
||||
$list = explode( "\n", file_get_contents( $list_file ) );
|
||||
|
||||
// pop the cached name that should match $compiled_name
|
||||
$css_file_name = array_pop( $list );
|
||||
|
||||
if ( !preg_match( '/^' . Less_Cache::$prefix . '[a-f0-9]+\.css$/', $css_file_name ) ) {
|
||||
$list[] = $css_file_name;
|
||||
$css_file_name = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
169
less.php/lib/Less/Colors.php
Normal file
169
less.php/lib/Less/Colors.php
Normal file
@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Utility for css colors
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage color
|
||||
*/
|
||||
class Less_Colors {
|
||||
|
||||
public static $colors = array(
|
||||
'aliceblue' => '#f0f8ff',
|
||||
'antiquewhite' => '#faebd7',
|
||||
'aqua' => '#00ffff',
|
||||
'aquamarine' => '#7fffd4',
|
||||
'azure' => '#f0ffff',
|
||||
'beige' => '#f5f5dc',
|
||||
'bisque' => '#ffe4c4',
|
||||
'black' => '#000000',
|
||||
'blanchedalmond' => '#ffebcd',
|
||||
'blue' => '#0000ff',
|
||||
'blueviolet' => '#8a2be2',
|
||||
'brown' => '#a52a2a',
|
||||
'burlywood' => '#deb887',
|
||||
'cadetblue' => '#5f9ea0',
|
||||
'chartreuse' => '#7fff00',
|
||||
'chocolate' => '#d2691e',
|
||||
'coral' => '#ff7f50',
|
||||
'cornflowerblue' => '#6495ed',
|
||||
'cornsilk' => '#fff8dc',
|
||||
'crimson' => '#dc143c',
|
||||
'cyan' => '#00ffff',
|
||||
'darkblue' => '#00008b',
|
||||
'darkcyan' => '#008b8b',
|
||||
'darkgoldenrod' => '#b8860b',
|
||||
'darkgray' => '#a9a9a9',
|
||||
'darkgrey' => '#a9a9a9',
|
||||
'darkgreen' => '#006400',
|
||||
'darkkhaki' => '#bdb76b',
|
||||
'darkmagenta' => '#8b008b',
|
||||
'darkolivegreen' => '#556b2f',
|
||||
'darkorange' => '#ff8c00',
|
||||
'darkorchid' => '#9932cc',
|
||||
'darkred' => '#8b0000',
|
||||
'darksalmon' => '#e9967a',
|
||||
'darkseagreen' => '#8fbc8f',
|
||||
'darkslateblue' => '#483d8b',
|
||||
'darkslategray' => '#2f4f4f',
|
||||
'darkslategrey' => '#2f4f4f',
|
||||
'darkturquoise' => '#00ced1',
|
||||
'darkviolet' => '#9400d3',
|
||||
'deeppink' => '#ff1493',
|
||||
'deepskyblue' => '#00bfff',
|
||||
'dimgray' => '#696969',
|
||||
'dimgrey' => '#696969',
|
||||
'dodgerblue' => '#1e90ff',
|
||||
'firebrick' => '#b22222',
|
||||
'floralwhite' => '#fffaf0',
|
||||
'forestgreen' => '#228b22',
|
||||
'fuchsia' => '#ff00ff',
|
||||
'gainsboro' => '#dcdcdc',
|
||||
'ghostwhite' => '#f8f8ff',
|
||||
'gold' => '#ffd700',
|
||||
'goldenrod' => '#daa520',
|
||||
'gray' => '#808080',
|
||||
'grey' => '#808080',
|
||||
'green' => '#008000',
|
||||
'greenyellow' => '#adff2f',
|
||||
'honeydew' => '#f0fff0',
|
||||
'hotpink' => '#ff69b4',
|
||||
'indianred' => '#cd5c5c',
|
||||
'indigo' => '#4b0082',
|
||||
'ivory' => '#fffff0',
|
||||
'khaki' => '#f0e68c',
|
||||
'lavender' => '#e6e6fa',
|
||||
'lavenderblush' => '#fff0f5',
|
||||
'lawngreen' => '#7cfc00',
|
||||
'lemonchiffon' => '#fffacd',
|
||||
'lightblue' => '#add8e6',
|
||||
'lightcoral' => '#f08080',
|
||||
'lightcyan' => '#e0ffff',
|
||||
'lightgoldenrodyellow' => '#fafad2',
|
||||
'lightgray' => '#d3d3d3',
|
||||
'lightgrey' => '#d3d3d3',
|
||||
'lightgreen' => '#90ee90',
|
||||
'lightpink' => '#ffb6c1',
|
||||
'lightsalmon' => '#ffa07a',
|
||||
'lightseagreen' => '#20b2aa',
|
||||
'lightskyblue' => '#87cefa',
|
||||
'lightslategray' => '#778899',
|
||||
'lightslategrey' => '#778899',
|
||||
'lightsteelblue' => '#b0c4de',
|
||||
'lightyellow' => '#ffffe0',
|
||||
'lime' => '#00ff00',
|
||||
'limegreen' => '#32cd32',
|
||||
'linen' => '#faf0e6',
|
||||
'magenta' => '#ff00ff',
|
||||
'maroon' => '#800000',
|
||||
'mediumaquamarine' => '#66cdaa',
|
||||
'mediumblue' => '#0000cd',
|
||||
'mediumorchid' => '#ba55d3',
|
||||
'mediumpurple' => '#9370d8',
|
||||
'mediumseagreen' => '#3cb371',
|
||||
'mediumslateblue' => '#7b68ee',
|
||||
'mediumspringgreen' => '#00fa9a',
|
||||
'mediumturquoise' => '#48d1cc',
|
||||
'mediumvioletred' => '#c71585',
|
||||
'midnightblue' => '#191970',
|
||||
'mintcream' => '#f5fffa',
|
||||
'mistyrose' => '#ffe4e1',
|
||||
'moccasin' => '#ffe4b5',
|
||||
'navajowhite' => '#ffdead',
|
||||
'navy' => '#000080',
|
||||
'oldlace' => '#fdf5e6',
|
||||
'olive' => '#808000',
|
||||
'olivedrab' => '#6b8e23',
|
||||
'orange' => '#ffa500',
|
||||
'orangered' => '#ff4500',
|
||||
'orchid' => '#da70d6',
|
||||
'palegoldenrod' => '#eee8aa',
|
||||
'palegreen' => '#98fb98',
|
||||
'paleturquoise' => '#afeeee',
|
||||
'palevioletred' => '#d87093',
|
||||
'papayawhip' => '#ffefd5',
|
||||
'peachpuff' => '#ffdab9',
|
||||
'peru' => '#cd853f',
|
||||
'pink' => '#ffc0cb',
|
||||
'plum' => '#dda0dd',
|
||||
'powderblue' => '#b0e0e6',
|
||||
'purple' => '#800080',
|
||||
'red' => '#ff0000',
|
||||
'rosybrown' => '#bc8f8f',
|
||||
'royalblue' => '#4169e1',
|
||||
'saddlebrown' => '#8b4513',
|
||||
'salmon' => '#fa8072',
|
||||
'sandybrown' => '#f4a460',
|
||||
'seagreen' => '#2e8b57',
|
||||
'seashell' => '#fff5ee',
|
||||
'sienna' => '#a0522d',
|
||||
'silver' => '#c0c0c0',
|
||||
'skyblue' => '#87ceeb',
|
||||
'slateblue' => '#6a5acd',
|
||||
'slategray' => '#708090',
|
||||
'slategrey' => '#708090',
|
||||
'snow' => '#fffafa',
|
||||
'springgreen' => '#00ff7f',
|
||||
'steelblue' => '#4682b4',
|
||||
'tan' => '#d2b48c',
|
||||
'teal' => '#008080',
|
||||
'thistle' => '#d8bfd8',
|
||||
'tomato' => '#ff6347',
|
||||
'turquoise' => '#40e0d0',
|
||||
'violet' => '#ee82ee',
|
||||
'wheat' => '#f5deb3',
|
||||
'white' => '#ffffff',
|
||||
'whitesmoke' => '#f5f5f5',
|
||||
'yellow' => '#ffff00',
|
||||
'yellowgreen' => '#9acd32'
|
||||
);
|
||||
|
||||
public static function hasOwnProperty( $color ) {
|
||||
return isset( self::$colors[$color] );
|
||||
}
|
||||
|
||||
public static function color( $color ) {
|
||||
return self::$colors[$color];
|
||||
}
|
||||
|
||||
}
|
66
less.php/lib/Less/Configurable.php
Normal file
66
less.php/lib/Less/Configurable.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Configurable
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage Core
|
||||
*/
|
||||
abstract class Less_Configurable {
|
||||
|
||||
/**
|
||||
* Array of options
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $options = array();
|
||||
|
||||
/**
|
||||
* Array of default options
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultOptions = array();
|
||||
|
||||
/**
|
||||
* Set options
|
||||
*
|
||||
* If $options is an object it will be converted into an array by called
|
||||
* it's toArray method.
|
||||
*
|
||||
* @throws Exception
|
||||
* @param array|object $options
|
||||
*
|
||||
*/
|
||||
public function setOptions( $options ) {
|
||||
$options = array_intersect_key( $options, $this->defaultOptions );
|
||||
$this->options = array_merge( $this->defaultOptions, $this->options, $options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an option value by name
|
||||
*
|
||||
* If the option is empty or not set a NULL value will be returned.
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $default Default value if confiuration of $name is not present
|
||||
* @return mixed
|
||||
*/
|
||||
public function getOption( $name, $default = null ) {
|
||||
if ( isset( $this->options[$name] ) ) {
|
||||
return $this->options[$name];
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an option
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function setOption( $name, $value ) {
|
||||
$this->options[$name] = $value;
|
||||
}
|
||||
|
||||
}
|
157
less.php/lib/Less/Environment.php
Normal file
157
less.php/lib/Less/Environment.php
Normal file
@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Environment
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage environment
|
||||
*/
|
||||
class Less_Environment {
|
||||
|
||||
// public $paths = array(); // option - unmodified - paths to search for imports on
|
||||
//public static $files = array(); // list of files that have been imported, used for import-once
|
||||
//public $rootpath; // option - rootpath to append to URL's
|
||||
//public static $strictImports = null; // option -
|
||||
//public $insecure; // option - whether to allow imports from insecure ssl hosts
|
||||
//public $processImports; // option - whether to process imports. if false then imports will not be imported
|
||||
//public $javascriptEnabled; // option - whether JavaScript is enabled. if undefined, defaults to true
|
||||
//public $useFileCache; // browser only - whether to use the per file session cache
|
||||
public $currentFileInfo; // information about the current file - for error reporting and importing and making urls relative etc.
|
||||
|
||||
public $importMultiple = false; // whether we are currently importing multiple copies
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $frames = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $mediaBlocks = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $mediaPath = array();
|
||||
|
||||
public static $parensStack = 0;
|
||||
|
||||
public static $tabLevel = 0;
|
||||
|
||||
public static $lastRule = false;
|
||||
|
||||
public static $_outputMap;
|
||||
|
||||
public static $mixin_stack = 0;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $functions = array();
|
||||
|
||||
public function Init() {
|
||||
self::$parensStack = 0;
|
||||
self::$tabLevel = 0;
|
||||
self::$lastRule = false;
|
||||
self::$mixin_stack = 0;
|
||||
|
||||
if ( Less_Parser::$options['compress'] ) {
|
||||
|
||||
Less_Environment::$_outputMap = array(
|
||||
',' => ',',
|
||||
': ' => ':',
|
||||
'' => '',
|
||||
' ' => ' ',
|
||||
':' => ' :',
|
||||
'+' => '+',
|
||||
'~' => '~',
|
||||
'>' => '>',
|
||||
'|' => '|',
|
||||
'^' => '^',
|
||||
'^^' => '^^'
|
||||
);
|
||||
|
||||
} else {
|
||||
|
||||
Less_Environment::$_outputMap = array(
|
||||
',' => ', ',
|
||||
': ' => ': ',
|
||||
'' => '',
|
||||
' ' => ' ',
|
||||
':' => ' :',
|
||||
'+' => ' + ',
|
||||
'~' => ' ~ ',
|
||||
'>' => ' > ',
|
||||
'|' => '|',
|
||||
'^' => ' ^ ',
|
||||
'^^' => ' ^^ '
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public function copyEvalEnv( $frames = array() ) {
|
||||
$new_env = new Less_Environment();
|
||||
$new_env->frames = $frames;
|
||||
return $new_env;
|
||||
}
|
||||
|
||||
public static function isMathOn() {
|
||||
return !Less_Parser::$options['strictMath'] || Less_Environment::$parensStack;
|
||||
}
|
||||
|
||||
public static function isPathRelative( $path ) {
|
||||
return !preg_match( '/^(?:[a-z-]+:|\/)/', $path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Canonicalize a path by resolving references to '/./', '/../'
|
||||
* Does not remove leading "../"
|
||||
* @param string path or url
|
||||
* @return string Canonicalized path
|
||||
*
|
||||
*/
|
||||
public static function normalizePath( $path ) {
|
||||
$segments = explode( '/', $path );
|
||||
$segments = array_reverse( $segments );
|
||||
|
||||
$path = array();
|
||||
$path_len = 0;
|
||||
|
||||
while ( $segments ) {
|
||||
$segment = array_pop( $segments );
|
||||
switch ( $segment ) {
|
||||
|
||||
case '.':
|
||||
break;
|
||||
|
||||
case '..':
|
||||
if ( !$path_len || ( $path[$path_len - 1] === '..' ) ) {
|
||||
$path[] = $segment;
|
||||
$path_len++;
|
||||
} else {
|
||||
array_pop( $path );
|
||||
$path_len--;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$path[] = $segment;
|
||||
$path_len++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return implode( '/', $path );
|
||||
}
|
||||
|
||||
public function unshiftFrame( $frame ) {
|
||||
array_unshift( $this->frames, $frame );
|
||||
}
|
||||
|
||||
public function shiftFrame() {
|
||||
return array_shift( $this->frames );
|
||||
}
|
||||
|
||||
}
|
203
less.php/lib/Less/Exception/Chunk.php
Normal file
203
less.php/lib/Less/Exception/Chunk.php
Normal file
@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Chunk Exception
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage exception
|
||||
*/
|
||||
class Less_Exception_Chunk extends Less_Exception_Parser {
|
||||
|
||||
protected $parserCurrentIndex = 0;
|
||||
|
||||
protected $emitFrom = 0;
|
||||
|
||||
protected $input_len;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $input
|
||||
* @param Exception $previous Previous exception
|
||||
* @param integer $index The current parser index
|
||||
* @param Less_FileInfo|string $currentFile The file
|
||||
* @param integer $code The exception code
|
||||
*/
|
||||
public function __construct( $input, Exception $previous = null, $index = null, $currentFile = null, $code = 0 ) {
|
||||
$this->message = 'ParseError: Unexpected input'; // default message
|
||||
|
||||
$this->index = $index;
|
||||
|
||||
$this->currentFile = $currentFile;
|
||||
|
||||
$this->input = $input;
|
||||
$this->input_len = strlen( $input );
|
||||
|
||||
$this->Chunks();
|
||||
$this->genMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* See less.js chunks()
|
||||
* We don't actually need the chunks
|
||||
*
|
||||
*/
|
||||
protected function Chunks() {
|
||||
$level = 0;
|
||||
$parenLevel = 0;
|
||||
$lastMultiCommentEndBrace = null;
|
||||
$lastOpening = null;
|
||||
$lastMultiComment = null;
|
||||
$lastParen = null;
|
||||
|
||||
for ( $this->parserCurrentIndex = 0; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++ ) {
|
||||
$cc = $this->CharCode( $this->parserCurrentIndex );
|
||||
if ( ( ( $cc >= 97 ) && ( $cc <= 122 ) ) || ( $cc < 34 ) ) {
|
||||
// a-z or whitespace
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ( $cc ) {
|
||||
|
||||
// (
|
||||
case 40:
|
||||
$parenLevel++;
|
||||
$lastParen = $this->parserCurrentIndex;
|
||||
break;
|
||||
|
||||
// )
|
||||
case 41:
|
||||
$parenLevel--;
|
||||
if ( $parenLevel < 0 ) {
|
||||
return $this->fail( "missing opening `(`" );
|
||||
}
|
||||
break;
|
||||
|
||||
// ;
|
||||
case 59:
|
||||
// if (!$parenLevel) { $this->emitChunk(); }
|
||||
break;
|
||||
|
||||
// {
|
||||
case 123:
|
||||
$level++;
|
||||
$lastOpening = $this->parserCurrentIndex;
|
||||
break;
|
||||
|
||||
// }
|
||||
case 125:
|
||||
$level--;
|
||||
if ( $level < 0 ) {
|
||||
return $this->fail( "missing opening `{`" );
|
||||
|
||||
}
|
||||
// if (!$level && !$parenLevel) { $this->emitChunk(); }
|
||||
break;
|
||||
// \
|
||||
case 92:
|
||||
if ( $this->parserCurrentIndex < $this->input_len - 1 ) { $this->parserCurrentIndex++; break;
|
||||
}
|
||||
return $this->fail( "unescaped `\\`" );
|
||||
|
||||
// ", ' and `
|
||||
case 34:
|
||||
case 39:
|
||||
case 96:
|
||||
$matched = 0;
|
||||
$currentChunkStartIndex = $this->parserCurrentIndex;
|
||||
for ( $this->parserCurrentIndex = $this->parserCurrentIndex + 1; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++ ) {
|
||||
$cc2 = $this->CharCode( $this->parserCurrentIndex );
|
||||
if ( $cc2 > 96 ) { continue;
|
||||
}
|
||||
if ( $cc2 == $cc ) { $matched = 1; break;
|
||||
}
|
||||
if ( $cc2 == 92 ) { // \
|
||||
if ( $this->parserCurrentIndex == $this->input_len - 1 ) {
|
||||
return $this->fail( "unescaped `\\`" );
|
||||
}
|
||||
$this->parserCurrentIndex++;
|
||||
}
|
||||
}
|
||||
if ( $matched ) { break;
|
||||
}
|
||||
return $this->fail( "unmatched `" . chr( $cc ) . "`", $currentChunkStartIndex );
|
||||
|
||||
// /, check for comment
|
||||
case 47:
|
||||
if ( $parenLevel || ( $this->parserCurrentIndex == $this->input_len - 1 ) ) { break;
|
||||
}
|
||||
$cc2 = $this->CharCode( $this->parserCurrentIndex + 1 );
|
||||
if ( $cc2 == 47 ) {
|
||||
// //, find lnfeed
|
||||
for ( $this->parserCurrentIndex = $this->parserCurrentIndex + 2; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++ ) {
|
||||
$cc2 = $this->CharCode( $this->parserCurrentIndex );
|
||||
if ( ( $cc2 <= 13 ) && ( ( $cc2 == 10 ) || ( $cc2 == 13 ) ) ) { break;
|
||||
}
|
||||
}
|
||||
} else if ( $cc2 == 42 ) {
|
||||
// /*, find */
|
||||
$lastMultiComment = $currentChunkStartIndex = $this->parserCurrentIndex;
|
||||
for ( $this->parserCurrentIndex = $this->parserCurrentIndex + 2; $this->parserCurrentIndex < $this->input_len - 1; $this->parserCurrentIndex++ ) {
|
||||
$cc2 = $this->CharCode( $this->parserCurrentIndex );
|
||||
if ( $cc2 == 125 ) { $lastMultiCommentEndBrace = $this->parserCurrentIndex;
|
||||
}
|
||||
if ( $cc2 != 42 ) { continue;
|
||||
}
|
||||
if ( $this->CharCode( $this->parserCurrentIndex + 1 ) == 47 ) { break;
|
||||
}
|
||||
}
|
||||
if ( $this->parserCurrentIndex == $this->input_len - 1 ) {
|
||||
return $this->fail( "missing closing `*/`", $currentChunkStartIndex );
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// *, check for unmatched */
|
||||
case 42:
|
||||
if ( ( $this->parserCurrentIndex < $this->input_len - 1 ) && ( $this->CharCode( $this->parserCurrentIndex + 1 ) == 47 ) ) {
|
||||
return $this->fail( "unmatched `/*`" );
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $level !== 0 ) {
|
||||
if ( ( $lastMultiComment > $lastOpening ) && ( $lastMultiCommentEndBrace > $lastMultiComment ) ) {
|
||||
return $this->fail( "missing closing `}` or `*/`", $lastOpening );
|
||||
} else {
|
||||
return $this->fail( "missing closing `}`", $lastOpening );
|
||||
}
|
||||
} else if ( $parenLevel !== 0 ) {
|
||||
return $this->fail( "missing closing `)`", $lastParen );
|
||||
}
|
||||
|
||||
// chunk didn't fail
|
||||
|
||||
//$this->emitChunk(true);
|
||||
}
|
||||
|
||||
public function CharCode( $pos ) {
|
||||
return ord( $this->input[$pos] );
|
||||
}
|
||||
|
||||
public function fail( $msg, $index = null ) {
|
||||
if ( !$index ) {
|
||||
$this->index = $this->parserCurrentIndex;
|
||||
} else {
|
||||
$this->index = $index;
|
||||
}
|
||||
$this->message = 'ParseError: '.$msg;
|
||||
}
|
||||
|
||||
/*
|
||||
function emitChunk( $force = false ){
|
||||
$len = $this->parserCurrentIndex - $this->emitFrom;
|
||||
if ((($len < 512) && !$force) || !$len) {
|
||||
return;
|
||||
}
|
||||
$chunks[] = substr($this->input, $this->emitFrom, $this->parserCurrentIndex + 1 - $this->emitFrom );
|
||||
$this->emitFrom = $this->parserCurrentIndex + 1;
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
11
less.php/lib/Less/Exception/Compiler.php
Normal file
11
less.php/lib/Less/Exception/Compiler.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Compiler Exception
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage exception
|
||||
*/
|
||||
class Less_Exception_Compiler extends Less_Exception_Parser {
|
||||
|
||||
}
|
116
less.php/lib/Less/Exception/Parser.php
Normal file
116
less.php/lib/Less/Exception/Parser.php
Normal file
@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Parser Exception
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage exception
|
||||
*/
|
||||
class Less_Exception_Parser extends Exception {
|
||||
|
||||
/**
|
||||
* The current file
|
||||
*
|
||||
* @var Less_ImportedFile
|
||||
*/
|
||||
public $currentFile;
|
||||
|
||||
/**
|
||||
* The current parser index
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $index;
|
||||
|
||||
protected $input;
|
||||
|
||||
protected $details = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $message
|
||||
* @param Exception $previous Previous exception
|
||||
* @param integer $index The current parser index
|
||||
* @param Less_FileInfo|string $currentFile The file
|
||||
* @param integer $code The exception code
|
||||
*/
|
||||
public function __construct( $message = null, Exception $previous = null, $index = null, $currentFile = null, $code = 0 ) {
|
||||
if ( PHP_VERSION_ID < 50300 ) {
|
||||
$this->previous = $previous;
|
||||
parent::__construct( $message, $code );
|
||||
} else {
|
||||
parent::__construct( $message, $code, $previous );
|
||||
}
|
||||
|
||||
$this->currentFile = $currentFile;
|
||||
$this->index = $index;
|
||||
|
||||
$this->genMessage();
|
||||
}
|
||||
|
||||
protected function getInput() {
|
||||
if ( !$this->input && $this->currentFile && $this->currentFile['filename'] && file_exists( $this->currentFile['filename'] ) ) {
|
||||
$this->input = file_get_contents( $this->currentFile['filename'] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the exception to string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function genMessage() {
|
||||
if ( $this->currentFile && $this->currentFile['filename'] ) {
|
||||
$this->message .= ' in '.basename( $this->currentFile['filename'] );
|
||||
}
|
||||
|
||||
if ( $this->index !== null ) {
|
||||
$this->getInput();
|
||||
if ( $this->input ) {
|
||||
$line = self::getLineNumber();
|
||||
$this->message .= ' on line '.$line.', column '.self::getColumn();
|
||||
|
||||
$lines = explode( "\n", $this->input );
|
||||
|
||||
$count = count( $lines );
|
||||
$start_line = max( 0, $line - 3 );
|
||||
$last_line = min( $count, $start_line + 6 );
|
||||
$num_len = strlen( $last_line );
|
||||
for ( $i = $start_line; $i < $last_line; $i++ ) {
|
||||
$this->message .= "\n".str_pad( $i + 1, $num_len, '0', STR_PAD_LEFT ).'| '.$lines[$i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the line number the error was encountered
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getLineNumber() {
|
||||
if ( $this->index ) {
|
||||
// https://bugs.php.net/bug.php?id=49790
|
||||
if ( ini_get( "mbstring.func_overload" ) ) {
|
||||
return substr_count( substr( $this->input, 0, $this->index ), "\n" ) + 1;
|
||||
} else {
|
||||
return substr_count( $this->input, "\n", 0, $this->index ) + 1;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the column the error was encountered
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getColumn() {
|
||||
$part = substr( $this->input, 0, $this->index );
|
||||
$pos = strrpos( $part, "\n" );
|
||||
return $this->index - $pos;
|
||||
}
|
||||
|
||||
}
|
1186
less.php/lib/Less/Functions.php
Normal file
1186
less.php/lib/Less/Functions.php
Normal file
File diff suppressed because it is too large
Load Diff
17
less.php/lib/Less/Less.php.combine
Normal file
17
less.php/lib/Less/Less.php.combine
Normal file
@ -0,0 +1,17 @@
|
||||
|
||||
./Parser.php
|
||||
./Colors.php
|
||||
./Environment.php
|
||||
./Functions.php
|
||||
./Mime.php
|
||||
./Tree.php
|
||||
./Output.php
|
||||
./Visitor.php
|
||||
./VisitorReplacing.php
|
||||
./Configurable.php
|
||||
./Tree
|
||||
./Visitor
|
||||
./Exception/Parser.php
|
||||
./Exception/
|
||||
./Output
|
||||
./SourceMap
|
41
less.php/lib/Less/Mime.php
Normal file
41
less.php/lib/Less/Mime.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Mime lookup
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage node
|
||||
*/
|
||||
class Less_Mime {
|
||||
|
||||
// this map is intentionally incomplete
|
||||
// if you want more, install 'mime' dep
|
||||
static $_types = array(
|
||||
'.htm' => 'text/html',
|
||||
'.html' => 'text/html',
|
||||
'.gif' => 'image/gif',
|
||||
'.jpg' => 'image/jpeg',
|
||||
'.jpeg' => 'image/jpeg',
|
||||
'.png' => 'image/png',
|
||||
'.ttf' => 'application/x-font-ttf',
|
||||
'.otf' => 'application/x-font-otf',
|
||||
'.eot' => 'application/vnd.ms-fontobject',
|
||||
'.woff' => 'application/x-font-woff',
|
||||
'.svg' => 'image/svg+xml',
|
||||
);
|
||||
|
||||
public static function lookup( $filepath ) {
|
||||
$parts = explode( '.', $filepath );
|
||||
$ext = '.'.strtolower( array_pop( $parts ) );
|
||||
|
||||
if ( !isset( self::$_types[$ext] ) ) {
|
||||
return null;
|
||||
}
|
||||
return self::$_types[$ext];
|
||||
}
|
||||
|
||||
public static function charsets_lookup( $type = null ) {
|
||||
// assumes all text types are UTF-8
|
||||
return $type && preg_match( '/^text\//', $type ) ? 'UTF-8' : '';
|
||||
}
|
||||
}
|
48
less.php/lib/Less/Output.php
Normal file
48
less.php/lib/Less/Output.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Parser output
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage output
|
||||
*/
|
||||
class Less_Output {
|
||||
|
||||
/**
|
||||
* Output holder
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $strs = array();
|
||||
|
||||
/**
|
||||
* Adds a chunk to the stack
|
||||
*
|
||||
* @param string $chunk The chunk to output
|
||||
* @param Less_FileInfo $fileInfo The file information
|
||||
* @param integer $index The index
|
||||
* @param mixed $mapLines
|
||||
*/
|
||||
public function add( $chunk, $fileInfo = null, $index = 0, $mapLines = null ) {
|
||||
$this->strs[] = $chunk;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the output empty?
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isEmpty() {
|
||||
return count( $this->strs ) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the output to string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toString() {
|
||||
return implode( '', $this->strs );
|
||||
}
|
||||
|
||||
}
|
119
less.php/lib/Less/Output/Mapped.php
Normal file
119
less.php/lib/Less/Output/Mapped.php
Normal file
@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Parser output with source map
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage Output
|
||||
*/
|
||||
class Less_Output_Mapped extends Less_Output {
|
||||
|
||||
/**
|
||||
* The source map generator
|
||||
*
|
||||
* @var Less_SourceMap_Generator
|
||||
*/
|
||||
protected $generator;
|
||||
|
||||
/**
|
||||
* Current line
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $lineNumber = 0;
|
||||
|
||||
/**
|
||||
* Current column
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $column = 0;
|
||||
|
||||
/**
|
||||
* Array of contents map (file and its content)
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $contentsMap = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $contentsMap Array of filename to contents map
|
||||
* @param Less_SourceMap_Generator $generator
|
||||
*/
|
||||
public function __construct( array $contentsMap, $generator ) {
|
||||
$this->contentsMap = $contentsMap;
|
||||
$this->generator = $generator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a chunk to the stack
|
||||
* The $index for less.php may be different from less.js since less.php does not chunkify inputs
|
||||
*
|
||||
* @param string $chunk
|
||||
* @param string $fileInfo
|
||||
* @param integer $index
|
||||
* @param mixed $mapLines
|
||||
*/
|
||||
public function add( $chunk, $fileInfo = null, $index = 0, $mapLines = null ) {
|
||||
// ignore adding empty strings
|
||||
if ( $chunk === '' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sourceLines = array();
|
||||
$sourceColumns = ' ';
|
||||
|
||||
if ( $fileInfo ) {
|
||||
|
||||
$url = $fileInfo['currentUri'];
|
||||
|
||||
if ( isset( $this->contentsMap[$url] ) ) {
|
||||
$inputSource = substr( $this->contentsMap[$url], 0, $index );
|
||||
$sourceLines = explode( "\n", $inputSource );
|
||||
$sourceColumns = end( $sourceLines );
|
||||
} else {
|
||||
throw new Exception( 'Filename '.$url.' not in contentsMap' );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$lines = explode( "\n", $chunk );
|
||||
$columns = end( $lines );
|
||||
|
||||
if ( $fileInfo ) {
|
||||
|
||||
if ( !$mapLines ) {
|
||||
$this->generator->addMapping(
|
||||
$this->lineNumber + 1, // generated_line
|
||||
$this->column, // generated_column
|
||||
count( $sourceLines ), // original_line
|
||||
strlen( $sourceColumns ), // original_column
|
||||
$fileInfo
|
||||
);
|
||||
} else {
|
||||
for ( $i = 0, $count = count( $lines ); $i < $count; $i++ ) {
|
||||
$this->generator->addMapping(
|
||||
$this->lineNumber + $i + 1, // generated_line
|
||||
$i === 0 ? $this->column : 0, // generated_column
|
||||
count( $sourceLines ) + $i, // original_line
|
||||
$i === 0 ? strlen( $sourceColumns ) : 0, // original_column
|
||||
$fileInfo
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( count( $lines ) === 1 ) {
|
||||
$this->column += strlen( $columns );
|
||||
} else {
|
||||
$this->lineNumber += count( $lines ) - 1;
|
||||
$this->column = strlen( $columns );
|
||||
}
|
||||
|
||||
// add only chunk
|
||||
parent::add( $chunk );
|
||||
}
|
||||
|
||||
}
|
2691
less.php/lib/Less/Parser.php
Normal file
2691
less.php/lib/Less/Parser.php
Normal file
File diff suppressed because it is too large
Load Diff
187
less.php/lib/Less/SourceMap/Base64VLQ.php
Normal file
187
less.php/lib/Less/SourceMap/Base64VLQ.php
Normal file
@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Encode / Decode Base64 VLQ.
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage SourceMap
|
||||
*/
|
||||
class Less_SourceMap_Base64VLQ {
|
||||
|
||||
/**
|
||||
* Shift
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
private $shift = 5;
|
||||
|
||||
/**
|
||||
* Mask
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
private $mask = 0x1F; // == (1 << shift) == 0b00011111
|
||||
|
||||
/**
|
||||
* Continuation bit
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
private $continuationBit = 0x20; // == (mask - 1 ) == 0b00100000
|
||||
|
||||
/**
|
||||
* Char to integer map
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $charToIntMap = array(
|
||||
'A' => 0, 'B' => 1, 'C' => 2, 'D' => 3, 'E' => 4, 'F' => 5, 'G' => 6,
|
||||
'H' => 7,'I' => 8, 'J' => 9, 'K' => 10, 'L' => 11, 'M' => 12, 'N' => 13,
|
||||
'O' => 14, 'P' => 15, 'Q' => 16, 'R' => 17, 'S' => 18, 'T' => 19, 'U' => 20,
|
||||
'V' => 21, 'W' => 22, 'X' => 23, 'Y' => 24, 'Z' => 25, 'a' => 26, 'b' => 27,
|
||||
'c' => 28, 'd' => 29, 'e' => 30, 'f' => 31, 'g' => 32, 'h' => 33, 'i' => 34,
|
||||
'j' => 35, 'k' => 36, 'l' => 37, 'm' => 38, 'n' => 39, 'o' => 40, 'p' => 41,
|
||||
'q' => 42, 'r' => 43, 's' => 44, 't' => 45, 'u' => 46, 'v' => 47, 'w' => 48,
|
||||
'x' => 49, 'y' => 50, 'z' => 51, 0 => 52, 1 => 53, 2 => 54, 3 => 55, 4 => 56,
|
||||
5 => 57, 6 => 58, 7 => 59, 8 => 60, 9 => 61, '+' => 62, '/' => 63,
|
||||
);
|
||||
|
||||
/**
|
||||
* Integer to char map
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $intToCharMap = array(
|
||||
0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D', 4 => 'E', 5 => 'F', 6 => 'G',
|
||||
7 => 'H', 8 => 'I', 9 => 'J', 10 => 'K', 11 => 'L', 12 => 'M', 13 => 'N',
|
||||
14 => 'O', 15 => 'P', 16 => 'Q', 17 => 'R', 18 => 'S', 19 => 'T', 20 => 'U',
|
||||
21 => 'V', 22 => 'W', 23 => 'X', 24 => 'Y', 25 => 'Z', 26 => 'a', 27 => 'b',
|
||||
28 => 'c', 29 => 'd', 30 => 'e', 31 => 'f', 32 => 'g', 33 => 'h', 34 => 'i',
|
||||
35 => 'j', 36 => 'k', 37 => 'l', 38 => 'm', 39 => 'n', 40 => 'o', 41 => 'p',
|
||||
42 => 'q', 43 => 'r', 44 => 's', 45 => 't', 46 => 'u', 47 => 'v', 48 => 'w',
|
||||
49 => 'x', 50 => 'y', 51 => 'z', 52 => '0', 53 => '1', 54 => '2', 55 => '3',
|
||||
56 => '4', 57 => '5', 58 => '6', 59 => '7', 60 => '8', 61 => '9', 62 => '+',
|
||||
63 => '/',
|
||||
);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
// I leave it here for future reference
|
||||
// foreach(str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/') as $i => $char)
|
||||
// {
|
||||
// $this->charToIntMap[$char] = $i;
|
||||
// $this->intToCharMap[$i] = $char;
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from a two-complement value to a value where the sign bit is
|
||||
* is placed in the least significant bit. For example, as decimals:
|
||||
* 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
|
||||
* 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
|
||||
* We generate the value for 32 bit machines, hence -2147483648 becomes 1, not 4294967297,
|
||||
* even on a 64 bit machine.
|
||||
* @param string $aValue
|
||||
*/
|
||||
public function toVLQSigned( $aValue ) {
|
||||
return 0xffffffff & ( $aValue < 0 ? ( ( -$aValue ) << 1 ) + 1 : ( $aValue << 1 ) + 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to a two-complement value from a value where the sign bit is
|
||||
* is placed in the least significant bit. For example, as decimals:
|
||||
* 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
|
||||
* 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
|
||||
* We assume that the value was generated with a 32 bit machine in mind.
|
||||
* Hence
|
||||
* 1 becomes -2147483648
|
||||
* even on a 64 bit machine.
|
||||
* @param integer $aValue
|
||||
*/
|
||||
public function fromVLQSigned( $aValue ) {
|
||||
return $aValue & 1 ? $this->zeroFill( ~$aValue + 2, 1 ) | ( -1 - 0x7fffffff ) : $this->zeroFill( $aValue, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base 64 VLQ encoded value.
|
||||
*
|
||||
* @param string $aValue The value to encode
|
||||
* @return string The encoded value
|
||||
*/
|
||||
public function encode( $aValue ) {
|
||||
$encoded = '';
|
||||
$vlq = $this->toVLQSigned( $aValue );
|
||||
do
|
||||
{
|
||||
$digit = $vlq & $this->mask;
|
||||
$vlq = $this->zeroFill( $vlq, $this->shift );
|
||||
if ( $vlq > 0 ) {
|
||||
$digit |= $this->continuationBit;
|
||||
}
|
||||
$encoded .= $this->base64Encode( $digit );
|
||||
} while ( $vlq > 0 );
|
||||
|
||||
return $encoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value decoded from base 64 VLQ.
|
||||
*
|
||||
* @param string $encoded The encoded value to decode
|
||||
* @return integer The decoded value
|
||||
*/
|
||||
public function decode( $encoded ) {
|
||||
$vlq = 0;
|
||||
$i = 0;
|
||||
do
|
||||
{
|
||||
$digit = $this->base64Decode( $encoded[$i] );
|
||||
$vlq |= ( $digit & $this->mask ) << ( $i * $this->shift );
|
||||
$i++;
|
||||
} while ( $digit & $this->continuationBit );
|
||||
|
||||
return $this->fromVLQSigned( $vlq );
|
||||
}
|
||||
|
||||
/**
|
||||
* Right shift with zero fill.
|
||||
*
|
||||
* @param integer $a number to shift
|
||||
* @param integer $b number of bits to shift
|
||||
* @return integer
|
||||
*/
|
||||
public function zeroFill( $a, $b ) {
|
||||
return ( $a >= 0 ) ? ( $a >> $b ) : ( $a >> $b ) & ( PHP_INT_MAX >> ( $b - 1 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode single 6-bit digit as base64.
|
||||
*
|
||||
* @param integer $number
|
||||
* @return string
|
||||
* @throws Exception If the number is invalid
|
||||
*/
|
||||
public function base64Encode( $number ) {
|
||||
if ( $number < 0 || $number > 63 ) {
|
||||
throw new Exception( sprintf( 'Invalid number "%s" given. Must be between 0 and 63.', $number ) );
|
||||
}
|
||||
return $this->intToCharMap[$number];
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode single 6-bit digit from base64
|
||||
*
|
||||
* @param string $char
|
||||
* @return number
|
||||
* @throws Exception If the number is invalid
|
||||
*/
|
||||
public function base64Decode( $char ) {
|
||||
if ( !array_key_exists( $char, $this->charToIntMap ) ) {
|
||||
throw new Exception( sprintf( 'Invalid base 64 digit "%s" given.', $char ) );
|
||||
}
|
||||
return $this->charToIntMap[$char];
|
||||
}
|
||||
|
||||
}
|
354
less.php/lib/Less/SourceMap/Generator.php
Normal file
354
less.php/lib/Less/SourceMap/Generator.php
Normal file
@ -0,0 +1,354 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Source map generator
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage Output
|
||||
*/
|
||||
class Less_SourceMap_Generator extends Less_Configurable {
|
||||
|
||||
/**
|
||||
* What version of source map does the generator generate?
|
||||
*/
|
||||
private const VERSION = 3;
|
||||
|
||||
/**
|
||||
* Array of default options
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultOptions = array(
|
||||
// an optional source root, useful for relocating source files
|
||||
// on a server or removing repeated values in the 'sources' entry.
|
||||
// This value is prepended to the individual entries in the 'source' field.
|
||||
'sourceRoot' => '',
|
||||
|
||||
// an optional name of the generated code that this source map is associated with.
|
||||
'sourceMapFilename' => null,
|
||||
|
||||
// url of the map
|
||||
'sourceMapURL' => null,
|
||||
|
||||
// absolute path to a file to write the map to
|
||||
'sourceMapWriteTo' => null,
|
||||
|
||||
// output source contents?
|
||||
'outputSourceFiles' => false,
|
||||
|
||||
// base path for filename normalization
|
||||
'sourceMapRootpath' => '',
|
||||
|
||||
// base path for filename normalization
|
||||
'sourceMapBasepath' => ''
|
||||
);
|
||||
|
||||
/**
|
||||
* The base64 VLQ encoder
|
||||
*
|
||||
* @var Less_SourceMap_Base64VLQ
|
||||
*/
|
||||
protected $encoder;
|
||||
|
||||
/**
|
||||
* Array of mappings
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $mappings = array();
|
||||
|
||||
/**
|
||||
* The root node
|
||||
*
|
||||
* @var Less_Tree_Ruleset
|
||||
*/
|
||||
protected $root;
|
||||
|
||||
/**
|
||||
* Array of contents map
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $contentsMap = array();
|
||||
|
||||
/**
|
||||
* File to content map
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $sources = array();
|
||||
protected $source_keys = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Less_Tree_Ruleset $root The root node
|
||||
* @param array $options Array of options
|
||||
*/
|
||||
public function __construct( Less_Tree_Ruleset $root, $contentsMap, $options = array() ) {
|
||||
$this->root = $root;
|
||||
$this->contentsMap = $contentsMap;
|
||||
$this->encoder = new Less_SourceMap_Base64VLQ();
|
||||
|
||||
$this->SetOptions( $options );
|
||||
|
||||
$this->options['sourceMapRootpath'] = $this->fixWindowsPath( $this->options['sourceMapRootpath'], true );
|
||||
$this->options['sourceMapBasepath'] = $this->fixWindowsPath( $this->options['sourceMapBasepath'], true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the CSS
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateCSS() {
|
||||
$output = new Less_Output_Mapped( $this->contentsMap, $this );
|
||||
|
||||
// catch the output
|
||||
$this->root->genCSS( $output );
|
||||
|
||||
$sourceMapUrl = $this->getOption( 'sourceMapURL' );
|
||||
$sourceMapFilename = $this->getOption( 'sourceMapFilename' );
|
||||
$sourceMapContent = $this->generateJson();
|
||||
$sourceMapWriteTo = $this->getOption( 'sourceMapWriteTo' );
|
||||
|
||||
if ( !$sourceMapUrl && $sourceMapFilename ) {
|
||||
$sourceMapUrl = $this->normalizeFilename( $sourceMapFilename );
|
||||
}
|
||||
|
||||
// write map to a file
|
||||
if ( $sourceMapWriteTo ) {
|
||||
$this->saveMap( $sourceMapWriteTo, $sourceMapContent );
|
||||
}
|
||||
|
||||
// inline the map
|
||||
if ( !$sourceMapUrl ) {
|
||||
$sourceMapUrl = sprintf( 'data:application/json,%s', Less_Functions::encodeURIComponent( $sourceMapContent ) );
|
||||
}
|
||||
|
||||
if ( $sourceMapUrl ) {
|
||||
$output->add( sprintf( '/*# sourceMappingURL=%s */', $sourceMapUrl ) );
|
||||
}
|
||||
|
||||
return $output->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the source map to a file
|
||||
*
|
||||
* @param string $file The absolute path to a file
|
||||
* @param string $content The content to write
|
||||
* @throws Exception If the file could not be saved
|
||||
*/
|
||||
protected function saveMap( $file, $content ) {
|
||||
$dir = dirname( $file );
|
||||
// directory does not exist
|
||||
if ( !is_dir( $dir ) ) {
|
||||
// FIXME: create the dir automatically?
|
||||
throw new Exception( sprintf( 'The directory "%s" does not exist. Cannot save the source map.', $dir ) );
|
||||
}
|
||||
// FIXME: proper saving, with dir write check!
|
||||
if ( file_put_contents( $file, $content ) === false ) {
|
||||
throw new Exception( sprintf( 'Cannot save the source map to "%s"', $file ) );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the filename
|
||||
*
|
||||
* @param string $filename
|
||||
* @return string
|
||||
*/
|
||||
protected function normalizeFilename( $filename ) {
|
||||
$filename = $this->fixWindowsPath( $filename );
|
||||
|
||||
$rootpath = $this->getOption( 'sourceMapRootpath' );
|
||||
$basePath = $this->getOption( 'sourceMapBasepath' );
|
||||
|
||||
// "Trim" the 'sourceMapBasepath' from the output filename.
|
||||
if ( is_string( $basePath ) && strpos( $filename, $basePath ) === 0 ) {
|
||||
$filename = substr( $filename, strlen( $basePath ) );
|
||||
}
|
||||
|
||||
// Remove extra leading path separators.
|
||||
if ( strpos( $filename, '\\' ) === 0 || strpos( $filename, '/' ) === 0 ) {
|
||||
$filename = substr( $filename, 1 );
|
||||
}
|
||||
|
||||
return $rootpath . $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a mapping
|
||||
*
|
||||
* @param integer $generatedLine The line number in generated file
|
||||
* @param integer $generatedColumn The column number in generated file
|
||||
* @param integer $originalLine The line number in original file
|
||||
* @param integer $originalColumn The column number in original file
|
||||
* @param string $sourceFile The original source file
|
||||
*/
|
||||
public function addMapping( $generatedLine, $generatedColumn, $originalLine, $originalColumn, $fileInfo ) {
|
||||
$this->mappings[] = array(
|
||||
'generated_line' => $generatedLine,
|
||||
'generated_column' => $generatedColumn,
|
||||
'original_line' => $originalLine,
|
||||
'original_column' => $originalColumn,
|
||||
'source_file' => $fileInfo['currentUri']
|
||||
);
|
||||
|
||||
$this->sources[$fileInfo['currentUri']] = $fileInfo['filename'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the JSON source map
|
||||
*
|
||||
* @return string
|
||||
* @see https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#
|
||||
*/
|
||||
protected function generateJson() {
|
||||
$sourceMap = array();
|
||||
$mappings = $this->generateMappings();
|
||||
|
||||
// File version (always the first entry in the object) and must be a positive integer.
|
||||
$sourceMap['version'] = self::VERSION;
|
||||
|
||||
// An optional name of the generated code that this source map is associated with.
|
||||
$file = $this->getOption( 'sourceMapFilename' );
|
||||
if ( $file ) {
|
||||
$sourceMap['file'] = $file;
|
||||
}
|
||||
|
||||
// An optional source root, useful for relocating source files on a server or removing repeated values in the 'sources' entry. This value is prepended to the individual entries in the 'source' field.
|
||||
$root = $this->getOption( 'sourceRoot' );
|
||||
if ( $root ) {
|
||||
$sourceMap['sourceRoot'] = $root;
|
||||
}
|
||||
|
||||
// A list of original sources used by the 'mappings' entry.
|
||||
$sourceMap['sources'] = array();
|
||||
foreach ( $this->sources as $source_uri => $source_filename ) {
|
||||
$sourceMap['sources'][] = $this->normalizeFilename( $source_filename );
|
||||
}
|
||||
|
||||
// A list of symbol names used by the 'mappings' entry.
|
||||
$sourceMap['names'] = array();
|
||||
|
||||
// A string with the encoded mapping data.
|
||||
$sourceMap['mappings'] = $mappings;
|
||||
|
||||
if ( $this->getOption( 'outputSourceFiles' ) ) {
|
||||
// An optional list of source content, useful when the 'source' can't be hosted.
|
||||
// The contents are listed in the same order as the sources above.
|
||||
// 'null' may be used if some original sources should be retrieved by name.
|
||||
$sourceMap['sourcesContent'] = $this->getSourcesContent();
|
||||
}
|
||||
|
||||
// less.js compat fixes
|
||||
if ( count( $sourceMap['sources'] ) && empty( $sourceMap['sourceRoot'] ) ) {
|
||||
unset( $sourceMap['sourceRoot'] );
|
||||
}
|
||||
|
||||
return json_encode( $sourceMap );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sources contents
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
protected function getSourcesContent() {
|
||||
if ( empty( $this->sources ) ) {
|
||||
return;
|
||||
}
|
||||
$content = array();
|
||||
foreach ( $this->sources as $sourceFile ) {
|
||||
$content[] = file_get_contents( $sourceFile );
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the mappings string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateMappings() {
|
||||
if ( !count( $this->mappings ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$this->source_keys = array_flip( array_keys( $this->sources ) );
|
||||
|
||||
// group mappings by generated line number.
|
||||
$groupedMap = $groupedMapEncoded = array();
|
||||
foreach ( $this->mappings as $m ) {
|
||||
$groupedMap[$m['generated_line']][] = $m;
|
||||
}
|
||||
ksort( $groupedMap );
|
||||
|
||||
$lastGeneratedLine = $lastOriginalIndex = $lastOriginalLine = $lastOriginalColumn = 0;
|
||||
|
||||
foreach ( $groupedMap as $lineNumber => $line_map ) {
|
||||
while ( ++$lastGeneratedLine < $lineNumber ) {
|
||||
$groupedMapEncoded[] = ';';
|
||||
}
|
||||
|
||||
$lineMapEncoded = array();
|
||||
$lastGeneratedColumn = 0;
|
||||
|
||||
foreach ( $line_map as $m ) {
|
||||
$mapEncoded = $this->encoder->encode( $m['generated_column'] - $lastGeneratedColumn );
|
||||
$lastGeneratedColumn = $m['generated_column'];
|
||||
|
||||
// find the index
|
||||
if ( $m['source_file'] ) {
|
||||
$index = $this->findFileIndex( $m['source_file'] );
|
||||
if ( $index !== false ) {
|
||||
$mapEncoded .= $this->encoder->encode( $index - $lastOriginalIndex );
|
||||
$lastOriginalIndex = $index;
|
||||
|
||||
// lines are stored 0-based in SourceMap spec version 3
|
||||
$mapEncoded .= $this->encoder->encode( $m['original_line'] - 1 - $lastOriginalLine );
|
||||
$lastOriginalLine = $m['original_line'] - 1;
|
||||
|
||||
$mapEncoded .= $this->encoder->encode( $m['original_column'] - $lastOriginalColumn );
|
||||
$lastOriginalColumn = $m['original_column'];
|
||||
}
|
||||
}
|
||||
|
||||
$lineMapEncoded[] = $mapEncoded;
|
||||
}
|
||||
|
||||
$groupedMapEncoded[] = implode( ',', $lineMapEncoded ) . ';';
|
||||
}
|
||||
|
||||
return rtrim( implode( $groupedMapEncoded ), ';' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the index for the filename
|
||||
*
|
||||
* @param string $filename
|
||||
* @return integer|false
|
||||
*/
|
||||
protected function findFileIndex( $filename ) {
|
||||
return $this->source_keys[$filename];
|
||||
}
|
||||
|
||||
/**
|
||||
* fix windows paths
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
public function fixWindowsPath( $path, $addEndSlash = false ) {
|
||||
$slash = ( $addEndSlash ) ? '/' : '';
|
||||
if ( !empty( $path ) ) {
|
||||
$path = str_replace( '\\', '/', $path );
|
||||
$path = rtrim( $path, '/' ) . $slash;
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
}
|
84
less.php/lib/Less/Tree.php
Normal file
84
less.php/lib/Less/Tree.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Tree
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree {
|
||||
|
||||
public $cache_string;
|
||||
|
||||
public function toCSS() {
|
||||
$output = new Less_Output();
|
||||
$this->genCSS( $output );
|
||||
return $output->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate CSS by adding it to the output object
|
||||
*
|
||||
* @param Less_Output $output The output
|
||||
* @return void
|
||||
*/
|
||||
public function genCSS( $output ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Less_Tree_Ruleset[] $rules
|
||||
*/
|
||||
public static function outputRuleset( $output, $rules ) {
|
||||
$ruleCnt = count( $rules );
|
||||
Less_Environment::$tabLevel++;
|
||||
|
||||
// Compressed
|
||||
if ( Less_Parser::$options['compress'] ) {
|
||||
$output->add( '{' );
|
||||
for ( $i = 0; $i < $ruleCnt; $i++ ) {
|
||||
$rules[$i]->genCSS( $output );
|
||||
}
|
||||
|
||||
$output->add( '}' );
|
||||
Less_Environment::$tabLevel--;
|
||||
return;
|
||||
}
|
||||
|
||||
// Non-compressed
|
||||
$tabSetStr = "\n".str_repeat( Less_Parser::$options['indentation'], Less_Environment::$tabLevel - 1 );
|
||||
$tabRuleStr = $tabSetStr.Less_Parser::$options['indentation'];
|
||||
|
||||
$output->add( " {" );
|
||||
for ( $i = 0; $i < $ruleCnt; $i++ ) {
|
||||
$output->add( $tabRuleStr );
|
||||
$rules[$i]->genCSS( $output );
|
||||
}
|
||||
Less_Environment::$tabLevel--;
|
||||
$output->add( $tabSetStr.'}' );
|
||||
|
||||
}
|
||||
|
||||
public function accept( $visitor ) {
|
||||
}
|
||||
|
||||
public static function ReferencedArray( $rules ) {
|
||||
foreach ( $rules as $rule ) {
|
||||
if ( method_exists( $rule, 'markReferenced' ) ) {
|
||||
$rule->markReferenced();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requires php 5.3+
|
||||
*/
|
||||
public static function __set_state( $args ) {
|
||||
$class = get_called_class();
|
||||
$obj = new $class( null, null, null, null );
|
||||
foreach ( $args as $key => $val ) {
|
||||
$obj->$key = $val;
|
||||
}
|
||||
return $obj;
|
||||
}
|
||||
|
||||
}
|
48
less.php/lib/Less/Tree/Alpha.php
Normal file
48
less.php/lib/Less/Tree/Alpha.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Alpha
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Alpha extends Less_Tree {
|
||||
public $value;
|
||||
public $type = 'Alpha';
|
||||
|
||||
public function __construct( $val ) {
|
||||
$this->value = $val;
|
||||
}
|
||||
|
||||
// function accept( $visitor ){
|
||||
// $this->value = $visitor->visit( $this->value );
|
||||
//}
|
||||
|
||||
public function compile( $env ) {
|
||||
if ( is_object( $this->value ) ) {
|
||||
$this->value = $this->value->compile( $env );
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Less_Tree::genCSS
|
||||
*/
|
||||
public function genCSS( $output ) {
|
||||
$output->add( "alpha(opacity=" );
|
||||
|
||||
if ( is_string( $this->value ) ) {
|
||||
$output->add( $this->value );
|
||||
} else {
|
||||
$this->value->genCSS( $output );
|
||||
}
|
||||
|
||||
$output->add( ')' );
|
||||
}
|
||||
|
||||
public function toCSS() {
|
||||
return "alpha(opacity=" . ( is_string( $this->value ) ? $this->value : $this->value->toCSS() ) . ")";
|
||||
}
|
||||
|
||||
}
|
58
less.php/lib/Less/Tree/Anonymous.php
Normal file
58
less.php/lib/Less/Tree/Anonymous.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Anonymous
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Anonymous extends Less_Tree {
|
||||
public $value;
|
||||
public $quote;
|
||||
public $index;
|
||||
public $mapLines;
|
||||
public $currentFileInfo;
|
||||
public $type = 'Anonymous';
|
||||
|
||||
/**
|
||||
* @param integer $index
|
||||
* @param boolean $mapLines
|
||||
*/
|
||||
public function __construct( $value, $index = null, $currentFileInfo = null, $mapLines = null ) {
|
||||
$this->value = $value;
|
||||
$this->index = $index;
|
||||
$this->mapLines = $mapLines;
|
||||
$this->currentFileInfo = $currentFileInfo;
|
||||
}
|
||||
|
||||
public function compile() {
|
||||
return new Less_Tree_Anonymous( $this->value, $this->index, $this->currentFileInfo, $this->mapLines );
|
||||
}
|
||||
|
||||
public function compare( $x ) {
|
||||
if ( !is_object( $x ) ) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
$left = $this->toCSS();
|
||||
$right = $x->toCSS();
|
||||
|
||||
if ( $left === $right ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $left < $right ? -1 : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Less_Tree::genCSS
|
||||
*/
|
||||
public function genCSS( $output ) {
|
||||
$output->add( $this->value, $this->currentFileInfo, $this->index, $this->mapLines );
|
||||
}
|
||||
|
||||
public function toCSS() {
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
}
|
39
less.php/lib/Less/Tree/Assignment.php
Normal file
39
less.php/lib/Less/Tree/Assignment.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Assignment
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Assignment extends Less_Tree {
|
||||
|
||||
public $key;
|
||||
public $value;
|
||||
public $type = 'Assignment';
|
||||
|
||||
public function __construct( $key, $val ) {
|
||||
$this->key = $key;
|
||||
$this->value = $val;
|
||||
}
|
||||
|
||||
public function accept( $visitor ) {
|
||||
$this->value = $visitor->visitObj( $this->value );
|
||||
}
|
||||
|
||||
public function compile( $env ) {
|
||||
return new Less_Tree_Assignment( $this->key, $this->value->compile( $env ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Less_Tree::genCSS
|
||||
*/
|
||||
public function genCSS( $output ) {
|
||||
$output->add( $this->key . '=' );
|
||||
$this->value->genCSS( $output );
|
||||
}
|
||||
|
||||
public function toCss() {
|
||||
return $this->key . '=' . $this->value->toCSS();
|
||||
}
|
||||
}
|
53
less.php/lib/Less/Tree/Attribute.php
Normal file
53
less.php/lib/Less/Tree/Attribute.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Attribute
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Attribute extends Less_Tree {
|
||||
|
||||
public $key;
|
||||
public $op;
|
||||
public $value;
|
||||
public $type = 'Attribute';
|
||||
|
||||
public function __construct( $key, $op, $value ) {
|
||||
$this->key = $key;
|
||||
$this->op = $op;
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function compile( $env ) {
|
||||
$key_obj = is_object( $this->key );
|
||||
$val_obj = is_object( $this->value );
|
||||
|
||||
if ( !$key_obj && !$val_obj ) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return new Less_Tree_Attribute(
|
||||
$key_obj ? $this->key->compile( $env ) : $this->key,
|
||||
$this->op,
|
||||
$val_obj ? $this->value->compile( $env ) : $this->value );
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Less_Tree::genCSS
|
||||
*/
|
||||
public function genCSS( $output ) {
|
||||
$output->add( $this->toCSS() );
|
||||
}
|
||||
|
||||
public function toCSS() {
|
||||
$value = $this->key;
|
||||
|
||||
if ( $this->op ) {
|
||||
$value .= $this->op;
|
||||
$value .= ( is_object( $this->value ) ? $this->value->toCSS() : $this->value );
|
||||
}
|
||||
|
||||
return '[' . $value . ']';
|
||||
}
|
||||
}
|
117
less.php/lib/Less/Tree/Call.php
Normal file
117
less.php/lib/Less/Tree/Call.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Call
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Call extends Less_Tree {
|
||||
public $value;
|
||||
|
||||
public $name;
|
||||
public $args;
|
||||
public $index;
|
||||
public $currentFileInfo;
|
||||
public $type = 'Call';
|
||||
|
||||
public function __construct( $name, $args, $index, $currentFileInfo = null ) {
|
||||
$this->name = $name;
|
||||
$this->args = $args;
|
||||
$this->index = $index;
|
||||
$this->currentFileInfo = $currentFileInfo;
|
||||
}
|
||||
|
||||
public function accept( $visitor ) {
|
||||
$this->args = $visitor->visitArray( $this->args );
|
||||
}
|
||||
|
||||
//
|
||||
// When evaluating a function call,
|
||||
// we either find the function in `tree.functions` [1],
|
||||
// in which case we call it, passing the evaluated arguments,
|
||||
// or we simply print it out as it appeared originally [2].
|
||||
//
|
||||
// The *functions.js* file contains the built-in functions.
|
||||
//
|
||||
// The reason why we evaluate the arguments, is in the case where
|
||||
// we try to pass a variable to a function, like: `saturate(@color)`.
|
||||
// The function should receive the value, not the variable.
|
||||
//
|
||||
public function compile( $env = null ) {
|
||||
$args = array();
|
||||
foreach ( $this->args as $a ) {
|
||||
$args[] = $a->compile( $env );
|
||||
}
|
||||
|
||||
$nameLC = strtolower( $this->name );
|
||||
switch ( $nameLC ) {
|
||||
case '%':
|
||||
$nameLC = '_percent';
|
||||
break;
|
||||
|
||||
case 'get-unit':
|
||||
$nameLC = 'getunit';
|
||||
break;
|
||||
|
||||
case 'data-uri':
|
||||
$nameLC = 'datauri';
|
||||
break;
|
||||
|
||||
case 'svg-gradient':
|
||||
$nameLC = 'svggradient';
|
||||
break;
|
||||
}
|
||||
|
||||
$result = null;
|
||||
if ( $nameLC === 'default' ) {
|
||||
$result = Less_Tree_DefaultFunc::compile();
|
||||
|
||||
} else {
|
||||
|
||||
if ( method_exists( 'Less_Functions', $nameLC ) ) { // 1.
|
||||
try {
|
||||
|
||||
$func = new Less_Functions( $env, $this->currentFileInfo );
|
||||
$result = call_user_func_array( array( $func,$nameLC ), $args );
|
||||
|
||||
} catch ( Exception $e ) {
|
||||
throw new Less_Exception_Compiler( 'error evaluating function `' . $this->name . '` '.$e->getMessage().' index: '. $this->index );
|
||||
}
|
||||
} elseif ( isset( $env->functions[$nameLC] ) && is_callable( $env->functions[$nameLC] ) ) {
|
||||
try {
|
||||
$result = call_user_func_array( $env->functions[$nameLC], $args );
|
||||
} catch ( Exception $e ) {
|
||||
throw new Less_Exception_Compiler( 'error evaluating function `' . $this->name . '` '.$e->getMessage().' index: '. $this->index );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $result !== null ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return new Less_Tree_Call( $this->name, $args, $this->index, $this->currentFileInfo );
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Less_Tree::genCSS
|
||||
*/
|
||||
public function genCSS( $output ) {
|
||||
$output->add( $this->name . '(', $this->currentFileInfo, $this->index );
|
||||
$args_len = count( $this->args );
|
||||
for ( $i = 0; $i < $args_len; $i++ ) {
|
||||
$this->args[$i]->genCSS( $output );
|
||||
if ( $i + 1 < $args_len ) {
|
||||
$output->add( ', ' );
|
||||
}
|
||||
}
|
||||
|
||||
$output->add( ')' );
|
||||
}
|
||||
|
||||
// public function toCSS(){
|
||||
// return $this->compile()->toCSS();
|
||||
//}
|
||||
|
||||
}
|
230
less.php/lib/Less/Tree/Color.php
Normal file
230
less.php/lib/Less/Tree/Color.php
Normal file
@ -0,0 +1,230 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Color
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Color extends Less_Tree {
|
||||
public $rgb;
|
||||
public $alpha;
|
||||
public $isTransparentKeyword;
|
||||
public $type = 'Color';
|
||||
|
||||
public function __construct( $rgb, $a = 1, $isTransparentKeyword = null ) {
|
||||
if ( $isTransparentKeyword ) {
|
||||
$this->rgb = $rgb;
|
||||
$this->alpha = $a;
|
||||
$this->isTransparentKeyword = true;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->rgb = array();
|
||||
if ( is_array( $rgb ) ) {
|
||||
$this->rgb = $rgb;
|
||||
} else if ( strlen( $rgb ) == 6 ) {
|
||||
foreach ( str_split( $rgb, 2 ) as $c ) {
|
||||
$this->rgb[] = hexdec( $c );
|
||||
}
|
||||
} else {
|
||||
foreach ( str_split( $rgb, 1 ) as $c ) {
|
||||
$this->rgb[] = hexdec( $c.$c );
|
||||
}
|
||||
}
|
||||
$this->alpha = is_numeric( $a ) ? $a : 1;
|
||||
}
|
||||
|
||||
public function compile() {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function luma() {
|
||||
$r = $this->rgb[0] / 255;
|
||||
$g = $this->rgb[1] / 255;
|
||||
$b = $this->rgb[2] / 255;
|
||||
|
||||
$r = ( $r <= 0.03928 ) ? $r / 12.92 : pow( ( ( $r + 0.055 ) / 1.055 ), 2.4 );
|
||||
$g = ( $g <= 0.03928 ) ? $g / 12.92 : pow( ( ( $g + 0.055 ) / 1.055 ), 2.4 );
|
||||
$b = ( $b <= 0.03928 ) ? $b / 12.92 : pow( ( ( $b + 0.055 ) / 1.055 ), 2.4 );
|
||||
|
||||
return 0.2126 * $r + 0.7152 * $g + 0.0722 * $b;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Less_Tree::genCSS
|
||||
*/
|
||||
public function genCSS( $output ) {
|
||||
$output->add( $this->toCSS() );
|
||||
}
|
||||
|
||||
public function toCSS( $doNotCompress = false ) {
|
||||
$compress = Less_Parser::$options['compress'] && !$doNotCompress;
|
||||
$alpha = Less_Functions::fround( $this->alpha );
|
||||
|
||||
//
|
||||
// If we have some transparency, the only way to represent it
|
||||
// is via `rgba`. Otherwise, we use the hex representation,
|
||||
// which has better compatibility with older browsers.
|
||||
// Values are capped between `0` and `255`, rounded and zero-padded.
|
||||
//
|
||||
if ( $alpha < 1 ) {
|
||||
if ( ( $alpha === 0 || $alpha === 0.0 ) && isset( $this->isTransparentKeyword ) && $this->isTransparentKeyword ) {
|
||||
return 'transparent';
|
||||
}
|
||||
|
||||
$values = array();
|
||||
foreach ( $this->rgb as $c ) {
|
||||
$values[] = Less_Functions::clamp( round( $c ), 255 );
|
||||
}
|
||||
$values[] = $alpha;
|
||||
|
||||
$glue = ( $compress ? ',' : ', ' );
|
||||
return "rgba(" . implode( $glue, $values ) . ")";
|
||||
} else {
|
||||
|
||||
$color = $this->toRGB();
|
||||
|
||||
if ( $compress ) {
|
||||
|
||||
// Convert color to short format
|
||||
if ( $color[1] === $color[2] && $color[3] === $color[4] && $color[5] === $color[6] ) {
|
||||
$color = '#'.$color[1] . $color[3] . $color[5];
|
||||
}
|
||||
}
|
||||
|
||||
return $color;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Operations have to be done per-channel, if not,
|
||||
// channels will spill onto each other. Once we have
|
||||
// our result, in the form of an integer triplet,
|
||||
// we create a new Color node to hold the result.
|
||||
//
|
||||
|
||||
/**
|
||||
* @param string $op
|
||||
*/
|
||||
public function operate( $op, $other ) {
|
||||
$rgb = array();
|
||||
$alpha = $this->alpha * ( 1 - $other->alpha ) + $other->alpha;
|
||||
for ( $c = 0; $c < 3; $c++ ) {
|
||||
$rgb[$c] = Less_Functions::operate( $op, $this->rgb[$c], $other->rgb[$c] );
|
||||
}
|
||||
return new Less_Tree_Color( $rgb, $alpha );
|
||||
}
|
||||
|
||||
public function toRGB() {
|
||||
return $this->toHex( $this->rgb );
|
||||
}
|
||||
|
||||
public function toHSL() {
|
||||
$r = $this->rgb[0] / 255;
|
||||
$g = $this->rgb[1] / 255;
|
||||
$b = $this->rgb[2] / 255;
|
||||
$a = $this->alpha;
|
||||
|
||||
$max = max( $r, $g, $b );
|
||||
$min = min( $r, $g, $b );
|
||||
$l = ( $max + $min ) / 2;
|
||||
$d = $max - $min;
|
||||
|
||||
$h = $s = 0;
|
||||
if ( $max !== $min ) {
|
||||
$s = $l > 0.5 ? $d / ( 2 - $max - $min ) : $d / ( $max + $min );
|
||||
|
||||
switch ( $max ) {
|
||||
case $r: $h = ( $g - $b ) / $d + ( $g < $b ? 6 : 0 );
|
||||
break;
|
||||
case $g: $h = ( $b - $r ) / $d + 2;
|
||||
break;
|
||||
case $b: $h = ( $r - $g ) / $d + 4;
|
||||
break;
|
||||
}
|
||||
$h /= 6;
|
||||
}
|
||||
return array( 'h' => $h * 360, 's' => $s, 'l' => $l, 'a' => $a );
|
||||
}
|
||||
|
||||
// Adapted from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
|
||||
public function toHSV() {
|
||||
$r = $this->rgb[0] / 255;
|
||||
$g = $this->rgb[1] / 255;
|
||||
$b = $this->rgb[2] / 255;
|
||||
$a = $this->alpha;
|
||||
|
||||
$max = max( $r, $g, $b );
|
||||
$min = min( $r, $g, $b );
|
||||
|
||||
$v = $max;
|
||||
|
||||
$d = $max - $min;
|
||||
if ( $max === 0 ) {
|
||||
$s = 0;
|
||||
} else {
|
||||
$s = $d / $max;
|
||||
}
|
||||
|
||||
$h = 0;
|
||||
if ( $max !== $min ) {
|
||||
switch ( $max ) {
|
||||
case $r: $h = ( $g - $b ) / $d + ( $g < $b ? 6 : 0 );
|
||||
break;
|
||||
case $g: $h = ( $b - $r ) / $d + 2;
|
||||
break;
|
||||
case $b: $h = ( $r - $g ) / $d + 4;
|
||||
break;
|
||||
}
|
||||
$h /= 6;
|
||||
}
|
||||
return array( 'h' => $h * 360, 's' => $s, 'v' => $v, 'a' => $a );
|
||||
}
|
||||
|
||||
public function toARGB() {
|
||||
$argb = array_merge( (array)Less_Parser::round( $this->alpha * 255 ), $this->rgb );
|
||||
return $this->toHex( $argb );
|
||||
}
|
||||
|
||||
public function compare( $x ) {
|
||||
if ( !property_exists( $x, 'rgb' ) ) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ( $x->rgb[0] === $this->rgb[0] &&
|
||||
$x->rgb[1] === $this->rgb[1] &&
|
||||
$x->rgb[2] === $this->rgb[2] &&
|
||||
$x->alpha === $this->alpha ) ? 0 : -1;
|
||||
}
|
||||
|
||||
public function toHex( $v ) {
|
||||
$ret = '#';
|
||||
foreach ( $v as $c ) {
|
||||
$c = Less_Functions::clamp( Less_Parser::round( $c ), 255 );
|
||||
if ( $c < 16 ) {
|
||||
$ret .= '0';
|
||||
}
|
||||
$ret .= dechex( $c );
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $keyword
|
||||
*/
|
||||
public static function fromKeyword( $keyword ) {
|
||||
$keyword = strtolower( $keyword );
|
||||
|
||||
if ( Less_Colors::hasOwnProperty( $keyword ) ) {
|
||||
// detect named color
|
||||
return new Less_Tree_Color( substr( Less_Colors::color( $keyword ), 1 ) );
|
||||
}
|
||||
|
||||
if ( $keyword === 'transparent' ) {
|
||||
return new Less_Tree_Color( array( 0, 0, 0 ), 0, true );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
51
less.php/lib/Less/Tree/Comment.php
Normal file
51
less.php/lib/Less/Tree/Comment.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Comment
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Comment extends Less_Tree {
|
||||
|
||||
public $value;
|
||||
public $silent;
|
||||
public $isReferenced;
|
||||
public $currentFileInfo;
|
||||
public $type = 'Comment';
|
||||
|
||||
public function __construct( $value, $silent, $index = null, $currentFileInfo = null ) {
|
||||
$this->value = $value;
|
||||
$this->silent = !!$silent;
|
||||
$this->currentFileInfo = $currentFileInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Less_Tree::genCSS
|
||||
*/
|
||||
public function genCSS( $output ) {
|
||||
// if( $this->debugInfo ){
|
||||
//$output->add( tree.debugInfo($env, $this), $this->currentFileInfo, $this->index);
|
||||
//}
|
||||
$output->add( trim( $this->value ) );// TODO shouldn't need to trim, we shouldn't grab the \n
|
||||
}
|
||||
|
||||
public function toCSS() {
|
||||
return Less_Parser::$options['compress'] ? '' : $this->value;
|
||||
}
|
||||
|
||||
public function isSilent() {
|
||||
$isReference = ( $this->currentFileInfo && isset( $this->currentFileInfo['reference'] ) && ( !isset( $this->isReferenced ) || !$this->isReferenced ) );
|
||||
$isCompressed = Less_Parser::$options['compress'] && !preg_match( '/^\/\*!/', $this->value );
|
||||
return $this->silent || $isReference || $isCompressed;
|
||||
}
|
||||
|
||||
public function compile() {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function markReferenced() {
|
||||
$this->isReferenced = true;
|
||||
}
|
||||
|
||||
}
|
72
less.php/lib/Less/Tree/Condition.php
Normal file
72
less.php/lib/Less/Tree/Condition.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Condition
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Condition extends Less_Tree {
|
||||
|
||||
public $op;
|
||||
public $lvalue;
|
||||
public $rvalue;
|
||||
public $index;
|
||||
public $negate;
|
||||
public $type = 'Condition';
|
||||
|
||||
public function __construct( $op, $l, $r, $i = 0, $negate = false ) {
|
||||
$this->op = trim( $op );
|
||||
$this->lvalue = $l;
|
||||
$this->rvalue = $r;
|
||||
$this->index = $i;
|
||||
$this->negate = $negate;
|
||||
}
|
||||
|
||||
public function accept( $visitor ) {
|
||||
$this->lvalue = $visitor->visitObj( $this->lvalue );
|
||||
$this->rvalue = $visitor->visitObj( $this->rvalue );
|
||||
}
|
||||
|
||||
public function compile( $env ) {
|
||||
$a = $this->lvalue->compile( $env );
|
||||
$b = $this->rvalue->compile( $env );
|
||||
|
||||
switch ( $this->op ) {
|
||||
case 'and':
|
||||
$result = $a && $b;
|
||||
break;
|
||||
|
||||
case 'or':
|
||||
$result = $a || $b;
|
||||
break;
|
||||
|
||||
default:
|
||||
if ( Less_Parser::is_method( $a, 'compare' ) ) {
|
||||
$result = $a->compare( $b );
|
||||
} elseif ( Less_Parser::is_method( $b, 'compare' ) ) {
|
||||
$result = $b->compare( $a );
|
||||
} else {
|
||||
throw new Less_Exception_Compiler( 'Unable to perform comparison', null, $this->index );
|
||||
}
|
||||
|
||||
switch ( $result ) {
|
||||
case -1:
|
||||
$result = $this->op === '<' || $this->op === '=<' || $this->op === '<=';
|
||||
break;
|
||||
|
||||
case 0:
|
||||
$result = $this->op === '=' || $this->op === '>=' || $this->op === '=<' || $this->op === '<=';
|
||||
break;
|
||||
|
||||
case 1:
|
||||
$result = $this->op === '>' || $this->op === '>=';
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $this->negate ? !$result : $result;
|
||||
}
|
||||
|
||||
}
|
34
less.php/lib/Less/Tree/DefaultFunc.php
Normal file
34
less.php/lib/Less/Tree/DefaultFunc.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* DefaultFunc
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_DefaultFunc {
|
||||
|
||||
static $error_;
|
||||
static $value_;
|
||||
|
||||
public static function compile() {
|
||||
if ( self::$error_ ) {
|
||||
throw new Exception( self::$error_ );
|
||||
}
|
||||
if ( self::$value_ !== null ) {
|
||||
return self::$value_ ? new Less_Tree_Keyword( 'true' ) : new Less_Tree_Keyword( 'false' );
|
||||
}
|
||||
}
|
||||
|
||||
public static function value( $v ) {
|
||||
self::$value_ = $v;
|
||||
}
|
||||
|
||||
public static function error( $e ) {
|
||||
self::$error_ = $e;
|
||||
}
|
||||
|
||||
public static function reset() {
|
||||
self::$value_ = self::$error_ = null;
|
||||
}
|
||||
}
|
39
less.php/lib/Less/Tree/DetachedRuleset.php
Normal file
39
less.php/lib/Less/Tree/DetachedRuleset.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* DetachedRuleset
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_DetachedRuleset extends Less_Tree {
|
||||
|
||||
public $ruleset;
|
||||
public $frames;
|
||||
public $type = 'DetachedRuleset';
|
||||
|
||||
public function __construct( $ruleset, $frames = null ) {
|
||||
$this->ruleset = $ruleset;
|
||||
$this->frames = $frames;
|
||||
}
|
||||
|
||||
public function accept( $visitor ) {
|
||||
$this->ruleset = $visitor->visitObj( $this->ruleset );
|
||||
}
|
||||
|
||||
public function compile( $env ) {
|
||||
if ( $this->frames ) {
|
||||
$frames = $this->frames;
|
||||
} else {
|
||||
$frames = $env->frames;
|
||||
}
|
||||
return new Less_Tree_DetachedRuleset( $this->ruleset, $frames );
|
||||
}
|
||||
|
||||
public function callEval( $env ) {
|
||||
if ( $this->frames ) {
|
||||
return $this->ruleset->compile( $env->copyEvalEnv( array_merge( $this->frames, $env->frames ) ) );
|
||||
}
|
||||
return $this->ruleset->compile( $env );
|
||||
}
|
||||
}
|
198
less.php/lib/Less/Tree/Dimension.php
Normal file
198
less.php/lib/Less/Tree/Dimension.php
Normal file
@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Dimension
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Dimension extends Less_Tree {
|
||||
|
||||
public $value;
|
||||
public $unit;
|
||||
public $type = 'Dimension';
|
||||
|
||||
public function __construct( $value, $unit = null ) {
|
||||
$this->value = floatval( $value );
|
||||
|
||||
if ( $unit && ( $unit instanceof Less_Tree_Unit ) ) {
|
||||
$this->unit = $unit;
|
||||
} elseif ( $unit ) {
|
||||
$this->unit = new Less_Tree_Unit( array( $unit ) );
|
||||
} else {
|
||||
$this->unit = new Less_Tree_Unit();
|
||||
}
|
||||
}
|
||||
|
||||
public function accept( $visitor ) {
|
||||
$this->unit = $visitor->visitObj( $this->unit );
|
||||
}
|
||||
|
||||
public function compile() {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toColor() {
|
||||
return new Less_Tree_Color( array( $this->value, $this->value, $this->value ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Less_Tree::genCSS
|
||||
*/
|
||||
public function genCSS( $output ) {
|
||||
if ( Less_Parser::$options['strictUnits'] && !$this->unit->isSingular() ) {
|
||||
throw new Less_Exception_Compiler( "Multiple units in dimension. Correct the units or use the unit function. Bad unit: ".$this->unit->toString() );
|
||||
}
|
||||
|
||||
$value = Less_Functions::fround( $this->value );
|
||||
$strValue = (string)$value;
|
||||
|
||||
if ( $value !== 0 && $value < 0.000001 && $value > -0.000001 ) {
|
||||
// would be output 1e-6 etc.
|
||||
$strValue = number_format( $strValue, 10 );
|
||||
$strValue = preg_replace( '/\.?0+$/', '', $strValue );
|
||||
}
|
||||
|
||||
if ( Less_Parser::$options['compress'] ) {
|
||||
// Zero values doesn't need a unit
|
||||
if ( $value === 0 && $this->unit->isLength() ) {
|
||||
$output->add( $strValue );
|
||||
return $strValue;
|
||||
}
|
||||
|
||||
// Float values doesn't need a leading zero
|
||||
if ( $value > 0 && $value < 1 && $strValue[0] === '0' ) {
|
||||
$strValue = substr( $strValue, 1 );
|
||||
}
|
||||
}
|
||||
|
||||
$output->add( $strValue );
|
||||
$this->unit->genCSS( $output );
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
return $this->toCSS();
|
||||
}
|
||||
|
||||
// In an operation between two Dimensions,
|
||||
// we default to the first Dimension's unit,
|
||||
// so `1px + 2em` will yield `3px`.
|
||||
|
||||
/**
|
||||
* @param string $op
|
||||
*/
|
||||
public function operate( $op, $other ) {
|
||||
$value = Less_Functions::operate( $op, $this->value, $other->value );
|
||||
$unit = clone $this->unit;
|
||||
|
||||
if ( $op === '+' || $op === '-' ) {
|
||||
|
||||
if ( !$unit->numerator && !$unit->denominator ) {
|
||||
$unit->numerator = $other->unit->numerator;
|
||||
$unit->denominator = $other->unit->denominator;
|
||||
} elseif ( !$other->unit->numerator && !$other->unit->denominator ) {
|
||||
// do nothing
|
||||
} else {
|
||||
$other = $other->convertTo( $this->unit->usedUnits() );
|
||||
|
||||
if ( Less_Parser::$options['strictUnits'] && $other->unit->toString() !== $unit->toCSS() ) {
|
||||
throw new Less_Exception_Compiler( "Incompatible units. Change the units or use the unit function. Bad units: '" . $unit->toString() . "' and " . $other->unit->toString() . "'." );
|
||||
}
|
||||
|
||||
$value = Less_Functions::operate( $op, $this->value, $other->value );
|
||||
}
|
||||
} elseif ( $op === '*' ) {
|
||||
$unit->numerator = array_merge( $unit->numerator, $other->unit->numerator );
|
||||
$unit->denominator = array_merge( $unit->denominator, $other->unit->denominator );
|
||||
sort( $unit->numerator );
|
||||
sort( $unit->denominator );
|
||||
$unit->cancel();
|
||||
} elseif ( $op === '/' ) {
|
||||
$unit->numerator = array_merge( $unit->numerator, $other->unit->denominator );
|
||||
$unit->denominator = array_merge( $unit->denominator, $other->unit->numerator );
|
||||
sort( $unit->numerator );
|
||||
sort( $unit->denominator );
|
||||
$unit->cancel();
|
||||
}
|
||||
return new Less_Tree_Dimension( $value, $unit );
|
||||
}
|
||||
|
||||
public function compare( $other ) {
|
||||
if ( $other instanceof Less_Tree_Dimension ) {
|
||||
|
||||
if ( $this->unit->isEmpty() || $other->unit->isEmpty() ) {
|
||||
$a = $this;
|
||||
$b = $other;
|
||||
} else {
|
||||
$a = $this->unify();
|
||||
$b = $other->unify();
|
||||
if ( $a->unit->compare( $b->unit ) !== 0 ) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
$aValue = $a->value;
|
||||
$bValue = $b->value;
|
||||
|
||||
if ( $bValue > $aValue ) {
|
||||
return -1;
|
||||
} elseif ( $bValue < $aValue ) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public function unify() {
|
||||
return $this->convertTo( array( 'length' => 'px', 'duration' => 's', 'angle' => 'rad' ) );
|
||||
}
|
||||
|
||||
public function convertTo( $conversions ) {
|
||||
$value = $this->value;
|
||||
$unit = clone $this->unit;
|
||||
|
||||
if ( is_string( $conversions ) ) {
|
||||
$derivedConversions = array();
|
||||
foreach ( Less_Tree_UnitConversions::$groups as $i ) {
|
||||
if ( isset( Less_Tree_UnitConversions::${$i}[$conversions] ) ) {
|
||||
$derivedConversions = array( $i => $conversions );
|
||||
}
|
||||
}
|
||||
$conversions = $derivedConversions;
|
||||
}
|
||||
|
||||
foreach ( $conversions as $groupName => $targetUnit ) {
|
||||
$group = Less_Tree_UnitConversions::${$groupName};
|
||||
|
||||
// numerator
|
||||
foreach ( $unit->numerator as $i => $atomicUnit ) {
|
||||
$atomicUnit = $unit->numerator[$i];
|
||||
if ( !isset( $group[$atomicUnit] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $value * ( $group[$atomicUnit] / $group[$targetUnit] );
|
||||
|
||||
$unit->numerator[$i] = $targetUnit;
|
||||
}
|
||||
|
||||
// denominator
|
||||
foreach ( $unit->denominator as $i => $atomicUnit ) {
|
||||
$atomicUnit = $unit->denominator[$i];
|
||||
if ( !isset( $group[$atomicUnit] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $value / ( $group[$atomicUnit] / $group[$targetUnit] );
|
||||
|
||||
$unit->denominator[$i] = $targetUnit;
|
||||
}
|
||||
}
|
||||
|
||||
$unit->cancel();
|
||||
|
||||
return new Less_Tree_Dimension( $value, $unit );
|
||||
}
|
||||
}
|
96
less.php/lib/Less/Tree/Directive.php
Normal file
96
less.php/lib/Less/Tree/Directive.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Directive
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Directive extends Less_Tree {
|
||||
|
||||
public $name;
|
||||
public $value;
|
||||
public $rules;
|
||||
public $index;
|
||||
public $isReferenced;
|
||||
public $currentFileInfo;
|
||||
public $debugInfo;
|
||||
public $type = 'Directive';
|
||||
|
||||
public function __construct( $name, $value = null, $rules = null, $index = null, $currentFileInfo = null, $debugInfo = null ) {
|
||||
$this->name = $name;
|
||||
$this->value = $value;
|
||||
if ( $rules ) {
|
||||
$this->rules = $rules;
|
||||
$this->rules->allowImports = true;
|
||||
}
|
||||
|
||||
$this->index = $index;
|
||||
$this->currentFileInfo = $currentFileInfo;
|
||||
$this->debugInfo = $debugInfo;
|
||||
}
|
||||
|
||||
public function accept( $visitor ) {
|
||||
if ( $this->rules ) {
|
||||
$this->rules = $visitor->visitObj( $this->rules );
|
||||
}
|
||||
if ( $this->value ) {
|
||||
$this->value = $visitor->visitObj( $this->value );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Less_Tree::genCSS
|
||||
*/
|
||||
public function genCSS( $output ) {
|
||||
$value = $this->value;
|
||||
$rules = $this->rules;
|
||||
$output->add( $this->name, $this->currentFileInfo, $this->index );
|
||||
if ( $this->value ) {
|
||||
$output->add( ' ' );
|
||||
$this->value->genCSS( $output );
|
||||
}
|
||||
if ( $this->rules ) {
|
||||
Less_Tree::outputRuleset( $output, array( $this->rules ) );
|
||||
} else {
|
||||
$output->add( ';' );
|
||||
}
|
||||
}
|
||||
|
||||
public function compile( $env ) {
|
||||
$value = $this->value;
|
||||
$rules = $this->rules;
|
||||
if ( $value ) {
|
||||
$value = $value->compile( $env );
|
||||
}
|
||||
|
||||
if ( $rules ) {
|
||||
$rules = $rules->compile( $env );
|
||||
$rules->root = true;
|
||||
}
|
||||
|
||||
return new Less_Tree_Directive( $this->name, $value, $rules, $this->index, $this->currentFileInfo, $this->debugInfo );
|
||||
}
|
||||
|
||||
public function variable( $name ) {
|
||||
if ( $this->rules ) {
|
||||
return $this->rules->variable( $name );
|
||||
}
|
||||
}
|
||||
|
||||
public function find( $selector ) {
|
||||
if ( $this->rules ) {
|
||||
return $this->rules->find( $selector, $this );
|
||||
}
|
||||
}
|
||||
|
||||
// rulesets: function () { if (this.rules) return tree.Ruleset.prototype.rulesets.apply(this.rules); },
|
||||
|
||||
public function markReferenced() {
|
||||
$this->isReferenced = true;
|
||||
if ( $this->rules ) {
|
||||
Less_Tree::ReferencedArray( $this->rules->rules );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
70
less.php/lib/Less/Tree/Element.php
Normal file
70
less.php/lib/Less/Tree/Element.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Element
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Element extends Less_Tree {
|
||||
|
||||
public $combinator = '';
|
||||
public $value = '';
|
||||
public $index;
|
||||
public $currentFileInfo;
|
||||
public $type = 'Element';
|
||||
|
||||
public $value_is_object = false;
|
||||
|
||||
public function __construct( $combinator, $value, $index = null, $currentFileInfo = null ) {
|
||||
$this->value = $value;
|
||||
$this->value_is_object = is_object( $value );
|
||||
|
||||
if ( $combinator ) {
|
||||
$this->combinator = $combinator;
|
||||
}
|
||||
|
||||
$this->index = $index;
|
||||
$this->currentFileInfo = $currentFileInfo;
|
||||
}
|
||||
|
||||
public function accept( $visitor ) {
|
||||
if ( $this->value_is_object ) { // object or string
|
||||
$this->value = $visitor->visitObj( $this->value );
|
||||
}
|
||||
}
|
||||
|
||||
public function compile( $env ) {
|
||||
if ( Less_Environment::$mixin_stack ) {
|
||||
return new Less_Tree_Element( $this->combinator, ( $this->value_is_object ? $this->value->compile( $env ) : $this->value ), $this->index, $this->currentFileInfo );
|
||||
}
|
||||
|
||||
if ( $this->value_is_object ) {
|
||||
$this->value = $this->value->compile( $env );
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Less_Tree::genCSS
|
||||
*/
|
||||
public function genCSS( $output ) {
|
||||
$output->add( $this->toCSS(), $this->currentFileInfo, $this->index );
|
||||
}
|
||||
|
||||
public function toCSS() {
|
||||
if ( $this->value_is_object ) {
|
||||
$value = $this->value->toCSS();
|
||||
} else {
|
||||
$value = $this->value;
|
||||
}
|
||||
|
||||
if ( $value === '' && $this->combinator && $this->combinator === '&' ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return Less_Environment::$_outputMap[$this->combinator] . $value;
|
||||
}
|
||||
|
||||
}
|
95
less.php/lib/Less/Tree/Expression.php
Normal file
95
less.php/lib/Less/Tree/Expression.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Expression
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Expression extends Less_Tree {
|
||||
|
||||
public $value = array();
|
||||
public $parens = false;
|
||||
public $parensInOp = false;
|
||||
public $type = 'Expression';
|
||||
|
||||
public function __construct( $value, $parens = null ) {
|
||||
$this->value = $value;
|
||||
$this->parens = $parens;
|
||||
}
|
||||
|
||||
public function accept( $visitor ) {
|
||||
$this->value = $visitor->visitArray( $this->value );
|
||||
}
|
||||
|
||||
public function compile( $env ) {
|
||||
$doubleParen = false;
|
||||
|
||||
if ( $this->parens && !$this->parensInOp ) {
|
||||
Less_Environment::$parensStack++;
|
||||
}
|
||||
|
||||
$returnValue = null;
|
||||
if ( $this->value ) {
|
||||
|
||||
$count = count( $this->value );
|
||||
|
||||
if ( $count > 1 ) {
|
||||
|
||||
$ret = array();
|
||||
foreach ( $this->value as $e ) {
|
||||
$ret[] = $e->compile( $env );
|
||||
}
|
||||
$returnValue = new Less_Tree_Expression( $ret );
|
||||
|
||||
} else {
|
||||
|
||||
if ( ( $this->value[0] instanceof Less_Tree_Expression ) && $this->value[0]->parens && !$this->value[0]->parensInOp ) {
|
||||
$doubleParen = true;
|
||||
}
|
||||
|
||||
$returnValue = $this->value[0]->compile( $env );
|
||||
}
|
||||
|
||||
} else {
|
||||
$returnValue = $this;
|
||||
}
|
||||
|
||||
if ( $this->parens ) {
|
||||
if ( !$this->parensInOp ) {
|
||||
Less_Environment::$parensStack--;
|
||||
|
||||
} elseif ( !Less_Environment::isMathOn() && !$doubleParen ) {
|
||||
$returnValue = new Less_Tree_Paren( $returnValue );
|
||||
|
||||
}
|
||||
}
|
||||
return $returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Less_Tree::genCSS
|
||||
*/
|
||||
public function genCSS( $output ) {
|
||||
$val_len = count( $this->value );
|
||||
for ( $i = 0; $i < $val_len; $i++ ) {
|
||||
$this->value[$i]->genCSS( $output );
|
||||
if ( $i + 1 < $val_len ) {
|
||||
$output->add( ' ' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function throwAwayComments() {
|
||||
if ( is_array( $this->value ) ) {
|
||||
$new_value = array();
|
||||
foreach ( $this->value as $v ) {
|
||||
if ( $v instanceof Less_Tree_Comment ) {
|
||||
continue;
|
||||
}
|
||||
$new_value[] = $v;
|
||||
}
|
||||
$this->value = $new_value;
|
||||
}
|
||||
}
|
||||
}
|
80
less.php/lib/Less/Tree/Extend.php
Normal file
80
less.php/lib/Less/Tree/Extend.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Extend
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Extend extends Less_Tree {
|
||||
|
||||
public $selector;
|
||||
public $option;
|
||||
public $index;
|
||||
public $selfSelectors = array();
|
||||
public $allowBefore;
|
||||
public $allowAfter;
|
||||
public $firstExtendOnThisSelectorPath;
|
||||
public $type = 'Extend';
|
||||
public $ruleset;
|
||||
|
||||
public $object_id;
|
||||
public $parent_ids = array();
|
||||
|
||||
/**
|
||||
* @param integer $index
|
||||
*/
|
||||
public function __construct( $selector, $option, $index ) {
|
||||
static $i = 0;
|
||||
$this->selector = $selector;
|
||||
$this->option = $option;
|
||||
$this->index = $index;
|
||||
|
||||
switch ( $option ) {
|
||||
case "all":
|
||||
$this->allowBefore = true;
|
||||
$this->allowAfter = true;
|
||||
break;
|
||||
default:
|
||||
$this->allowBefore = false;
|
||||
$this->allowAfter = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// This must use a string (instead of int) so that array_merge()
|
||||
// preserves keys on arrays that use IDs in their keys.
|
||||
$this->object_id = 'id_' . $i++;
|
||||
|
||||
$this->parent_ids = array(
|
||||
$this->object_id => true
|
||||
);
|
||||
}
|
||||
|
||||
public function accept( $visitor ) {
|
||||
$this->selector = $visitor->visitObj( $this->selector );
|
||||
}
|
||||
|
||||
public function compile( $env ) {
|
||||
Less_Parser::$has_extends = true;
|
||||
$this->selector = $this->selector->compile( $env );
|
||||
return $this;
|
||||
// return new Less_Tree_Extend( $this->selector->compile($env), $this->option, $this->index);
|
||||
}
|
||||
|
||||
public function findSelfSelectors( $selectors ) {
|
||||
$selfElements = array();
|
||||
|
||||
for ( $i = 0, $selectors_len = count( $selectors ); $i < $selectors_len; $i++ ) {
|
||||
$selectorElements = $selectors[$i]->elements;
|
||||
// duplicate the logic in genCSS function inside the selector node.
|
||||
// future TODO - move both logics into the selector joiner visitor
|
||||
if ( $i && $selectorElements && $selectorElements[0]->combinator === "" ) {
|
||||
$selectorElements[0]->combinator = ' ';
|
||||
}
|
||||
$selfElements = array_merge( $selfElements, $selectors[$i]->elements );
|
||||
}
|
||||
|
||||
$this->selfSelectors = array( new Less_Tree_Selector( $selfElements ) );
|
||||
}
|
||||
|
||||
}
|
291
less.php/lib/Less/Tree/Import.php
Normal file
291
less.php/lib/Less/Tree/Import.php
Normal file
@ -0,0 +1,291 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* CSS @import node
|
||||
*
|
||||
* The general strategy here is that we don't want to wait
|
||||
* for the parsing to be completed, before we start importing
|
||||
* the file. That's because in the context of a browser,
|
||||
* most of the time will be spent waiting for the server to respond.
|
||||
*
|
||||
* On creation, we push the import path to our import queue, though
|
||||
* `import,push`, we also pass it a callback, which it'll call once
|
||||
* the file has been fetched, and parsed.
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Import extends Less_Tree {
|
||||
|
||||
public $options;
|
||||
public $index;
|
||||
public $path;
|
||||
public $features;
|
||||
public $currentFileInfo;
|
||||
public $css;
|
||||
public $skip;
|
||||
public $root;
|
||||
public $type = 'Import';
|
||||
|
||||
public function __construct( $path, $features, $options, $index, $currentFileInfo = null ) {
|
||||
$this->options = $options;
|
||||
$this->index = $index;
|
||||
$this->path = $path;
|
||||
$this->features = $features;
|
||||
$this->currentFileInfo = $currentFileInfo;
|
||||
|
||||
if ( is_array( $options ) ) {
|
||||
$this->options += array( 'inline' => false );
|
||||
|
||||
if ( isset( $this->options['less'] ) || $this->options['inline'] ) {
|
||||
$this->css = !isset( $this->options['less'] ) || !$this->options['less'] || $this->options['inline'];
|
||||
} else {
|
||||
$pathValue = $this->getPath();
|
||||
if ( $pathValue && preg_match( '/css([\?;].*)?$/', $pathValue ) ) {
|
||||
$this->css = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// The actual import node doesn't return anything, when converted to CSS.
|
||||
// The reason is that it's used at the evaluation stage, so that the rules
|
||||
// it imports can be treated like any other rules.
|
||||
//
|
||||
// In `eval`, we make sure all Import nodes get evaluated, recursively, so
|
||||
// we end up with a flat structure, which can easily be imported in the parent
|
||||
// ruleset.
|
||||
//
|
||||
|
||||
public function accept( $visitor ) {
|
||||
if ( $this->features ) {
|
||||
$this->features = $visitor->visitObj( $this->features );
|
||||
}
|
||||
$this->path = $visitor->visitObj( $this->path );
|
||||
|
||||
if ( !$this->options['inline'] && $this->root ) {
|
||||
$this->root = $visitor->visit( $this->root );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Less_Tree::genCSS
|
||||
*/
|
||||
public function genCSS( $output ) {
|
||||
if ( $this->css ) {
|
||||
|
||||
$output->add( '@import ', $this->currentFileInfo, $this->index );
|
||||
|
||||
$this->path->genCSS( $output );
|
||||
if ( $this->features ) {
|
||||
$output->add( ' ' );
|
||||
$this->features->genCSS( $output );
|
||||
}
|
||||
$output->add( ';' );
|
||||
}
|
||||
}
|
||||
|
||||
public function toCSS() {
|
||||
$features = $this->features ? ' ' . $this->features->toCSS() : '';
|
||||
|
||||
if ( $this->css ) {
|
||||
return "@import " . $this->path->toCSS() . $features . ";\n";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPath() {
|
||||
if ( $this->path instanceof Less_Tree_Quoted ) {
|
||||
$path = $this->path->value;
|
||||
$path = ( isset( $this->css ) || preg_match( '/(\.[a-z]*$)|([\?;].*)$/', $path ) ) ? $path : $path . '.less';
|
||||
} else if ( $this->path instanceof Less_Tree_URL ) {
|
||||
$path = $this->path->value->value;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
// remove query string and fragment
|
||||
return preg_replace( '/[\?#][^\?]*$/', '', $path );
|
||||
}
|
||||
|
||||
public function compileForImport( $env ) {
|
||||
return new Less_Tree_Import( $this->path->compile( $env ), $this->features, $this->options, $this->index, $this->currentFileInfo );
|
||||
}
|
||||
|
||||
public function compilePath( $env ) {
|
||||
$path = $this->path->compile( $env );
|
||||
$rootpath = '';
|
||||
if ( $this->currentFileInfo && $this->currentFileInfo['rootpath'] ) {
|
||||
$rootpath = $this->currentFileInfo['rootpath'];
|
||||
}
|
||||
|
||||
if ( !( $path instanceof Less_Tree_URL ) ) {
|
||||
if ( $rootpath ) {
|
||||
$pathValue = $path->value;
|
||||
// Add the base path if the import is relative
|
||||
if ( $pathValue && Less_Environment::isPathRelative( $pathValue ) ) {
|
||||
$path->value = $this->currentFileInfo['uri_root'].$pathValue;
|
||||
}
|
||||
}
|
||||
$path->value = Less_Environment::normalizePath( $path->value );
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function compile( $env ) {
|
||||
$evald = $this->compileForImport( $env );
|
||||
|
||||
// get path & uri
|
||||
$path_and_uri = null;
|
||||
if ( is_callable( Less_Parser::$options['import_callback'] ) ) {
|
||||
$path_and_uri = call_user_func( Less_Parser::$options['import_callback'], $evald );
|
||||
}
|
||||
|
||||
if ( !$path_and_uri ) {
|
||||
$path_and_uri = $evald->PathAndUri();
|
||||
}
|
||||
|
||||
if ( $path_and_uri ) {
|
||||
list( $full_path, $uri ) = $path_and_uri;
|
||||
} else {
|
||||
$full_path = $uri = $evald->getPath();
|
||||
}
|
||||
|
||||
// import once
|
||||
if ( $evald->skip( $full_path, $env ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
if ( $this->options['inline'] ) {
|
||||
// todo needs to reference css file not import
|
||||
//$contents = new Less_Tree_Anonymous($this->root, 0, array('filename'=>$this->importedFilename), true );
|
||||
|
||||
Less_Parser::AddParsedFile( $full_path );
|
||||
$contents = new Less_Tree_Anonymous( file_get_contents( $full_path ), 0, array(), true );
|
||||
|
||||
if ( $this->features ) {
|
||||
return new Less_Tree_Media( array( $contents ), $this->features->value );
|
||||
}
|
||||
|
||||
return array( $contents );
|
||||
}
|
||||
|
||||
// optional (need to be before "CSS" to support optional CSS imports. CSS should be checked only if empty($this->currentFileInfo))
|
||||
if ( isset( $this->options['optional'] ) && $this->options['optional'] && !file_exists( $full_path ) && ( !$evald->css || !empty( $this->currentFileInfo ) ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// css ?
|
||||
if ( $evald->css ) {
|
||||
$features = ( $evald->features ? $evald->features->compile( $env ) : null );
|
||||
return new Less_Tree_Import( $this->compilePath( $env ), $features, $this->options, $this->index );
|
||||
}
|
||||
|
||||
return $this->ParseImport( $full_path, $uri, $env );
|
||||
}
|
||||
|
||||
/**
|
||||
* Using the import directories, get the full absolute path and uri of the import
|
||||
*
|
||||
* @param Less_Tree_Import $evald
|
||||
*/
|
||||
public function PathAndUri() {
|
||||
$evald_path = $this->getPath();
|
||||
|
||||
if ( $evald_path ) {
|
||||
|
||||
$import_dirs = array();
|
||||
|
||||
if ( Less_Environment::isPathRelative( $evald_path ) ) {
|
||||
// if the path is relative, the file should be in the current directory
|
||||
if ( $this->currentFileInfo ) {
|
||||
$import_dirs[ $this->currentFileInfo['currentDirectory'] ] = $this->currentFileInfo['uri_root'];
|
||||
}
|
||||
|
||||
} else {
|
||||
// otherwise, the file should be relative to the server root
|
||||
if ( $this->currentFileInfo ) {
|
||||
$import_dirs[ $this->currentFileInfo['entryPath'] ] = $this->currentFileInfo['entryUri'];
|
||||
}
|
||||
// if the user supplied entryPath isn't the actual root
|
||||
$import_dirs[ $_SERVER['DOCUMENT_ROOT'] ] = '';
|
||||
|
||||
}
|
||||
|
||||
// always look in user supplied import directories
|
||||
$import_dirs = array_merge( $import_dirs, Less_Parser::$options['import_dirs'] );
|
||||
|
||||
foreach ( $import_dirs as $rootpath => $rooturi ) {
|
||||
if ( is_callable( $rooturi ) ) {
|
||||
list( $path, $uri ) = call_user_func( $rooturi, $evald_path );
|
||||
if ( is_string( $path ) ) {
|
||||
$full_path = $path;
|
||||
return array( $full_path, $uri );
|
||||
}
|
||||
} elseif ( !empty( $rootpath ) ) {
|
||||
|
||||
$path = rtrim( $rootpath, '/\\' ).'/'.ltrim( $evald_path, '/\\' );
|
||||
|
||||
if ( file_exists( $path ) ) {
|
||||
$full_path = Less_Environment::normalizePath( $path );
|
||||
$uri = Less_Environment::normalizePath( dirname( $rooturi.$evald_path ) );
|
||||
return array( $full_path, $uri );
|
||||
} elseif ( file_exists( $path.'.less' ) ) {
|
||||
$full_path = Less_Environment::normalizePath( $path.'.less' );
|
||||
$uri = Less_Environment::normalizePath( dirname( $rooturi.$evald_path.'.less' ) );
|
||||
return array( $full_path, $uri );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the import url and return the rules
|
||||
*
|
||||
* @return Less_Tree_Media|array
|
||||
*/
|
||||
public function ParseImport( $full_path, $uri, $env ) {
|
||||
$import_env = clone $env;
|
||||
if ( ( isset( $this->options['reference'] ) && $this->options['reference'] ) || isset( $this->currentFileInfo['reference'] ) ) {
|
||||
$import_env->currentFileInfo['reference'] = true;
|
||||
}
|
||||
|
||||
if ( ( isset( $this->options['multiple'] ) && $this->options['multiple'] ) ) {
|
||||
$import_env->importMultiple = true;
|
||||
}
|
||||
|
||||
$parser = new Less_Parser( $import_env );
|
||||
$root = $parser->parseFile( $full_path, $uri, true );
|
||||
|
||||
$ruleset = new Less_Tree_Ruleset( array(), $root->rules );
|
||||
$ruleset->evalImports( $import_env );
|
||||
|
||||
return $this->features ? new Less_Tree_Media( $ruleset->rules, $this->features->value ) : $ruleset->rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the import be skipped?
|
||||
*
|
||||
* @return boolean|null
|
||||
*/
|
||||
private function Skip( $path, $env ) {
|
||||
$path = Less_Parser::AbsPath( $path, true );
|
||||
|
||||
if ( $path && Less_Parser::FileParsed( $path ) ) {
|
||||
|
||||
if ( isset( $this->currentFileInfo['reference'] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !isset( $this->options['multiple'] ) && !$env->importMultiple;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
30
less.php/lib/Less/Tree/Javascript.php
Normal file
30
less.php/lib/Less/Tree/Javascript.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Javascript
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Javascript extends Less_Tree {
|
||||
|
||||
public $type = 'Javascript';
|
||||
public $escaped;
|
||||
public $expression;
|
||||
public $index;
|
||||
|
||||
/**
|
||||
* @param boolean $index
|
||||
* @param boolean $escaped
|
||||
*/
|
||||
public function __construct( $string, $index, $escaped ) {
|
||||
$this->escaped = $escaped;
|
||||
$this->expression = $string;
|
||||
$this->index = $index;
|
||||
}
|
||||
|
||||
public function compile() {
|
||||
return new Less_Tree_Anonymous( '/* Sorry, can not do JavaScript evaluation in PHP... :( */' );
|
||||
}
|
||||
|
||||
}
|
43
less.php/lib/Less/Tree/Keyword.php
Normal file
43
less.php/lib/Less/Tree/Keyword.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Keyword
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Keyword extends Less_Tree {
|
||||
|
||||
public $value;
|
||||
public $type = 'Keyword';
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
*/
|
||||
public function __construct( $value ) {
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function compile() {
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Less_Tree::genCSS
|
||||
*/
|
||||
public function genCSS( $output ) {
|
||||
if ( $this->value === '%' ) {
|
||||
throw new Less_Exception_Compiler( "Invalid % without number" );
|
||||
}
|
||||
|
||||
$output->add( $this->value );
|
||||
}
|
||||
|
||||
public function compare( $other ) {
|
||||
if ( $other instanceof Less_Tree_Keyword ) {
|
||||
return $other->value === $this->value ? 0 : 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
173
less.php/lib/Less/Tree/Media.php
Normal file
173
less.php/lib/Less/Tree/Media.php
Normal file
@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Media
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Media extends Less_Tree {
|
||||
|
||||
public $features;
|
||||
public $rules;
|
||||
public $index;
|
||||
public $currentFileInfo;
|
||||
public $isReferenced;
|
||||
public $type = 'Media';
|
||||
|
||||
public function __construct( $value = array(), $features = array(), $index = null, $currentFileInfo = null ) {
|
||||
$this->index = $index;
|
||||
$this->currentFileInfo = $currentFileInfo;
|
||||
|
||||
$selectors = $this->emptySelectors();
|
||||
|
||||
$this->features = new Less_Tree_Value( $features );
|
||||
|
||||
$this->rules = array( new Less_Tree_Ruleset( $selectors, $value ) );
|
||||
$this->rules[0]->allowImports = true;
|
||||
}
|
||||
|
||||
public function accept( $visitor ) {
|
||||
$this->features = $visitor->visitObj( $this->features );
|
||||
$this->rules = $visitor->visitArray( $this->rules );
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Less_Tree::genCSS
|
||||
*/
|
||||
public function genCSS( $output ) {
|
||||
$output->add( '@media ', $this->currentFileInfo, $this->index );
|
||||
$this->features->genCSS( $output );
|
||||
Less_Tree::outputRuleset( $output, $this->rules );
|
||||
|
||||
}
|
||||
|
||||
public function compile( $env ) {
|
||||
$media = new Less_Tree_Media( array(), array(), $this->index, $this->currentFileInfo );
|
||||
|
||||
$strictMathBypass = false;
|
||||
if ( Less_Parser::$options['strictMath'] === false ) {
|
||||
$strictMathBypass = true;
|
||||
Less_Parser::$options['strictMath'] = true;
|
||||
}
|
||||
|
||||
$media->features = $this->features->compile( $env );
|
||||
|
||||
if ( $strictMathBypass ) {
|
||||
Less_Parser::$options['strictMath'] = false;
|
||||
}
|
||||
|
||||
$env->mediaPath[] = $media;
|
||||
$env->mediaBlocks[] = $media;
|
||||
|
||||
array_unshift( $env->frames, $this->rules[0] );
|
||||
$media->rules = array( $this->rules[0]->compile( $env ) );
|
||||
array_shift( $env->frames );
|
||||
|
||||
array_pop( $env->mediaPath );
|
||||
|
||||
return !$env->mediaPath ? $media->compileTop( $env ) : $media->compileNested( $env );
|
||||
}
|
||||
|
||||
public function variable( $name ) {
|
||||
return $this->rules[0]->variable( $name );
|
||||
}
|
||||
|
||||
public function find( $selector ) {
|
||||
return $this->rules[0]->find( $selector, $this );
|
||||
}
|
||||
|
||||
public function emptySelectors() {
|
||||
$el = new Less_Tree_Element( '', '&', $this->index, $this->currentFileInfo );
|
||||
$sels = array( new Less_Tree_Selector( array( $el ), array(), null, $this->index, $this->currentFileInfo ) );
|
||||
$sels[0]->mediaEmpty = true;
|
||||
return $sels;
|
||||
}
|
||||
|
||||
public function markReferenced() {
|
||||
$this->rules[0]->markReferenced();
|
||||
$this->isReferenced = true;
|
||||
Less_Tree::ReferencedArray( $this->rules[0]->rules );
|
||||
}
|
||||
|
||||
// evaltop
|
||||
public function compileTop( $env ) {
|
||||
$result = $this;
|
||||
|
||||
if ( count( $env->mediaBlocks ) > 1 ) {
|
||||
$selectors = $this->emptySelectors();
|
||||
$result = new Less_Tree_Ruleset( $selectors, $env->mediaBlocks );
|
||||
$result->multiMedia = true;
|
||||
}
|
||||
|
||||
$env->mediaBlocks = array();
|
||||
$env->mediaPath = array();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function compileNested( $env ) {
|
||||
$path = array_merge( $env->mediaPath, array( $this ) );
|
||||
|
||||
// Extract the media-query conditions separated with `,` (OR).
|
||||
foreach ( $path as $key => $p ) {
|
||||
$value = $p->features instanceof Less_Tree_Value ? $p->features->value : $p->features;
|
||||
$path[$key] = is_array( $value ) ? $value : array( $value );
|
||||
}
|
||||
|
||||
// Trace all permutations to generate the resulting media-query.
|
||||
//
|
||||
// (a, b and c) with nested (d, e) ->
|
||||
// a and d
|
||||
// a and e
|
||||
// b and c and d
|
||||
// b and c and e
|
||||
|
||||
$permuted = $this->permute( $path );
|
||||
$expressions = array();
|
||||
foreach ( $permuted as $path ) {
|
||||
|
||||
for ( $i = 0, $len = count( $path ); $i < $len; $i++ ) {
|
||||
$path[$i] = Less_Parser::is_method( $path[$i], 'toCSS' ) ? $path[$i] : new Less_Tree_Anonymous( $path[$i] );
|
||||
}
|
||||
|
||||
for ( $i = count( $path ) - 1; $i > 0; $i-- ) {
|
||||
array_splice( $path, $i, 0, array( new Less_Tree_Anonymous( 'and' ) ) );
|
||||
}
|
||||
|
||||
$expressions[] = new Less_Tree_Expression( $path );
|
||||
}
|
||||
$this->features = new Less_Tree_Value( $expressions );
|
||||
|
||||
// Fake a tree-node that doesn't output anything.
|
||||
return new Less_Tree_Ruleset( array(), array() );
|
||||
}
|
||||
|
||||
public function permute( $arr ) {
|
||||
if ( !$arr )
|
||||
return array();
|
||||
|
||||
if ( count( $arr ) == 1 )
|
||||
return $arr[0];
|
||||
|
||||
$result = array();
|
||||
$rest = $this->permute( array_slice( $arr, 1 ) );
|
||||
foreach ( $rest as $r ) {
|
||||
foreach ( $arr[0] as $a ) {
|
||||
$result[] = array_merge(
|
||||
is_array( $a ) ? $a : array( $a ),
|
||||
is_array( $r ) ? $r : array( $r )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function bubbleSelectors( $selectors ) {
|
||||
if ( !$selectors ) return;
|
||||
|
||||
$this->rules = array( new Less_Tree_Ruleset( $selectors, array( $this->rules[0] ) ) );
|
||||
}
|
||||
|
||||
}
|
193
less.php/lib/Less/Tree/Mixin/Call.php
Normal file
193
less.php/lib/Less/Tree/Mixin/Call.php
Normal file
@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
class Less_Tree_Mixin_Call extends Less_Tree {
|
||||
|
||||
public $selector;
|
||||
public $arguments;
|
||||
public $index;
|
||||
public $currentFileInfo;
|
||||
|
||||
public $important;
|
||||
public $type = 'MixinCall';
|
||||
|
||||
/**
|
||||
* less.js: tree.mixin.Call
|
||||
*
|
||||
*/
|
||||
public function __construct( $elements, $args, $index, $currentFileInfo, $important = false ) {
|
||||
$this->selector = new Less_Tree_Selector( $elements );
|
||||
$this->arguments = $args;
|
||||
$this->index = $index;
|
||||
$this->currentFileInfo = $currentFileInfo;
|
||||
$this->important = $important;
|
||||
}
|
||||
|
||||
// function accept($visitor){
|
||||
// $this->selector = $visitor->visit($this->selector);
|
||||
// $this->arguments = $visitor->visit($this->arguments);
|
||||
//}
|
||||
|
||||
public function compile( $env ) {
|
||||
$rules = array();
|
||||
$match = false;
|
||||
$isOneFound = false;
|
||||
$candidates = array();
|
||||
$defaultUsed = false;
|
||||
$conditionResult = array();
|
||||
|
||||
$args = array();
|
||||
foreach ( $this->arguments as $a ) {
|
||||
$args[] = array( 'name' => $a['name'], 'value' => $a['value']->compile( $env ) );
|
||||
}
|
||||
|
||||
foreach ( $env->frames as $frame ) {
|
||||
|
||||
$mixins = $frame->find( $this->selector );
|
||||
|
||||
if ( !$mixins ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isOneFound = true;
|
||||
$defNone = 0;
|
||||
$defTrue = 1;
|
||||
$defFalse = 2;
|
||||
|
||||
// To make `default()` function independent of definition order we have two "subpasses" here.
|
||||
// At first we evaluate each guard *twice* (with `default() == true` and `default() == false`),
|
||||
// and build candidate list with corresponding flags. Then, when we know all possible matches,
|
||||
// we make a final decision.
|
||||
|
||||
$mixins_len = count( $mixins );
|
||||
for ( $m = 0; $m < $mixins_len; $m++ ) {
|
||||
$mixin = $mixins[$m];
|
||||
|
||||
if ( $this->IsRecursive( $env, $mixin ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $mixin->matchArgs( $args, $env ) ) {
|
||||
|
||||
$candidate = array( 'mixin' => $mixin, 'group' => $defNone );
|
||||
|
||||
if ( $mixin instanceof Less_Tree_Ruleset ) {
|
||||
|
||||
for ( $f = 0; $f < 2; $f++ ) {
|
||||
Less_Tree_DefaultFunc::value( $f );
|
||||
$conditionResult[$f] = $mixin->matchCondition( $args, $env );
|
||||
}
|
||||
if ( $conditionResult[0] || $conditionResult[1] ) {
|
||||
if ( $conditionResult[0] != $conditionResult[1] ) {
|
||||
$candidate['group'] = $conditionResult[1] ? $defTrue : $defFalse;
|
||||
}
|
||||
|
||||
$candidates[] = $candidate;
|
||||
}
|
||||
} else {
|
||||
$candidates[] = $candidate;
|
||||
}
|
||||
|
||||
$match = true;
|
||||
}
|
||||
}
|
||||
|
||||
Less_Tree_DefaultFunc::reset();
|
||||
|
||||
$count = array( 0, 0, 0 );
|
||||
for ( $m = 0; $m < count( $candidates ); $m++ ) {
|
||||
$count[ $candidates[$m]['group'] ]++;
|
||||
}
|
||||
|
||||
if ( $count[$defNone] > 0 ) {
|
||||
$defaultResult = $defFalse;
|
||||
} else {
|
||||
$defaultResult = $defTrue;
|
||||
if ( ( $count[$defTrue] + $count[$defFalse] ) > 1 ) {
|
||||
throw new Exception( 'Ambiguous use of `default()` found when matching for `' . $this->format( $args ) . '`' );
|
||||
}
|
||||
}
|
||||
|
||||
$candidates_length = count( $candidates );
|
||||
$length_1 = ( $candidates_length == 1 );
|
||||
|
||||
for ( $m = 0; $m < $candidates_length; $m++ ) {
|
||||
$candidate = $candidates[$m]['group'];
|
||||
if ( ( $candidate === $defNone ) || ( $candidate === $defaultResult ) ) {
|
||||
try{
|
||||
$mixin = $candidates[$m]['mixin'];
|
||||
if ( !( $mixin instanceof Less_Tree_Mixin_Definition ) ) {
|
||||
$mixin = new Less_Tree_Mixin_Definition( '', array(), $mixin->rules, null, false );
|
||||
$mixin->originalRuleset = $mixins[$m]->originalRuleset;
|
||||
}
|
||||
$rules = array_merge( $rules, $mixin->evalCall( $env, $args, $this->important )->rules );
|
||||
} catch ( Exception $e ) {
|
||||
// throw new Less_Exception_Compiler($e->getMessage(), $e->index, null, $this->currentFileInfo['filename']);
|
||||
throw new Less_Exception_Compiler( $e->getMessage(), null, null, $this->currentFileInfo );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $match ) {
|
||||
if ( !$this->currentFileInfo || !isset( $this->currentFileInfo['reference'] ) || !$this->currentFileInfo['reference'] ) {
|
||||
Less_Tree::ReferencedArray( $rules );
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $isOneFound ) {
|
||||
throw new Less_Exception_Compiler( 'No matching definition was found for `'.$this->Format( $args ).'`', null, $this->index, $this->currentFileInfo );
|
||||
|
||||
} else {
|
||||
throw new Less_Exception_Compiler( trim( $this->selector->toCSS() ) . " is undefined in ".$this->currentFileInfo['filename'], null, $this->index );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the args for use in exception messages
|
||||
*
|
||||
*/
|
||||
private function Format( $args ) {
|
||||
$message = array();
|
||||
if ( $args ) {
|
||||
foreach ( $args as $a ) {
|
||||
$argValue = '';
|
||||
if ( $a['name'] ) {
|
||||
$argValue .= $a['name'] . ':';
|
||||
}
|
||||
if ( is_object( $a['value'] ) ) {
|
||||
$argValue .= $a['value']->toCSS();
|
||||
} else {
|
||||
$argValue .= '???';
|
||||
}
|
||||
$message[] = $argValue;
|
||||
}
|
||||
}
|
||||
return implode( ', ', $message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Are we in a recursive mixin call?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function IsRecursive( $env, $mixin ) {
|
||||
foreach ( $env->frames as $recur_frame ) {
|
||||
if ( !( $mixin instanceof Less_Tree_Mixin_Definition ) ) {
|
||||
|
||||
if ( $mixin === $recur_frame ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( isset( $recur_frame->originalRuleset ) && $mixin->ruleset_id === $recur_frame->originalRuleset ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
233
less.php/lib/Less/Tree/Mixin/Definition.php
Normal file
233
less.php/lib/Less/Tree/Mixin/Definition.php
Normal file
@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
class Less_Tree_Mixin_Definition extends Less_Tree_Ruleset {
|
||||
public $name;
|
||||
public $selectors;
|
||||
public $params;
|
||||
public $arity = 0;
|
||||
public $rules;
|
||||
public $lookups = array();
|
||||
public $required = 0;
|
||||
public $frames = array();
|
||||
public $condition;
|
||||
public $variadic;
|
||||
public $type = 'MixinDefinition';
|
||||
|
||||
// less.js : /lib/less/tree/mixin.js : tree.mixin.Definition
|
||||
public function __construct( $name, $params, $rules, $condition, $variadic = false, $frames = array() ) {
|
||||
$this->name = $name;
|
||||
$this->selectors = array( new Less_Tree_Selector( array( new Less_Tree_Element( null, $name ) ) ) );
|
||||
|
||||
$this->params = $params;
|
||||
$this->condition = $condition;
|
||||
$this->variadic = $variadic;
|
||||
$this->rules = $rules;
|
||||
|
||||
if ( $params ) {
|
||||
$this->arity = count( $params );
|
||||
foreach ( $params as $p ) {
|
||||
if ( !isset( $p['name'] ) || ( $p['name'] && !isset( $p['value'] ) ) ) {
|
||||
$this->required++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->frames = $frames;
|
||||
$this->SetRulesetIndex();
|
||||
}
|
||||
|
||||
// function accept( $visitor ){
|
||||
// $this->params = $visitor->visit($this->params);
|
||||
// $this->rules = $visitor->visit($this->rules);
|
||||
// $this->condition = $visitor->visit($this->condition);
|
||||
//}
|
||||
|
||||
public function toCSS() {
|
||||
return '';
|
||||
}
|
||||
|
||||
// less.js : /lib/less/tree/mixin.js : tree.mixin.Definition.evalParams
|
||||
public function compileParams( $env, $mixinFrames, $args = array(), &$evaldArguments = array() ) {
|
||||
$frame = new Less_Tree_Ruleset( null, array() );
|
||||
$params = $this->params;
|
||||
$mixinEnv = null;
|
||||
$argsLength = 0;
|
||||
|
||||
if ( $args ) {
|
||||
$argsLength = count( $args );
|
||||
for ( $i = 0; $i < $argsLength; $i++ ) {
|
||||
$arg = $args[$i];
|
||||
|
||||
if ( $arg && $arg['name'] ) {
|
||||
$isNamedFound = false;
|
||||
|
||||
foreach ( $params as $j => $param ) {
|
||||
if ( !isset( $evaldArguments[$j] ) && $arg['name'] === $params[$j]['name'] ) {
|
||||
$evaldArguments[$j] = $arg['value']->compile( $env );
|
||||
array_unshift( $frame->rules, new Less_Tree_Rule( $arg['name'], $arg['value']->compile( $env ) ) );
|
||||
$isNamedFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( $isNamedFound ) {
|
||||
array_splice( $args, $i, 1 );
|
||||
$i--;
|
||||
$argsLength--;
|
||||
continue;
|
||||
} else {
|
||||
throw new Less_Exception_Compiler( "Named argument for " . $this->name .' '.$args[$i]['name'] . ' not found' );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$argIndex = 0;
|
||||
foreach ( $params as $i => $param ) {
|
||||
|
||||
if ( isset( $evaldArguments[$i] ) ) { continue;
|
||||
}
|
||||
|
||||
$arg = null;
|
||||
if ( isset( $args[$argIndex] ) ) {
|
||||
$arg = $args[$argIndex];
|
||||
}
|
||||
|
||||
if ( isset( $param['name'] ) && $param['name'] ) {
|
||||
|
||||
if ( isset( $param['variadic'] ) ) {
|
||||
$varargs = array();
|
||||
for ( $j = $argIndex; $j < $argsLength; $j++ ) {
|
||||
$varargs[] = $args[$j]['value']->compile( $env );
|
||||
}
|
||||
$expression = new Less_Tree_Expression( $varargs );
|
||||
array_unshift( $frame->rules, new Less_Tree_Rule( $param['name'], $expression->compile( $env ) ) );
|
||||
} else {
|
||||
$val = ( $arg && $arg['value'] ) ? $arg['value'] : false;
|
||||
|
||||
if ( $val ) {
|
||||
$val = $val->compile( $env );
|
||||
} else if ( isset( $param['value'] ) ) {
|
||||
|
||||
if ( !$mixinEnv ) {
|
||||
$mixinEnv = new Less_Environment();
|
||||
$mixinEnv->frames = array_merge( array( $frame ), $mixinFrames );
|
||||
}
|
||||
|
||||
$val = $param['value']->compile( $mixinEnv );
|
||||
$frame->resetCache();
|
||||
} else {
|
||||
throw new Less_Exception_Compiler( "Wrong number of arguments for " . $this->name . " (" . $argsLength . ' for ' . $this->arity . ")" );
|
||||
}
|
||||
|
||||
array_unshift( $frame->rules, new Less_Tree_Rule( $param['name'], $val ) );
|
||||
$evaldArguments[$i] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $param['variadic'] ) && $args ) {
|
||||
for ( $j = $argIndex; $j < $argsLength; $j++ ) {
|
||||
$evaldArguments[$j] = $args[$j]['value']->compile( $env );
|
||||
}
|
||||
}
|
||||
$argIndex++;
|
||||
}
|
||||
|
||||
ksort( $evaldArguments );
|
||||
$evaldArguments = array_values( $evaldArguments );
|
||||
|
||||
return $frame;
|
||||
}
|
||||
|
||||
public function compile( $env ) {
|
||||
if ( $this->frames ) {
|
||||
return new Less_Tree_Mixin_Definition( $this->name, $this->params, $this->rules, $this->condition, $this->variadic, $this->frames );
|
||||
}
|
||||
return new Less_Tree_Mixin_Definition( $this->name, $this->params, $this->rules, $this->condition, $this->variadic, $env->frames );
|
||||
}
|
||||
|
||||
public function evalCall( $env, $args = NULL, $important = NULL ) {
|
||||
Less_Environment::$mixin_stack++;
|
||||
|
||||
$_arguments = array();
|
||||
|
||||
if ( $this->frames ) {
|
||||
$mixinFrames = array_merge( $this->frames, $env->frames );
|
||||
} else {
|
||||
$mixinFrames = $env->frames;
|
||||
}
|
||||
|
||||
$frame = $this->compileParams( $env, $mixinFrames, $args, $_arguments );
|
||||
|
||||
$ex = new Less_Tree_Expression( $_arguments );
|
||||
array_unshift( $frame->rules, new Less_Tree_Rule( '@arguments', $ex->compile( $env ) ) );
|
||||
|
||||
$ruleset = new Less_Tree_Ruleset( null, $this->rules );
|
||||
$ruleset->originalRuleset = $this->ruleset_id;
|
||||
|
||||
$ruleSetEnv = new Less_Environment();
|
||||
$ruleSetEnv->frames = array_merge( array( $this, $frame ), $mixinFrames );
|
||||
$ruleset = $ruleset->compile( $ruleSetEnv );
|
||||
|
||||
if ( $important ) {
|
||||
$ruleset = $ruleset->makeImportant();
|
||||
}
|
||||
|
||||
Less_Environment::$mixin_stack--;
|
||||
|
||||
return $ruleset;
|
||||
}
|
||||
|
||||
public function matchCondition( $args, $env ) {
|
||||
if ( !$this->condition ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// set array to prevent error on array_merge
|
||||
if ( !is_array( $this->frames ) ) {
|
||||
$this->frames = array();
|
||||
}
|
||||
|
||||
$frame = $this->compileParams( $env, array_merge( $this->frames, $env->frames ), $args );
|
||||
|
||||
$compile_env = new Less_Environment();
|
||||
$compile_env->frames = array_merge(
|
||||
array( $frame ), // the parameter variables
|
||||
$this->frames, // the parent namespace/mixin frames
|
||||
$env->frames // the current environment frames
|
||||
);
|
||||
|
||||
$compile_env->functions = $env->functions;
|
||||
|
||||
return (bool)$this->condition->compile( $compile_env );
|
||||
}
|
||||
|
||||
public function matchArgs( $args, $env = NULL ) {
|
||||
$argsLength = count( $args );
|
||||
|
||||
if ( !$this->variadic ) {
|
||||
if ( $argsLength < $this->required ) {
|
||||
return false;
|
||||
}
|
||||
if ( $argsLength > count( $this->params ) ) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if ( $argsLength < ( $this->required - 1 ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$len = min( $argsLength, $this->arity );
|
||||
|
||||
for ( $i = 0; $i < $len; $i++ ) {
|
||||
if ( !isset( $this->params[$i]['name'] ) && !isset( $this->params[$i]['variadic'] ) ) {
|
||||
if ( $args[$i]['value']->compile( $env )->toCSS() != $this->params[$i]['value']->compile( $env )->toCSS() ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
49
less.php/lib/Less/Tree/NameValue.php
Normal file
49
less.php/lib/Less/Tree/NameValue.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* A simple css name-value pair
|
||||
* ex: width:100px;
|
||||
*
|
||||
* In bootstrap, there are about 600-1,000 simple name-value pairs (depending on how forgiving the match is) -vs- 6,020 dynamic rules (Less_Tree_Rule)
|
||||
* Using the name-value object can speed up bootstrap compilation slightly, but it breaks color keyword interpretation: color:red -> color:#FF0000;
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_NameValue extends Less_Tree {
|
||||
|
||||
public $name;
|
||||
public $value;
|
||||
public $index;
|
||||
public $currentFileInfo;
|
||||
public $type = 'NameValue';
|
||||
public $important = '';
|
||||
|
||||
public function __construct( $name, $value = null, $index = null, $currentFileInfo = null ) {
|
||||
$this->name = $name;
|
||||
$this->value = $value;
|
||||
$this->index = $index;
|
||||
$this->currentFileInfo = $currentFileInfo;
|
||||
}
|
||||
|
||||
public function genCSS( $output ) {
|
||||
$output->add(
|
||||
$this->name
|
||||
. Less_Environment::$_outputMap[': ']
|
||||
. $this->value
|
||||
. $this->important
|
||||
. ( ( ( Less_Environment::$lastRule && Less_Parser::$options['compress'] ) ) ? "" : ";" ),
|
||||
$this->currentFileInfo, $this->index );
|
||||
}
|
||||
|
||||
public function compile( $env ) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function makeImportant() {
|
||||
$new = new Less_Tree_NameValue( $this->name, $this->value, $this->index, $this->currentFileInfo );
|
||||
$new->important = ' !important';
|
||||
return $new;
|
||||
}
|
||||
|
||||
}
|
37
less.php/lib/Less/Tree/Negative.php
Normal file
37
less.php/lib/Less/Tree/Negative.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Negative
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Negative extends Less_Tree {
|
||||
|
||||
public $value;
|
||||
public $type = 'Negative';
|
||||
|
||||
public function __construct( $node ) {
|
||||
$this->value = $node;
|
||||
}
|
||||
|
||||
// function accept($visitor) {
|
||||
// $this->value = $visitor->visit($this->value);
|
||||
//}
|
||||
|
||||
/**
|
||||
* @see Less_Tree::genCSS
|
||||
*/
|
||||
public function genCSS( $output ) {
|
||||
$output->add( '-' );
|
||||
$this->value->genCSS( $output );
|
||||
}
|
||||
|
||||
public function compile( $env ) {
|
||||
if ( Less_Environment::isMathOn() ) {
|
||||
$ret = new Less_Tree_Operation( '*', array( new Less_Tree_Dimension( -1 ), $this->value ) );
|
||||
return $ret->compile( $env );
|
||||
}
|
||||
return new Less_Tree_Negative( $this->value->compile( $env ) );
|
||||
}
|
||||
}
|
68
less.php/lib/Less/Tree/Operation.php
Normal file
68
less.php/lib/Less/Tree/Operation.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Operation
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Operation extends Less_Tree {
|
||||
|
||||
public $op;
|
||||
public $operands;
|
||||
public $isSpaced;
|
||||
public $type = 'Operation';
|
||||
|
||||
/**
|
||||
* @param string $op
|
||||
*/
|
||||
public function __construct( $op, $operands, $isSpaced = false ) {
|
||||
$this->op = trim( $op );
|
||||
$this->operands = $operands;
|
||||
$this->isSpaced = $isSpaced;
|
||||
}
|
||||
|
||||
public function accept( $visitor ) {
|
||||
$this->operands = $visitor->visitArray( $this->operands );
|
||||
}
|
||||
|
||||
public function compile( $env ) {
|
||||
$a = $this->operands[0]->compile( $env );
|
||||
$b = $this->operands[1]->compile( $env );
|
||||
|
||||
if ( Less_Environment::isMathOn() ) {
|
||||
|
||||
if ( $a instanceof Less_Tree_Dimension && $b instanceof Less_Tree_Color ) {
|
||||
$a = $a->toColor();
|
||||
|
||||
} elseif ( $b instanceof Less_Tree_Dimension && $a instanceof Less_Tree_Color ) {
|
||||
$b = $b->toColor();
|
||||
|
||||
}
|
||||
|
||||
if ( !method_exists( $a, 'operate' ) ) {
|
||||
throw new Less_Exception_Compiler( "Operation on an invalid type" );
|
||||
}
|
||||
|
||||
return $a->operate( $this->op, $b );
|
||||
}
|
||||
|
||||
return new Less_Tree_Operation( $this->op, array( $a, $b ), $this->isSpaced );
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Less_Tree::genCSS
|
||||
*/
|
||||
public function genCSS( $output ) {
|
||||
$this->operands[0]->genCSS( $output );
|
||||
if ( $this->isSpaced ) {
|
||||
$output->add( " " );
|
||||
}
|
||||
$output->add( $this->op );
|
||||
if ( $this->isSpaced ) {
|
||||
$output->add( ' ' );
|
||||
}
|
||||
$this->operands[1]->genCSS( $output );
|
||||
}
|
||||
|
||||
}
|
35
less.php/lib/Less/Tree/Paren.php
Normal file
35
less.php/lib/Less/Tree/Paren.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Paren
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Paren extends Less_Tree {
|
||||
|
||||
public $value;
|
||||
public $type = 'Paren';
|
||||
|
||||
public function __construct( $value ) {
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function accept( $visitor ) {
|
||||
$this->value = $visitor->visitObj( $this->value );
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Less_Tree::genCSS
|
||||
*/
|
||||
public function genCSS( $output ) {
|
||||
$output->add( '(' );
|
||||
$this->value->genCSS( $output );
|
||||
$output->add( ')' );
|
||||
}
|
||||
|
||||
public function compile( $env ) {
|
||||
return new Less_Tree_Paren( $this->value->compile( $env ) );
|
||||
}
|
||||
|
||||
}
|
79
less.php/lib/Less/Tree/Quoted.php
Normal file
79
less.php/lib/Less/Tree/Quoted.php
Normal file
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Quoted
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Quoted extends Less_Tree {
|
||||
public $escaped;
|
||||
public $value;
|
||||
public $quote;
|
||||
public $index;
|
||||
public $currentFileInfo;
|
||||
public $type = 'Quoted';
|
||||
|
||||
/**
|
||||
* @param string $str
|
||||
*/
|
||||
public function __construct( $str, $content = '', $escaped = false, $index = false, $currentFileInfo = null ) {
|
||||
$this->escaped = $escaped;
|
||||
$this->value = $content;
|
||||
if ( $str ) {
|
||||
$this->quote = $str[0];
|
||||
}
|
||||
$this->index = $index;
|
||||
$this->currentFileInfo = $currentFileInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Less_Tree::genCSS
|
||||
*/
|
||||
public function genCSS( $output ) {
|
||||
if ( !$this->escaped ) {
|
||||
$output->add( $this->quote, $this->currentFileInfo, $this->index );
|
||||
}
|
||||
$output->add( $this->value );
|
||||
if ( !$this->escaped ) {
|
||||
$output->add( $this->quote );
|
||||
}
|
||||
}
|
||||
|
||||
public function compile( $env ) {
|
||||
$value = $this->value;
|
||||
if ( preg_match_all( '/`([^`]+)`/', $this->value, $matches ) ) {
|
||||
foreach ( $matches as $i => $match ) {
|
||||
$js = new Less_Tree_JavaScript( $matches[1], $this->index, true );
|
||||
$js = $js->compile()->value;
|
||||
$value = str_replace( $matches[0][$i], $js, $value );
|
||||
}
|
||||
}
|
||||
|
||||
if ( preg_match_all( '/@\{([\w-]+)\}/', $value, $matches ) ) {
|
||||
foreach ( $matches[1] as $i => $match ) {
|
||||
$v = new Less_Tree_Variable( '@' . $match, $this->index, $this->currentFileInfo );
|
||||
$v = $v->compile( $env );
|
||||
$v = ( $v instanceof Less_Tree_Quoted ) ? $v->value : $v->toCSS();
|
||||
$value = str_replace( $matches[0][$i], $v, $value );
|
||||
}
|
||||
}
|
||||
|
||||
return new Less_Tree_Quoted( $this->quote . $value . $this->quote, $value, $this->escaped, $this->index, $this->currentFileInfo );
|
||||
}
|
||||
|
||||
public function compare( $x ) {
|
||||
if ( !Less_Parser::is_method( $x, 'toCSS' ) ) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
$left = $this->toCSS();
|
||||
$right = $x->toCSS();
|
||||
|
||||
if ( $left === $right ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $left < $right ? -1 : 1;
|
||||
}
|
||||
}
|
112
less.php/lib/Less/Tree/Rule.php
Normal file
112
less.php/lib/Less/Tree/Rule.php
Normal file
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Rule
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Rule extends Less_Tree {
|
||||
|
||||
public $name;
|
||||
public $value;
|
||||
public $important;
|
||||
public $merge;
|
||||
public $index;
|
||||
public $inline;
|
||||
public $variable;
|
||||
public $currentFileInfo;
|
||||
public $type = 'Rule';
|
||||
|
||||
/**
|
||||
* @param string $important
|
||||
*/
|
||||
public function __construct( $name, $value = null, $important = null, $merge = null, $index = null, $currentFileInfo = null, $inline = false ) {
|
||||
$this->name = $name;
|
||||
$this->value = ( $value instanceof Less_Tree_Value || $value instanceof Less_Tree_Ruleset ) ? $value : new Less_Tree_Value( array( $value ) );
|
||||
$this->important = $important ? ' ' . trim( $important ) : '';
|
||||
$this->merge = $merge;
|
||||
$this->index = $index;
|
||||
$this->currentFileInfo = $currentFileInfo;
|
||||
$this->inline = $inline;
|
||||
$this->variable = ( is_string( $name ) && $name[0] === '@' );
|
||||
}
|
||||
|
||||
public function accept( $visitor ) {
|
||||
$this->value = $visitor->visitObj( $this->value );
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Less_Tree::genCSS
|
||||
*/
|
||||
public function genCSS( $output ) {
|
||||
$output->add( $this->name . Less_Environment::$_outputMap[': '], $this->currentFileInfo, $this->index );
|
||||
try{
|
||||
$this->value->genCSS( $output );
|
||||
|
||||
}catch ( Less_Exception_Parser $e ) {
|
||||
$e->index = $this->index;
|
||||
$e->currentFile = $this->currentFileInfo;
|
||||
throw $e;
|
||||
}
|
||||
$output->add( $this->important . ( ( $this->inline || ( Less_Environment::$lastRule && Less_Parser::$options['compress'] ) ) ? "" : ";" ), $this->currentFileInfo, $this->index );
|
||||
}
|
||||
|
||||
public function compile( $env ) {
|
||||
$name = $this->name;
|
||||
if ( is_array( $name ) ) {
|
||||
// expand 'primitive' name directly to get
|
||||
// things faster (~10% for benchmark.less):
|
||||
if ( count( $name ) === 1 && $name[0] instanceof Less_Tree_Keyword ) {
|
||||
$name = $name[0]->value;
|
||||
} else {
|
||||
$name = $this->CompileName( $env, $name );
|
||||
}
|
||||
}
|
||||
|
||||
$strictMathBypass = Less_Parser::$options['strictMath'];
|
||||
if ( $name === "font" && !Less_Parser::$options['strictMath'] ) {
|
||||
Less_Parser::$options['strictMath'] = true;
|
||||
}
|
||||
|
||||
try {
|
||||
$evaldValue = $this->value->compile( $env );
|
||||
|
||||
if ( !$this->variable && $evaldValue->type === "DetachedRuleset" ) {
|
||||
throw new Less_Exception_Compiler( "Rulesets cannot be evaluated on a property.", null, $this->index, $this->currentFileInfo );
|
||||
}
|
||||
|
||||
if ( Less_Environment::$mixin_stack ) {
|
||||
$return = new Less_Tree_Rule( $name, $evaldValue, $this->important, $this->merge, $this->index, $this->currentFileInfo, $this->inline );
|
||||
} else {
|
||||
$this->name = $name;
|
||||
$this->value = $evaldValue;
|
||||
$return = $this;
|
||||
}
|
||||
|
||||
}catch ( Less_Exception_Parser $e ) {
|
||||
if ( !is_numeric( $e->index ) ) {
|
||||
$e->index = $this->index;
|
||||
$e->currentFile = $this->currentFileInfo;
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
Less_Parser::$options['strictMath'] = $strictMathBypass;
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
public function CompileName( $env, $name ) {
|
||||
$output = new Less_Output();
|
||||
foreach ( $name as $n ) {
|
||||
$n->compile( $env )->genCSS( $output );
|
||||
}
|
||||
return $output->toString();
|
||||
}
|
||||
|
||||
public function makeImportant() {
|
||||
return new Less_Tree_Rule( $this->name, $this->value, '!important', $this->merge, $this->index, $this->currentFileInfo, $this->inline );
|
||||
}
|
||||
|
||||
}
|
621
less.php/lib/Less/Tree/Ruleset.php
Normal file
621
less.php/lib/Less/Tree/Ruleset.php
Normal file
@ -0,0 +1,621 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Ruleset
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Ruleset extends Less_Tree {
|
||||
|
||||
protected $lookups;
|
||||
public $_variables;
|
||||
public $_rulesets;
|
||||
|
||||
public $strictImports;
|
||||
|
||||
public $selectors;
|
||||
public $rules;
|
||||
public $root;
|
||||
public $allowImports;
|
||||
public $paths;
|
||||
public $firstRoot;
|
||||
public $type = 'Ruleset';
|
||||
public $multiMedia;
|
||||
public $allExtends;
|
||||
|
||||
public $ruleset_id;
|
||||
public $originalRuleset;
|
||||
|
||||
public $first_oelements;
|
||||
|
||||
public function SetRulesetIndex() {
|
||||
$this->ruleset_id = Less_Parser::$next_id++;
|
||||
$this->originalRuleset = $this->ruleset_id;
|
||||
|
||||
if ( $this->selectors ) {
|
||||
foreach ( $this->selectors as $sel ) {
|
||||
if ( $sel->_oelements ) {
|
||||
$this->first_oelements[$sel->_oelements[0]] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function __construct( $selectors, $rules, $strictImports = null ) {
|
||||
$this->selectors = $selectors;
|
||||
$this->rules = $rules;
|
||||
$this->lookups = array();
|
||||
$this->strictImports = $strictImports;
|
||||
$this->SetRulesetIndex();
|
||||
}
|
||||
|
||||
public function accept( $visitor ) {
|
||||
if ( $this->paths ) {
|
||||
$paths_len = count( $this->paths );
|
||||
for ( $i = 0,$paths_len; $i < $paths_len; $i++ ) {
|
||||
$this->paths[$i] = $visitor->visitArray( $this->paths[$i] );
|
||||
}
|
||||
} elseif ( $this->selectors ) {
|
||||
$this->selectors = $visitor->visitArray( $this->selectors );
|
||||
}
|
||||
|
||||
if ( $this->rules ) {
|
||||
$this->rules = $visitor->visitArray( $this->rules );
|
||||
}
|
||||
}
|
||||
|
||||
public function compile( $env ) {
|
||||
$ruleset = $this->PrepareRuleset( $env );
|
||||
|
||||
// Store the frames around mixin definitions,
|
||||
// so they can be evaluated like closures when the time comes.
|
||||
$rsRuleCnt = count( $ruleset->rules );
|
||||
for ( $i = 0; $i < $rsRuleCnt; $i++ ) {
|
||||
if ( $ruleset->rules[$i] instanceof Less_Tree_Mixin_Definition || $ruleset->rules[$i] instanceof Less_Tree_DetachedRuleset ) {
|
||||
$ruleset->rules[$i] = $ruleset->rules[$i]->compile( $env );
|
||||
}
|
||||
}
|
||||
|
||||
$mediaBlockCount = 0;
|
||||
if ( $env instanceof Less_Environment ) {
|
||||
$mediaBlockCount = count( $env->mediaBlocks );
|
||||
}
|
||||
|
||||
// Evaluate mixin calls.
|
||||
$this->EvalMixinCalls( $ruleset, $env, $rsRuleCnt );
|
||||
|
||||
// Evaluate everything else
|
||||
for ( $i = 0; $i < $rsRuleCnt; $i++ ) {
|
||||
if ( !( $ruleset->rules[$i] instanceof Less_Tree_Mixin_Definition || $ruleset->rules[$i] instanceof Less_Tree_DetachedRuleset ) ) {
|
||||
$ruleset->rules[$i] = $ruleset->rules[$i]->compile( $env );
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate everything else
|
||||
for ( $i = 0; $i < $rsRuleCnt; $i++ ) {
|
||||
$rule = $ruleset->rules[$i];
|
||||
|
||||
// for rulesets, check if it is a css guard and can be removed
|
||||
if ( $rule instanceof Less_Tree_Ruleset && $rule->selectors && count( $rule->selectors ) === 1 ) {
|
||||
|
||||
// check if it can be folded in (e.g. & where)
|
||||
if ( $rule->selectors[0]->isJustParentSelector() ) {
|
||||
array_splice( $ruleset->rules, $i--, 1 );
|
||||
$rsRuleCnt--;
|
||||
|
||||
for ( $j = 0; $j < count( $rule->rules ); $j++ ) {
|
||||
$subRule = $rule->rules[$j];
|
||||
if ( !( $subRule instanceof Less_Tree_Rule ) || !$subRule->variable ) {
|
||||
array_splice( $ruleset->rules, ++$i, 0, array( $subRule ) );
|
||||
$rsRuleCnt++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pop the stack
|
||||
$env->shiftFrame();
|
||||
|
||||
if ( $mediaBlockCount ) {
|
||||
$len = count( $env->mediaBlocks );
|
||||
for ( $i = $mediaBlockCount; $i < $len; $i++ ) {
|
||||
$env->mediaBlocks[$i]->bubbleSelectors( $ruleset->selectors );
|
||||
}
|
||||
}
|
||||
|
||||
return $ruleset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile Less_Tree_Mixin_Call objects
|
||||
*
|
||||
* @param Less_Tree_Ruleset $ruleset
|
||||
* @param integer $rsRuleCnt
|
||||
*/
|
||||
private function EvalMixinCalls( $ruleset, $env, &$rsRuleCnt ) {
|
||||
for ( $i = 0; $i < $rsRuleCnt; $i++ ) {
|
||||
$rule = $ruleset->rules[$i];
|
||||
|
||||
if ( $rule instanceof Less_Tree_Mixin_Call ) {
|
||||
$rule = $rule->compile( $env );
|
||||
|
||||
$temp = array();
|
||||
foreach ( $rule as $r ) {
|
||||
if ( ( $r instanceof Less_Tree_Rule ) && $r->variable ) {
|
||||
// do not pollute the scope if the variable is
|
||||
// already there. consider returning false here
|
||||
// but we need a way to "return" variable from mixins
|
||||
if ( !$ruleset->variable( $r->name ) ) {
|
||||
$temp[] = $r;
|
||||
}
|
||||
} else {
|
||||
$temp[] = $r;
|
||||
}
|
||||
}
|
||||
$temp_count = count( $temp ) - 1;
|
||||
array_splice( $ruleset->rules, $i, 1, $temp );
|
||||
$rsRuleCnt += $temp_count;
|
||||
$i += $temp_count;
|
||||
$ruleset->resetCache();
|
||||
|
||||
} elseif ( $rule instanceof Less_Tree_RulesetCall ) {
|
||||
|
||||
$rule = $rule->compile( $env );
|
||||
$rules = array();
|
||||
foreach ( $rule->rules as $r ) {
|
||||
if ( ( $r instanceof Less_Tree_Rule ) && $r->variable ) {
|
||||
continue;
|
||||
}
|
||||
$rules[] = $r;
|
||||
}
|
||||
|
||||
array_splice( $ruleset->rules, $i, 1, $rules );
|
||||
$temp_count = count( $rules );
|
||||
$rsRuleCnt += $temp_count - 1;
|
||||
$i += $temp_count - 1;
|
||||
$ruleset->resetCache();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile the selectors and create a new ruleset object for the compile() method
|
||||
*
|
||||
*/
|
||||
private function PrepareRuleset( $env ) {
|
||||
$hasOnePassingSelector = false;
|
||||
$selectors = array();
|
||||
if ( $this->selectors ) {
|
||||
Less_Tree_DefaultFunc::error( "it is currently only allowed in parametric mixin guards," );
|
||||
|
||||
foreach ( $this->selectors as $s ) {
|
||||
$selector = $s->compile( $env );
|
||||
$selectors[] = $selector;
|
||||
if ( $selector->evaldCondition ) {
|
||||
$hasOnePassingSelector = true;
|
||||
}
|
||||
}
|
||||
|
||||
Less_Tree_DefaultFunc::reset();
|
||||
} else {
|
||||
$hasOnePassingSelector = true;
|
||||
}
|
||||
|
||||
if ( $this->rules && $hasOnePassingSelector ) {
|
||||
$rules = $this->rules;
|
||||
} else {
|
||||
$rules = array();
|
||||
}
|
||||
|
||||
$ruleset = new Less_Tree_Ruleset( $selectors, $rules, $this->strictImports );
|
||||
|
||||
$ruleset->originalRuleset = $this->ruleset_id;
|
||||
|
||||
$ruleset->root = $this->root;
|
||||
$ruleset->firstRoot = $this->firstRoot;
|
||||
$ruleset->allowImports = $this->allowImports;
|
||||
|
||||
// push the current ruleset to the frames stack
|
||||
$env->unshiftFrame( $ruleset );
|
||||
|
||||
// Evaluate imports
|
||||
if ( $ruleset->root || $ruleset->allowImports || !$ruleset->strictImports ) {
|
||||
$ruleset->evalImports( $env );
|
||||
}
|
||||
|
||||
return $ruleset;
|
||||
}
|
||||
|
||||
function evalImports( $env ) {
|
||||
$rules_len = count( $this->rules );
|
||||
for ( $i = 0; $i < $rules_len; $i++ ) {
|
||||
$rule = $this->rules[$i];
|
||||
|
||||
if ( $rule instanceof Less_Tree_Import ) {
|
||||
$rules = $rule->compile( $env );
|
||||
if ( is_array( $rules ) ) {
|
||||
array_splice( $this->rules, $i, 1, $rules );
|
||||
$temp_count = count( $rules ) - 1;
|
||||
$i += $temp_count;
|
||||
$rules_len += $temp_count;
|
||||
} else {
|
||||
array_splice( $this->rules, $i, 1, array( $rules ) );
|
||||
}
|
||||
|
||||
$this->resetCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function makeImportant() {
|
||||
$important_rules = array();
|
||||
foreach ( $this->rules as $rule ) {
|
||||
if ( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_Ruleset || $rule instanceof Less_Tree_NameValue ) {
|
||||
$important_rules[] = $rule->makeImportant();
|
||||
} else {
|
||||
$important_rules[] = $rule;
|
||||
}
|
||||
}
|
||||
|
||||
return new Less_Tree_Ruleset( $this->selectors, $important_rules, $this->strictImports );
|
||||
}
|
||||
|
||||
public function matchArgs( $args ) {
|
||||
return !$args;
|
||||
}
|
||||
|
||||
// lets you call a css selector with a guard
|
||||
public function matchCondition( $args, $env ) {
|
||||
$lastSelector = end( $this->selectors );
|
||||
|
||||
if ( !$lastSelector->evaldCondition ) {
|
||||
return false;
|
||||
}
|
||||
if ( $lastSelector->condition && !$lastSelector->condition->compile( $env->copyEvalEnv( $env->frames ) ) ) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function resetCache() {
|
||||
$this->_rulesets = null;
|
||||
$this->_variables = null;
|
||||
$this->lookups = array();
|
||||
}
|
||||
|
||||
public function variables() {
|
||||
$this->_variables = array();
|
||||
foreach ( $this->rules as $r ) {
|
||||
if ( $r instanceof Less_Tree_Rule && $r->variable === true ) {
|
||||
$this->_variables[$r->name] = $r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function variable( $name ) {
|
||||
if ( is_null( $this->_variables ) ) {
|
||||
$this->variables();
|
||||
}
|
||||
return isset( $this->_variables[$name] ) ? $this->_variables[$name] : null;
|
||||
}
|
||||
|
||||
public function find( $selector, $self = null ) {
|
||||
$key = implode( ' ', $selector->_oelements );
|
||||
|
||||
if ( !isset( $this->lookups[$key] ) ) {
|
||||
|
||||
if ( !$self ) {
|
||||
$self = $this->ruleset_id;
|
||||
}
|
||||
|
||||
$this->lookups[$key] = array();
|
||||
|
||||
$first_oelement = $selector->_oelements[0];
|
||||
|
||||
foreach ( $this->rules as $rule ) {
|
||||
if ( $rule instanceof Less_Tree_Ruleset && $rule->ruleset_id != $self ) {
|
||||
|
||||
if ( isset( $rule->first_oelements[$first_oelement] ) ) {
|
||||
|
||||
foreach ( $rule->selectors as $ruleSelector ) {
|
||||
$match = $selector->match( $ruleSelector );
|
||||
if ( $match ) {
|
||||
if ( $selector->elements_len > $match ) {
|
||||
$this->lookups[$key] = array_merge( $this->lookups[$key], $rule->find( new Less_Tree_Selector( array_slice( $selector->elements, $match ) ), $self ) );
|
||||
} else {
|
||||
$this->lookups[$key][] = $rule;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->lookups[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Less_Tree::genCSS
|
||||
*/
|
||||
public function genCSS( $output ) {
|
||||
if ( !$this->root ) {
|
||||
Less_Environment::$tabLevel++;
|
||||
}
|
||||
|
||||
$tabRuleStr = $tabSetStr = '';
|
||||
if ( !Less_Parser::$options['compress'] ) {
|
||||
if ( Less_Environment::$tabLevel ) {
|
||||
$tabRuleStr = "\n".str_repeat( Less_Parser::$options['indentation'], Less_Environment::$tabLevel );
|
||||
$tabSetStr = "\n".str_repeat( Less_Parser::$options['indentation'], Less_Environment::$tabLevel - 1 );
|
||||
} else {
|
||||
$tabSetStr = $tabRuleStr = "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$ruleNodes = array();
|
||||
$rulesetNodes = array();
|
||||
foreach ( $this->rules as $rule ) {
|
||||
|
||||
$class = get_class( $rule );
|
||||
if ( ( $class === 'Less_Tree_Media' ) || ( $class === 'Less_Tree_Directive' ) || ( $this->root && $class === 'Less_Tree_Comment' ) || ( $class === 'Less_Tree_Ruleset' && $rule->rules ) ) {
|
||||
$rulesetNodes[] = $rule;
|
||||
} else {
|
||||
$ruleNodes[] = $rule;
|
||||
}
|
||||
}
|
||||
|
||||
// If this is the root node, we don't render
|
||||
// a selector, or {}.
|
||||
if ( !$this->root ) {
|
||||
|
||||
/*
|
||||
debugInfo = tree.debugInfo(env, this, tabSetStr);
|
||||
|
||||
if (debugInfo) {
|
||||
output.add(debugInfo);
|
||||
output.add(tabSetStr);
|
||||
}
|
||||
*/
|
||||
|
||||
$paths_len = count( $this->paths );
|
||||
for ( $i = 0; $i < $paths_len; $i++ ) {
|
||||
$path = $this->paths[$i];
|
||||
$firstSelector = true;
|
||||
|
||||
foreach ( $path as $p ) {
|
||||
$p->genCSS( $output, $firstSelector );
|
||||
$firstSelector = false;
|
||||
}
|
||||
|
||||
if ( $i + 1 < $paths_len ) {
|
||||
$output->add( ',' . $tabSetStr );
|
||||
}
|
||||
}
|
||||
|
||||
$output->add( ( Less_Parser::$options['compress'] ? '{' : " {" ) . $tabRuleStr );
|
||||
}
|
||||
|
||||
// Compile rules and rulesets
|
||||
$ruleNodes_len = count( $ruleNodes );
|
||||
$rulesetNodes_len = count( $rulesetNodes );
|
||||
for ( $i = 0; $i < $ruleNodes_len; $i++ ) {
|
||||
$rule = $ruleNodes[$i];
|
||||
|
||||
// @page{ directive ends up with root elements inside it, a mix of rules and rulesets
|
||||
// In this instance we do not know whether it is the last property
|
||||
if ( $i + 1 === $ruleNodes_len && ( !$this->root || $rulesetNodes_len === 0 || $this->firstRoot ) ) {
|
||||
Less_Environment::$lastRule = true;
|
||||
}
|
||||
|
||||
$rule->genCSS( $output );
|
||||
|
||||
if ( !Less_Environment::$lastRule ) {
|
||||
$output->add( $tabRuleStr );
|
||||
} else {
|
||||
Less_Environment::$lastRule = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !$this->root ) {
|
||||
$output->add( $tabSetStr . '}' );
|
||||
Less_Environment::$tabLevel--;
|
||||
}
|
||||
|
||||
$firstRuleset = true;
|
||||
$space = ( $this->root ? $tabRuleStr : $tabSetStr );
|
||||
for ( $i = 0; $i < $rulesetNodes_len; $i++ ) {
|
||||
|
||||
if ( $ruleNodes_len && $firstRuleset ) {
|
||||
$output->add( $space );
|
||||
} elseif ( !$firstRuleset ) {
|
||||
$output->add( $space );
|
||||
}
|
||||
$firstRuleset = false;
|
||||
$rulesetNodes[$i]->genCSS( $output );
|
||||
}
|
||||
|
||||
if ( !Less_Parser::$options['compress'] && $this->firstRoot ) {
|
||||
$output->add( "\n" );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function markReferenced() {
|
||||
if ( !$this->selectors ) {
|
||||
return;
|
||||
}
|
||||
foreach ( $this->selectors as $selector ) {
|
||||
$selector->markReferenced();
|
||||
}
|
||||
}
|
||||
|
||||
public function joinSelectors( $context, $selectors ) {
|
||||
$paths = array();
|
||||
if ( is_array( $selectors ) ) {
|
||||
foreach ( $selectors as $selector ) {
|
||||
$this->joinSelector( $paths, $context, $selector );
|
||||
}
|
||||
}
|
||||
return $paths;
|
||||
}
|
||||
|
||||
public function joinSelector( &$paths, $context, $selector ) {
|
||||
$hasParentSelector = false;
|
||||
|
||||
foreach ( $selector->elements as $el ) {
|
||||
if ( $el->value === '&' ) {
|
||||
$hasParentSelector = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !$hasParentSelector ) {
|
||||
if ( $context ) {
|
||||
foreach ( $context as $context_el ) {
|
||||
$paths[] = array_merge( $context_el, array( $selector ) );
|
||||
}
|
||||
} else {
|
||||
$paths[] = array( $selector );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// The paths are [[Selector]]
|
||||
// The first list is a list of comma separated selectors
|
||||
// The inner list is a list of inheritance separated selectors
|
||||
// e.g.
|
||||
// .a, .b {
|
||||
// .c {
|
||||
// }
|
||||
// }
|
||||
// == [[.a] [.c]] [[.b] [.c]]
|
||||
//
|
||||
|
||||
// the elements from the current selector so far
|
||||
$currentElements = array();
|
||||
// the current list of new selectors to add to the path.
|
||||
// We will build it up. We initiate it with one empty selector as we "multiply" the new selectors
|
||||
// by the parents
|
||||
$newSelectors = array( array() );
|
||||
|
||||
foreach ( $selector->elements as $el ) {
|
||||
|
||||
// non parent reference elements just get added
|
||||
if ( $el->value !== '&' ) {
|
||||
$currentElements[] = $el;
|
||||
} else {
|
||||
// the new list of selectors to add
|
||||
$selectorsMultiplied = array();
|
||||
|
||||
// merge the current list of non parent selector elements
|
||||
// on to the current list of selectors to add
|
||||
if ( $currentElements ) {
|
||||
$this->mergeElementsOnToSelectors( $currentElements, $newSelectors );
|
||||
}
|
||||
|
||||
// loop through our current selectors
|
||||
foreach ( $newSelectors as $sel ) {
|
||||
|
||||
// if we don't have any parent paths, the & might be in a mixin so that it can be used
|
||||
// whether there are parents or not
|
||||
if ( !$context ) {
|
||||
// the combinator used on el should now be applied to the next element instead so that
|
||||
// it is not lost
|
||||
if ( $sel ) {
|
||||
$sel[0]->elements = array_slice( $sel[0]->elements, 0 );
|
||||
$sel[0]->elements[] = new Less_Tree_Element( $el->combinator, '', $el->index, $el->currentFileInfo );
|
||||
}
|
||||
$selectorsMultiplied[] = $sel;
|
||||
} else {
|
||||
|
||||
// and the parent selectors
|
||||
foreach ( $context as $parentSel ) {
|
||||
// We need to put the current selectors
|
||||
// then join the last selector's elements on to the parents selectors
|
||||
|
||||
// our new selector path
|
||||
$newSelectorPath = array();
|
||||
// selectors from the parent after the join
|
||||
$afterParentJoin = array();
|
||||
$newJoinedSelectorEmpty = true;
|
||||
|
||||
// construct the joined selector - if & is the first thing this will be empty,
|
||||
// if not newJoinedSelector will be the last set of elements in the selector
|
||||
if ( $sel ) {
|
||||
$newSelectorPath = $sel;
|
||||
$lastSelector = array_pop( $newSelectorPath );
|
||||
$newJoinedSelector = $selector->createDerived( array_slice( $lastSelector->elements, 0 ) );
|
||||
$newJoinedSelectorEmpty = false;
|
||||
} else {
|
||||
$newJoinedSelector = $selector->createDerived( array() );
|
||||
}
|
||||
|
||||
// put together the parent selectors after the join
|
||||
if ( count( $parentSel ) > 1 ) {
|
||||
$afterParentJoin = array_merge( $afterParentJoin, array_slice( $parentSel, 1 ) );
|
||||
}
|
||||
|
||||
if ( $parentSel ) {
|
||||
$newJoinedSelectorEmpty = false;
|
||||
|
||||
// join the elements so far with the first part of the parent
|
||||
$newJoinedSelector->elements[] = new Less_Tree_Element( $el->combinator, $parentSel[0]->elements[0]->value, $el->index, $el->currentFileInfo );
|
||||
|
||||
$newJoinedSelector->elements = array_merge( $newJoinedSelector->elements, array_slice( $parentSel[0]->elements, 1 ) );
|
||||
}
|
||||
|
||||
if ( !$newJoinedSelectorEmpty ) {
|
||||
// now add the joined selector
|
||||
$newSelectorPath[] = $newJoinedSelector;
|
||||
}
|
||||
|
||||
// and the rest of the parent
|
||||
$newSelectorPath = array_merge( $newSelectorPath, $afterParentJoin );
|
||||
|
||||
// add that to our new set of selectors
|
||||
$selectorsMultiplied[] = $newSelectorPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// our new selectors has been multiplied, so reset the state
|
||||
$newSelectors = $selectorsMultiplied;
|
||||
$currentElements = array();
|
||||
}
|
||||
}
|
||||
|
||||
// if we have any elements left over (e.g. .a& .b == .b)
|
||||
// add them on to all the current selectors
|
||||
if ( $currentElements ) {
|
||||
$this->mergeElementsOnToSelectors( $currentElements, $newSelectors );
|
||||
}
|
||||
foreach ( $newSelectors as $new_sel ) {
|
||||
if ( $new_sel ) {
|
||||
$paths[] = $new_sel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mergeElementsOnToSelectors( $elements, &$selectors ) {
|
||||
if ( !$selectors ) {
|
||||
$selectors[] = array( new Less_Tree_Selector( $elements ) );
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $selectors as &$sel ) {
|
||||
|
||||
// if the previous thing in sel is a parent this needs to join on to it
|
||||
if ( $sel ) {
|
||||
$last = count( $sel ) - 1;
|
||||
$sel[$last] = $sel[$last]->createDerived( array_merge( $sel[$last]->elements, $elements ) );
|
||||
} else {
|
||||
$sel[] = new Less_Tree_Selector( $elements );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
26
less.php/lib/Less/Tree/RulesetCall.php
Normal file
26
less.php/lib/Less/Tree/RulesetCall.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* RulesetCall
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_RulesetCall extends Less_Tree {
|
||||
|
||||
public $variable;
|
||||
public $type = "RulesetCall";
|
||||
|
||||
public function __construct( $variable ) {
|
||||
$this->variable = $variable;
|
||||
}
|
||||
|
||||
public function accept( $visitor ) {
|
||||
}
|
||||
|
||||
public function compile( $env ) {
|
||||
$variable = new Less_Tree_Variable( $this->variable );
|
||||
$detachedRuleset = $variable->compile( $env );
|
||||
return $detachedRuleset->callEval( $env );
|
||||
}
|
||||
}
|
165
less.php/lib/Less/Tree/Selector.php
Normal file
165
less.php/lib/Less/Tree/Selector.php
Normal file
@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Selector
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Selector extends Less_Tree {
|
||||
|
||||
public $elements;
|
||||
public $condition;
|
||||
public $extendList = array();
|
||||
public $_css;
|
||||
public $index;
|
||||
public $evaldCondition = false;
|
||||
public $type = 'Selector';
|
||||
public $currentFileInfo = array();
|
||||
public $isReferenced;
|
||||
public $mediaEmpty;
|
||||
|
||||
public $elements_len = 0;
|
||||
|
||||
public $_oelements;
|
||||
public $_oelements_assoc;
|
||||
public $_oelements_len;
|
||||
public $cacheable = true;
|
||||
|
||||
/**
|
||||
* @param boolean $isReferenced
|
||||
*/
|
||||
public function __construct( $elements, $extendList = array(), $condition = null, $index = null, $currentFileInfo = null, $isReferenced = null ) {
|
||||
$this->elements = $elements;
|
||||
$this->elements_len = count( $elements );
|
||||
$this->extendList = $extendList;
|
||||
$this->condition = $condition;
|
||||
if ( $currentFileInfo ) {
|
||||
$this->currentFileInfo = $currentFileInfo;
|
||||
}
|
||||
$this->isReferenced = $isReferenced;
|
||||
if ( !$condition ) {
|
||||
$this->evaldCondition = true;
|
||||
}
|
||||
|
||||
$this->CacheElements();
|
||||
}
|
||||
|
||||
public function accept( $visitor ) {
|
||||
$this->elements = $visitor->visitArray( $this->elements );
|
||||
$this->extendList = $visitor->visitArray( $this->extendList );
|
||||
if ( $this->condition ) {
|
||||
$this->condition = $visitor->visitObj( $this->condition );
|
||||
}
|
||||
|
||||
if ( $visitor instanceof Less_Visitor_extendFinder ) {
|
||||
$this->CacheElements();
|
||||
}
|
||||
}
|
||||
|
||||
public function createDerived( $elements, $extendList = null, $evaldCondition = null ) {
|
||||
$newSelector = new Less_Tree_Selector( $elements, ( $extendList ? $extendList : $this->extendList ), null, $this->index, $this->currentFileInfo, $this->isReferenced );
|
||||
$newSelector->evaldCondition = $evaldCondition ? $evaldCondition : $this->evaldCondition;
|
||||
return $newSelector;
|
||||
}
|
||||
|
||||
public function match( $other ) {
|
||||
if ( !$other->_oelements || ( $this->elements_len < $other->_oelements_len ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for ( $i = 0; $i < $other->_oelements_len; $i++ ) {
|
||||
if ( $this->elements[$i]->value !== $other->_oelements[$i] ) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return $other->_oelements_len; // return number of matched elements
|
||||
}
|
||||
|
||||
public function CacheElements() {
|
||||
$this->_oelements = array();
|
||||
$this->_oelements_assoc = array();
|
||||
|
||||
$css = '';
|
||||
|
||||
foreach ( $this->elements as $v ) {
|
||||
|
||||
$css .= $v->combinator;
|
||||
if ( !$v->value_is_object ) {
|
||||
$css .= $v->value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( !property_exists( $v->value, 'value' ) || !is_string( $v->value->value ) ) {
|
||||
$this->cacheable = false;
|
||||
return;
|
||||
}
|
||||
$css .= $v->value->value;
|
||||
}
|
||||
|
||||
$this->_oelements_len = preg_match_all( '/[,&#\.\w-](?:[\w-]|(?:\\\\.))*/', $css, $matches );
|
||||
if ( $this->_oelements_len ) {
|
||||
$this->_oelements = $matches[0];
|
||||
|
||||
if ( $this->_oelements[0] === '&' ) {
|
||||
array_shift( $this->_oelements );
|
||||
$this->_oelements_len--;
|
||||
}
|
||||
|
||||
$this->_oelements_assoc = array_fill_keys( $this->_oelements, true );
|
||||
}
|
||||
}
|
||||
|
||||
public function isJustParentSelector() {
|
||||
return !$this->mediaEmpty &&
|
||||
count( $this->elements ) === 1 &&
|
||||
$this->elements[0]->value === '&' &&
|
||||
( $this->elements[0]->combinator === ' ' || $this->elements[0]->combinator === '' );
|
||||
}
|
||||
|
||||
public function compile( $env ) {
|
||||
$elements = array();
|
||||
foreach ( $this->elements as $el ) {
|
||||
$elements[] = $el->compile( $env );
|
||||
}
|
||||
|
||||
$extendList = array();
|
||||
foreach ( $this->extendList as $el ) {
|
||||
$extendList[] = $el->compile( $el );
|
||||
}
|
||||
|
||||
$evaldCondition = false;
|
||||
if ( $this->condition ) {
|
||||
$evaldCondition = $this->condition->compile( $env );
|
||||
}
|
||||
|
||||
return $this->createDerived( $elements, $extendList, $evaldCondition );
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Less_Tree::genCSS
|
||||
*/
|
||||
public function genCSS( $output, $firstSelector = true ) {
|
||||
if ( !$firstSelector && $this->elements[0]->combinator === "" ) {
|
||||
$output->add( ' ', $this->currentFileInfo, $this->index );
|
||||
}
|
||||
|
||||
foreach ( $this->elements as $element ) {
|
||||
$element->genCSS( $output );
|
||||
}
|
||||
}
|
||||
|
||||
public function markReferenced() {
|
||||
$this->isReferenced = true;
|
||||
}
|
||||
|
||||
public function getIsReferenced() {
|
||||
return !isset( $this->currentFileInfo['reference'] ) || !$this->currentFileInfo['reference'] || $this->isReferenced;
|
||||
}
|
||||
|
||||
public function getIsOutput() {
|
||||
return $this->evaldCondition;
|
||||
}
|
||||
|
||||
}
|
28
less.php/lib/Less/Tree/UnicodeDescriptor.php
Normal file
28
less.php/lib/Less/Tree/UnicodeDescriptor.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* UnicodeDescriptor
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_UnicodeDescriptor extends Less_Tree {
|
||||
|
||||
public $value;
|
||||
public $type = 'UnicodeDescriptor';
|
||||
|
||||
public function __construct( $value ) {
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Less_Tree::genCSS
|
||||
*/
|
||||
public function genCSS( $output ) {
|
||||
$output->add( $this->value );
|
||||
}
|
||||
|
||||
public function compile() {
|
||||
return $this;
|
||||
}
|
||||
}
|
142
less.php/lib/Less/Tree/Unit.php
Normal file
142
less.php/lib/Less/Tree/Unit.php
Normal file
@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Unit
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Unit extends Less_Tree {
|
||||
|
||||
var $numerator = array();
|
||||
var $denominator = array();
|
||||
public $backupUnit;
|
||||
public $type = 'Unit';
|
||||
|
||||
public function __construct( $numerator = array(), $denominator = array(), $backupUnit = null ) {
|
||||
$this->numerator = $numerator;
|
||||
$this->denominator = $denominator;
|
||||
$this->backupUnit = $backupUnit;
|
||||
}
|
||||
|
||||
public function __clone() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Less_Tree::genCSS
|
||||
*/
|
||||
public function genCSS( $output ) {
|
||||
if ( $this->numerator ) {
|
||||
$output->add( $this->numerator[0] );
|
||||
} elseif ( $this->denominator ) {
|
||||
$output->add( $this->denominator[0] );
|
||||
} elseif ( !Less_Parser::$options['strictUnits'] && $this->backupUnit ) {
|
||||
$output->add( $this->backupUnit );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public function toString() {
|
||||
$returnStr = implode( '*', $this->numerator );
|
||||
foreach ( $this->denominator as $d ) {
|
||||
$returnStr .= '/'.$d;
|
||||
}
|
||||
return $returnStr;
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
return $this->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Less_Tree_Unit $other
|
||||
*/
|
||||
public function compare( $other ) {
|
||||
return $this->is( $other->toString() ) ? 0 : -1;
|
||||
}
|
||||
|
||||
public function is( $unitString ) {
|
||||
return $this->toString() === $unitString;
|
||||
}
|
||||
|
||||
public function isLength() {
|
||||
$css = $this->toCSS();
|
||||
return !!preg_match( '/px|em|%|in|cm|mm|pc|pt|ex/', $css );
|
||||
}
|
||||
|
||||
public function isAngle() {
|
||||
return isset( Less_Tree_UnitConversions::$angle[$this->toCSS()] );
|
||||
}
|
||||
|
||||
public function isEmpty() {
|
||||
return !$this->numerator && !$this->denominator;
|
||||
}
|
||||
|
||||
public function isSingular() {
|
||||
return count( $this->numerator ) <= 1 && !$this->denominator;
|
||||
}
|
||||
|
||||
public function usedUnits() {
|
||||
$result = array();
|
||||
|
||||
foreach ( Less_Tree_UnitConversions::$groups as $groupName ) {
|
||||
$group = Less_Tree_UnitConversions::${$groupName};
|
||||
|
||||
foreach ( $this->numerator as $atomicUnit ) {
|
||||
if ( isset( $group[$atomicUnit] ) && !isset( $result[$groupName] ) ) {
|
||||
$result[$groupName] = $atomicUnit;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $this->denominator as $atomicUnit ) {
|
||||
if ( isset( $group[$atomicUnit] ) && !isset( $result[$groupName] ) ) {
|
||||
$result[$groupName] = $atomicUnit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function cancel() {
|
||||
$counter = array();
|
||||
$backup = null;
|
||||
|
||||
foreach ( $this->numerator as $atomicUnit ) {
|
||||
if ( !$backup ) {
|
||||
$backup = $atomicUnit;
|
||||
}
|
||||
$counter[$atomicUnit] = ( isset( $counter[$atomicUnit] ) ? $counter[$atomicUnit] : 0 ) + 1;
|
||||
}
|
||||
|
||||
foreach ( $this->denominator as $atomicUnit ) {
|
||||
if ( !$backup ) {
|
||||
$backup = $atomicUnit;
|
||||
}
|
||||
$counter[$atomicUnit] = ( isset( $counter[$atomicUnit] ) ? $counter[$atomicUnit] : 0 ) - 1;
|
||||
}
|
||||
|
||||
$this->numerator = array();
|
||||
$this->denominator = array();
|
||||
|
||||
foreach ( $counter as $atomicUnit => $count ) {
|
||||
if ( $count > 0 ) {
|
||||
for ( $i = 0; $i < $count; $i++ ) {
|
||||
$this->numerator[] = $atomicUnit;
|
||||
}
|
||||
} elseif ( $count < 0 ) {
|
||||
for ( $i = 0; $i < -$count; $i++ ) {
|
||||
$this->denominator[] = $atomicUnit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( !$this->numerator && !$this->denominator && $backup ) {
|
||||
$this->backupUnit = $backup;
|
||||
}
|
||||
|
||||
sort( $this->numerator );
|
||||
sort( $this->denominator );
|
||||
}
|
||||
|
||||
}
|
35
less.php/lib/Less/Tree/UnitConversions.php
Normal file
35
less.php/lib/Less/Tree/UnitConversions.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* UnitConversions
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_UnitConversions {
|
||||
|
||||
public static $groups = array( 'length','duration','angle' );
|
||||
|
||||
public static $length = array(
|
||||
'm' => 1,
|
||||
'cm' => 0.01,
|
||||
'mm' => 0.001,
|
||||
'in' => 0.0254,
|
||||
'px' => 0.000264583, // 0.0254 / 96,
|
||||
'pt' => 0.000352778, // 0.0254 / 72,
|
||||
'pc' => 0.004233333, // 0.0254 / 72 * 12
|
||||
);
|
||||
|
||||
public static $duration = array(
|
||||
's' => 1,
|
||||
'ms' => 0.001
|
||||
);
|
||||
|
||||
public static $angle = array(
|
||||
'rad' => 0.1591549430919, // 1/(2*M_PI),
|
||||
'deg' => 0.002777778, // 1/360,
|
||||
'grad' => 0.0025, // 1/400,
|
||||
'turn' => 1
|
||||
);
|
||||
|
||||
}
|
76
less.php/lib/Less/Tree/Url.php
Normal file
76
less.php/lib/Less/Tree/Url.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Url
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Url extends Less_Tree {
|
||||
|
||||
public $attrs;
|
||||
public $value;
|
||||
public $currentFileInfo;
|
||||
public $isEvald;
|
||||
public $type = 'Url';
|
||||
|
||||
public function __construct( $value, $currentFileInfo = null, $isEvald = null ) {
|
||||
$this->value = $value;
|
||||
$this->currentFileInfo = $currentFileInfo;
|
||||
$this->isEvald = $isEvald;
|
||||
}
|
||||
|
||||
public function accept( $visitor ) {
|
||||
$this->value = $visitor->visitObj( $this->value );
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Less_Tree::genCSS
|
||||
*/
|
||||
public function genCSS( $output ) {
|
||||
$output->add( 'url(' );
|
||||
$this->value->genCSS( $output );
|
||||
$output->add( ')' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Less_Functions $ctx
|
||||
*/
|
||||
public function compile( $ctx ) {
|
||||
$val = $this->value->compile( $ctx );
|
||||
|
||||
if ( !$this->isEvald ) {
|
||||
// Add the base path if the URL is relative
|
||||
if ( Less_Parser::$options['relativeUrls']
|
||||
&& $this->currentFileInfo
|
||||
&& is_string( $val->value )
|
||||
&& Less_Environment::isPathRelative( $val->value )
|
||||
) {
|
||||
$rootpath = $this->currentFileInfo['uri_root'];
|
||||
if ( !$val->quote ) {
|
||||
$rootpath = preg_replace( '/[\(\)\'"\s]/', '\\$1', $rootpath );
|
||||
}
|
||||
$val->value = $rootpath . $val->value;
|
||||
}
|
||||
|
||||
$val->value = Less_Environment::normalizePath( $val->value );
|
||||
}
|
||||
|
||||
// Add cache buster if enabled
|
||||
if ( Less_Parser::$options['urlArgs'] ) {
|
||||
if ( !preg_match( '/^\s*data:/', $val->value ) ) {
|
||||
$delimiter = strpos( $val->value, '?' ) === false ? '?' : '&';
|
||||
$urlArgs = $delimiter . Less_Parser::$options['urlArgs'];
|
||||
$hash_pos = strpos( $val->value, '#' );
|
||||
if ( $hash_pos !== false ) {
|
||||
$val->value = substr_replace( $val->value, $urlArgs, $hash_pos, 0 );
|
||||
} else {
|
||||
$val->value .= $urlArgs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Less_Tree_URL( $val, $this->currentFileInfo, true );
|
||||
}
|
||||
|
||||
}
|
47
less.php/lib/Less/Tree/Value.php
Normal file
47
less.php/lib/Less/Tree/Value.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Value
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Value extends Less_Tree {
|
||||
|
||||
public $type = 'Value';
|
||||
public $value;
|
||||
|
||||
public function __construct( $value ) {
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function accept( $visitor ) {
|
||||
$this->value = $visitor->visitArray( $this->value );
|
||||
}
|
||||
|
||||
public function compile( $env ) {
|
||||
$ret = array();
|
||||
$i = 0;
|
||||
foreach ( $this->value as $i => $v ) {
|
||||
$ret[] = $v->compile( $env );
|
||||
}
|
||||
if ( $i > 0 ) {
|
||||
return new Less_Tree_Value( $ret );
|
||||
}
|
||||
return $ret[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Less_Tree::genCSS
|
||||
*/
|
||||
function genCSS( $output ) {
|
||||
$len = count( $this->value );
|
||||
for ( $i = 0; $i < $len; $i++ ) {
|
||||
$this->value[$i]->genCSS( $output );
|
||||
if ( $i + 1 < $len ) {
|
||||
$output->add( Less_Environment::$_outputMap[','] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
51
less.php/lib/Less/Tree/Variable.php
Normal file
51
less.php/lib/Less/Tree/Variable.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Variable
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage tree
|
||||
*/
|
||||
class Less_Tree_Variable extends Less_Tree {
|
||||
|
||||
public $name;
|
||||
public $index;
|
||||
public $currentFileInfo;
|
||||
public $evaluating = false;
|
||||
public $type = 'Variable';
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*/
|
||||
public function __construct( $name, $index = null, $currentFileInfo = null ) {
|
||||
$this->name = $name;
|
||||
$this->index = $index;
|
||||
$this->currentFileInfo = $currentFileInfo;
|
||||
}
|
||||
|
||||
public function compile( $env ) {
|
||||
if ( $this->name[1] === '@' ) {
|
||||
$v = new Less_Tree_Variable( substr( $this->name, 1 ), $this->index + 1, $this->currentFileInfo );
|
||||
$name = '@' . $v->compile( $env )->value;
|
||||
} else {
|
||||
$name = $this->name;
|
||||
}
|
||||
|
||||
if ( $this->evaluating ) {
|
||||
throw new Less_Exception_Compiler( "Recursive variable definition for " . $name, null, $this->index, $this->currentFileInfo );
|
||||
}
|
||||
|
||||
$this->evaluating = true;
|
||||
|
||||
foreach ( $env->frames as $frame ) {
|
||||
if ( $v = $frame->variable( $name ) ) {
|
||||
$r = $v->value->compile( $env );
|
||||
$this->evaluating = false;
|
||||
return $r;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Less_Exception_Compiler( "variable " . $name . " is undefined in file ".$this->currentFileInfo["filename"], null, $this->index, $this->currentFileInfo );
|
||||
}
|
||||
|
||||
}
|
15
less.php/lib/Less/Version.php
Normal file
15
less.php/lib/Less/Version.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Release numbers
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage version
|
||||
*/
|
||||
class Less_Version {
|
||||
|
||||
public const version = '3.1.0'; // The current build number of less.php
|
||||
public const less_version = '2.5.3'; // The less.js version that this build should be compatible with
|
||||
public const cache_version = '253'; // The parser cache version
|
||||
|
||||
}
|
46
less.php/lib/Less/Visitor.php
Normal file
46
less.php/lib/Less/Visitor.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Visitor
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage visitor
|
||||
*/
|
||||
class Less_Visitor {
|
||||
|
||||
protected $methods = array();
|
||||
protected $_visitFnCache = array();
|
||||
|
||||
public function __construct() {
|
||||
$this->_visitFnCache = get_class_methods( get_class( $this ) );
|
||||
$this->_visitFnCache = array_flip( $this->_visitFnCache );
|
||||
}
|
||||
|
||||
public function visitObj( $node ) {
|
||||
$funcName = 'visit'.$node->type;
|
||||
if ( isset( $this->_visitFnCache[$funcName] ) ) {
|
||||
|
||||
$visitDeeper = true;
|
||||
$this->$funcName( $node, $visitDeeper );
|
||||
|
||||
if ( $visitDeeper ) {
|
||||
$node->accept( $this );
|
||||
}
|
||||
|
||||
$funcName = $funcName . "Out";
|
||||
if ( isset( $this->_visitFnCache[$funcName] ) ) {
|
||||
$this->$funcName( $node );
|
||||
}
|
||||
|
||||
} else {
|
||||
$node->accept( $this );
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
public function visitArray( $nodes ) {
|
||||
array_map( array( $this,'visitObj' ), $nodes );
|
||||
return $nodes;
|
||||
}
|
||||
}
|
109
less.php/lib/Less/Visitor/extendFinder.php
Normal file
109
less.php/lib/Less/Visitor/extendFinder.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Extend Finder Visitor
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage visitor
|
||||
*/
|
||||
class Less_Visitor_extendFinder extends Less_Visitor {
|
||||
|
||||
public $contexts = array();
|
||||
public $allExtendsStack;
|
||||
public $foundExtends;
|
||||
|
||||
public function __construct() {
|
||||
$this->contexts = array();
|
||||
$this->allExtendsStack = array( array() );
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Less_Tree_Ruleset $root
|
||||
*/
|
||||
public function run( $root ) {
|
||||
$root = $this->visitObj( $root );
|
||||
$root->allExtends =& $this->allExtendsStack[0];
|
||||
return $root;
|
||||
}
|
||||
|
||||
public function visitRule( $ruleNode, &$visitDeeper ) {
|
||||
$visitDeeper = false;
|
||||
}
|
||||
|
||||
public function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ) {
|
||||
$visitDeeper = false;
|
||||
}
|
||||
|
||||
public function visitRuleset( $rulesetNode ) {
|
||||
if ( $rulesetNode->root ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$allSelectorsExtendList = array();
|
||||
|
||||
// get &:extend(.a); rules which apply to all selectors in this ruleset
|
||||
if ( $rulesetNode->rules ) {
|
||||
foreach ( $rulesetNode->rules as $rule ) {
|
||||
if ( $rule instanceof Less_Tree_Extend ) {
|
||||
$allSelectorsExtendList[] = $rule;
|
||||
$rulesetNode->extendOnEveryPath = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now find every selector and apply the extends that apply to all extends
|
||||
// and the ones which apply to an individual extend
|
||||
foreach ( $rulesetNode->paths as $selectorPath ) {
|
||||
$selector = end( $selectorPath ); // $selectorPath[ count($selectorPath)-1];
|
||||
|
||||
$j = 0;
|
||||
foreach ( $selector->extendList as $extend ) {
|
||||
$this->allExtendsStackPush( $rulesetNode, $selectorPath, $extend, $j );
|
||||
}
|
||||
foreach ( $allSelectorsExtendList as $extend ) {
|
||||
$this->allExtendsStackPush( $rulesetNode, $selectorPath, $extend, $j );
|
||||
}
|
||||
}
|
||||
|
||||
$this->contexts[] = $rulesetNode->selectors;
|
||||
}
|
||||
|
||||
public function allExtendsStackPush( $rulesetNode, $selectorPath, $extend, &$j ) {
|
||||
$this->foundExtends = true;
|
||||
$extend = clone $extend;
|
||||
$extend->findSelfSelectors( $selectorPath );
|
||||
$extend->ruleset = $rulesetNode;
|
||||
if ( $j === 0 ) {
|
||||
$extend->firstExtendOnThisSelectorPath = true;
|
||||
}
|
||||
|
||||
$end_key = count( $this->allExtendsStack ) - 1;
|
||||
$this->allExtendsStack[$end_key][] = $extend;
|
||||
$j++;
|
||||
}
|
||||
|
||||
public function visitRulesetOut( $rulesetNode ) {
|
||||
if ( !is_object( $rulesetNode ) || !$rulesetNode->root ) {
|
||||
array_pop( $this->contexts );
|
||||
}
|
||||
}
|
||||
|
||||
public function visitMedia( $mediaNode ) {
|
||||
$mediaNode->allExtends = array();
|
||||
$this->allExtendsStack[] =& $mediaNode->allExtends;
|
||||
}
|
||||
|
||||
public function visitMediaOut() {
|
||||
array_pop( $this->allExtendsStack );
|
||||
}
|
||||
|
||||
public function visitDirective( $directiveNode ) {
|
||||
$directiveNode->allExtends = array();
|
||||
$this->allExtendsStack[] =& $directiveNode->allExtends;
|
||||
}
|
||||
|
||||
public function visitDirectiveOut() {
|
||||
array_pop( $this->allExtendsStack );
|
||||
}
|
||||
}
|
137
less.php/lib/Less/Visitor/import.php
Normal file
137
less.php/lib/Less/Visitor/import.php
Normal file
@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
class Less_Visitor_import extends Less_VisitorReplacing{
|
||||
|
||||
public $_visitor;
|
||||
public $_importer;
|
||||
public $importCount;
|
||||
|
||||
function __construct( $evalEnv ){
|
||||
$this->env = $evalEnv;
|
||||
$this->importCount = 0;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
||||
function run( $root ){
|
||||
$root = $this->visitObj($root);
|
||||
$this->isFinished = true;
|
||||
|
||||
//if( $this->importCount === 0) {
|
||||
// $this->_finish();
|
||||
//}
|
||||
}
|
||||
|
||||
function visitImport($importNode, &$visitDeeper ){
|
||||
$importVisitor = $this;
|
||||
$inlineCSS = $importNode->options['inline'];
|
||||
|
||||
if( !$importNode->css || $inlineCSS ){
|
||||
$evaldImportNode = $importNode->compileForImport($this->env);
|
||||
|
||||
if( $evaldImportNode && (!$evaldImportNode->css || $inlineCSS) ){
|
||||
$importNode = $evaldImportNode;
|
||||
$this->importCount++;
|
||||
$env = clone $this->env;
|
||||
|
||||
if( (isset($importNode->options['multiple']) && $importNode->options['multiple']) ){
|
||||
$env->importMultiple = true;
|
||||
}
|
||||
|
||||
//get path & uri
|
||||
$path_and_uri = null;
|
||||
if( is_callable(Less_Parser::$options['import_callback']) ){
|
||||
$path_and_uri = call_user_func(Less_Parser::$options['import_callback'],$importNode);
|
||||
}
|
||||
|
||||
if( !$path_and_uri ){
|
||||
$path_and_uri = $importNode->PathAndUri();
|
||||
}
|
||||
|
||||
if( $path_and_uri ){
|
||||
list($full_path, $uri) = $path_and_uri;
|
||||
}else{
|
||||
$full_path = $uri = $importNode->getPath();
|
||||
}
|
||||
|
||||
|
||||
//import once
|
||||
if( $importNode->skip( $full_path, $env) ){
|
||||
return array();
|
||||
}
|
||||
|
||||
if( $importNode->options['inline'] ){
|
||||
//todo needs to reference css file not import
|
||||
//$contents = new Less_Tree_Anonymous($importNode->root, 0, array('filename'=>$importNode->importedFilename), true );
|
||||
|
||||
Less_Parser::AddParsedFile($full_path);
|
||||
$contents = new Less_Tree_Anonymous( file_get_contents($full_path), 0, array(), true );
|
||||
|
||||
if( $importNode->features ){
|
||||
return new Less_Tree_Media( array($contents), $importNode->features->value );
|
||||
}
|
||||
|
||||
return array( $contents );
|
||||
}
|
||||
|
||||
|
||||
// css ?
|
||||
if( $importNode->css ){
|
||||
$features = ( $importNode->features ? $importNode->features->compile($env) : null );
|
||||
return new Less_Tree_Import( $importNode->compilePath( $env), $features, $importNode->options, $this->index);
|
||||
}
|
||||
|
||||
return $importNode->ParseImport( $full_path, $uri, $env );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$visitDeeper = false;
|
||||
return $importNode;
|
||||
}
|
||||
|
||||
|
||||
function visitRule( $ruleNode, &$visitDeeper ){
|
||||
$visitDeeper = false;
|
||||
return $ruleNode;
|
||||
}
|
||||
|
||||
function visitDirective($directiveNode, $visitArgs){
|
||||
array_unshift($this->env->frames,$directiveNode);
|
||||
return $directiveNode;
|
||||
}
|
||||
|
||||
function visitDirectiveOut($directiveNode) {
|
||||
array_shift($this->env->frames);
|
||||
}
|
||||
|
||||
function visitMixinDefinition($mixinDefinitionNode, $visitArgs) {
|
||||
array_unshift($this->env->frames,$mixinDefinitionNode);
|
||||
return $mixinDefinitionNode;
|
||||
}
|
||||
|
||||
function visitMixinDefinitionOut($mixinDefinitionNode) {
|
||||
array_shift($this->env->frames);
|
||||
}
|
||||
|
||||
function visitRuleset($rulesetNode, $visitArgs) {
|
||||
array_unshift($this->env->frames,$rulesetNode);
|
||||
return $rulesetNode;
|
||||
}
|
||||
|
||||
function visitRulesetOut($rulesetNode) {
|
||||
array_shift($this->env->frames);
|
||||
}
|
||||
|
||||
function visitMedia($mediaNode, $visitArgs) {
|
||||
array_unshift($this->env->frames, $mediaNode->ruleset);
|
||||
return $mediaNode;
|
||||
}
|
||||
|
||||
function visitMediaOut($mediaNode) {
|
||||
array_shift($this->env->frames);
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
68
less.php/lib/Less/Visitor/joinSelector.php
Normal file
68
less.php/lib/Less/Visitor/joinSelector.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Join Selector Visitor
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage visitor
|
||||
*/
|
||||
class Less_Visitor_joinSelector extends Less_Visitor {
|
||||
|
||||
public $contexts = array( array() );
|
||||
|
||||
/**
|
||||
* @param Less_Tree_Ruleset $root
|
||||
*/
|
||||
public function run( $root ) {
|
||||
return $this->visitObj( $root );
|
||||
}
|
||||
|
||||
public function visitRule( $ruleNode, &$visitDeeper ) {
|
||||
$visitDeeper = false;
|
||||
}
|
||||
|
||||
public function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ) {
|
||||
$visitDeeper = false;
|
||||
}
|
||||
|
||||
public function visitRuleset( $rulesetNode ) {
|
||||
$paths = array();
|
||||
|
||||
if ( !$rulesetNode->root ) {
|
||||
$selectors = array();
|
||||
|
||||
if ( $rulesetNode->selectors && $rulesetNode->selectors ) {
|
||||
foreach ( $rulesetNode->selectors as $selector ) {
|
||||
if ( $selector->getIsOutput() ) {
|
||||
$selectors[] = $selector;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( !$selectors ) {
|
||||
$rulesetNode->selectors = null;
|
||||
$rulesetNode->rules = null;
|
||||
} else {
|
||||
$context = end( $this->contexts ); // $context = $this->contexts[ count($this->contexts) - 1];
|
||||
$paths = $rulesetNode->joinSelectors( $context, $selectors );
|
||||
}
|
||||
|
||||
$rulesetNode->paths = $paths;
|
||||
}
|
||||
|
||||
$this->contexts[] = $paths; // different from less.js. Placed after joinSelectors() so that $this->contexts will get correct $paths
|
||||
}
|
||||
|
||||
public function visitRulesetOut() {
|
||||
array_pop( $this->contexts );
|
||||
}
|
||||
|
||||
public function visitMedia( $mediaNode ) {
|
||||
$context = end( $this->contexts ); // $context = $this->contexts[ count($this->contexts) - 1];
|
||||
|
||||
if ( !count( $context ) || ( is_object( $context[0] ) && $context[0]->multiMedia ) ) {
|
||||
$mediaNode->rules[0]->root = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
441
less.php/lib/Less/Visitor/processExtends.php
Normal file
441
less.php/lib/Less/Visitor/processExtends.php
Normal file
@ -0,0 +1,441 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Process Extends Visitor
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage visitor
|
||||
*/
|
||||
class Less_Visitor_processExtends extends Less_Visitor {
|
||||
|
||||
public $allExtendsStack;
|
||||
|
||||
/**
|
||||
* @param Less_Tree_Ruleset $root
|
||||
*/
|
||||
public function run( $root ) {
|
||||
$extendFinder = new Less_Visitor_extendFinder();
|
||||
$extendFinder->run( $root );
|
||||
if ( !$extendFinder->foundExtends ) {
|
||||
return $root;
|
||||
}
|
||||
|
||||
$root->allExtends = $this->doExtendChaining( $root->allExtends, $root->allExtends );
|
||||
|
||||
$this->allExtendsStack = array();
|
||||
$this->allExtendsStack[] = &$root->allExtends;
|
||||
|
||||
return $this->visitObj( $root );
|
||||
}
|
||||
|
||||
private function doExtendChaining( $extendsList, $extendsListTarget, $iterationCount = 0 ) {
|
||||
//
|
||||
// chaining is different from normal extension.. if we extend an extend then we are not just copying, altering and pasting
|
||||
// the selector we would do normally, but we are also adding an extend with the same target selector
|
||||
// this means this new extend can then go and alter other extends
|
||||
//
|
||||
// this method deals with all the chaining work - without it, extend is flat and doesn't work on other extend selectors
|
||||
// this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already processed if
|
||||
// we look at each selector at a time, as is done in visitRuleset
|
||||
|
||||
$extendsToAdd = array();
|
||||
|
||||
// loop through comparing every extend with every target extend.
|
||||
// a target extend is the one on the ruleset we are looking at copy/edit/pasting in place
|
||||
// e.g. .a:extend(.b) {} and .b:extend(.c) {} then the first extend extends the second one
|
||||
// and the second is the target.
|
||||
// the separation into two lists allows us to process a subset of chains with a bigger set, as is the
|
||||
// case when processing media queries
|
||||
for ( $extendIndex = 0, $extendsList_len = count( $extendsList ); $extendIndex < $extendsList_len; $extendIndex++ ) {
|
||||
for ( $targetExtendIndex = 0; $targetExtendIndex < count( $extendsListTarget ); $targetExtendIndex++ ) {
|
||||
|
||||
$extend = $extendsList[$extendIndex];
|
||||
$targetExtend = $extendsListTarget[$targetExtendIndex];
|
||||
|
||||
// Optimisation: Explicit reference, <https://github.com/wikimedia/less.php/pull/14>
|
||||
if ( \array_key_exists( $targetExtend->object_id, $extend->parent_ids ) ) {
|
||||
// ignore circular references
|
||||
continue;
|
||||
}
|
||||
|
||||
// find a match in the target extends self selector (the bit before :extend)
|
||||
$selectorPath = array( $targetExtend->selfSelectors[0] );
|
||||
$matches = $this->findMatch( $extend, $selectorPath );
|
||||
|
||||
if ( $matches ) {
|
||||
|
||||
// we found a match, so for each self selector..
|
||||
foreach ( $extend->selfSelectors as $selfSelector ) {
|
||||
|
||||
// process the extend as usual
|
||||
$newSelector = $this->extendSelector( $matches, $selectorPath, $selfSelector );
|
||||
|
||||
// but now we create a new extend from it
|
||||
$newExtend = new Less_Tree_Extend( $targetExtend->selector, $targetExtend->option, 0 );
|
||||
$newExtend->selfSelectors = $newSelector;
|
||||
|
||||
// add the extend onto the list of extends for that selector
|
||||
end( $newSelector )->extendList = array( $newExtend );
|
||||
// $newSelector[ count($newSelector)-1]->extendList = array($newExtend);
|
||||
|
||||
// record that we need to add it.
|
||||
$extendsToAdd[] = $newExtend;
|
||||
$newExtend->ruleset = $targetExtend->ruleset;
|
||||
|
||||
// remember its parents for circular references
|
||||
$newExtend->parent_ids = array_merge( $newExtend->parent_ids, $targetExtend->parent_ids, $extend->parent_ids );
|
||||
|
||||
// only process the selector once.. if we have :extend(.a,.b) then multiple
|
||||
// extends will look at the same selector path, so when extending
|
||||
// we know that any others will be duplicates in terms of what is added to the css
|
||||
if ( $targetExtend->firstExtendOnThisSelectorPath ) {
|
||||
$newExtend->firstExtendOnThisSelectorPath = true;
|
||||
$targetExtend->ruleset->paths[] = $newSelector;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $extendsToAdd ) {
|
||||
// try to detect circular references to stop a stack overflow.
|
||||
// may no longer be needed. $this->extendChainCount++;
|
||||
if ( $iterationCount > 100 ) {
|
||||
|
||||
try{
|
||||
$selectorOne = $extendsToAdd[0]->selfSelectors[0]->toCSS();
|
||||
$selectorTwo = $extendsToAdd[0]->selector->toCSS();
|
||||
}catch ( Exception $e ) {
|
||||
$selectorOne = "{unable to calculate}";
|
||||
$selectorTwo = "{unable to calculate}";
|
||||
}
|
||||
|
||||
throw new Less_Exception_Parser( "extend circular reference detected. One of the circular extends is currently:" . $selectorOne . ":extend(" . $selectorTwo . ")" );
|
||||
}
|
||||
|
||||
// now process the new extends on the existing rules so that we can handle a extending b extending c ectending d extending e...
|
||||
$extendsToAdd = $this->doExtendChaining( $extendsToAdd, $extendsListTarget, $iterationCount + 1 );
|
||||
}
|
||||
|
||||
return array_merge( $extendsList, $extendsToAdd );
|
||||
}
|
||||
|
||||
protected function visitRule( $ruleNode, &$visitDeeper ) {
|
||||
$visitDeeper = false;
|
||||
}
|
||||
|
||||
protected function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ) {
|
||||
$visitDeeper = false;
|
||||
}
|
||||
|
||||
protected function visitSelector( $selectorNode, &$visitDeeper ) {
|
||||
$visitDeeper = false;
|
||||
}
|
||||
|
||||
protected function visitRuleset( $rulesetNode ) {
|
||||
if ( $rulesetNode->root ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$allExtends = end( $this->allExtendsStack );
|
||||
$paths_len = count( $rulesetNode->paths );
|
||||
|
||||
// look at each selector path in the ruleset, find any extend matches and then copy, find and replace
|
||||
foreach ( $allExtends as $allExtend ) {
|
||||
for ( $pathIndex = 0; $pathIndex < $paths_len; $pathIndex++ ) {
|
||||
|
||||
// extending extends happens initially, before the main pass
|
||||
if ( isset( $rulesetNode->extendOnEveryPath ) && $rulesetNode->extendOnEveryPath ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$selectorPath = $rulesetNode->paths[$pathIndex];
|
||||
|
||||
if ( end( $selectorPath )->extendList ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->ExtendMatch( $rulesetNode, $allExtend, $selectorPath );
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function ExtendMatch( $rulesetNode, $extend, $selectorPath ) {
|
||||
$matches = $this->findMatch( $extend, $selectorPath );
|
||||
|
||||
if ( $matches ) {
|
||||
foreach ( $extend->selfSelectors as $selfSelector ) {
|
||||
$rulesetNode->paths[] = $this->extendSelector( $matches, $selectorPath, $selfSelector );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function findMatch( $extend, $haystackSelectorPath ) {
|
||||
if ( !$this->HasMatches( $extend, $haystackSelectorPath ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// look through the haystack selector path to try and find the needle - extend.selector
|
||||
// returns an array of selector matches that can then be replaced
|
||||
//
|
||||
$needleElements = $extend->selector->elements;
|
||||
$potentialMatches = array();
|
||||
$potentialMatches_len = 0;
|
||||
$potentialMatch = null;
|
||||
$matches = array();
|
||||
|
||||
// loop through the haystack elements
|
||||
$haystack_path_len = count( $haystackSelectorPath );
|
||||
for ( $haystackSelectorIndex = 0; $haystackSelectorIndex < $haystack_path_len; $haystackSelectorIndex++ ) {
|
||||
$hackstackSelector = $haystackSelectorPath[$haystackSelectorIndex];
|
||||
|
||||
$haystack_elements_len = count( $hackstackSelector->elements );
|
||||
for ( $hackstackElementIndex = 0; $hackstackElementIndex < $haystack_elements_len; $hackstackElementIndex++ ) {
|
||||
|
||||
$haystackElement = $hackstackSelector->elements[$hackstackElementIndex];
|
||||
|
||||
// if we allow elements before our match we can add a potential match every time. otherwise only at the first element.
|
||||
if ( $extend->allowBefore || ( $haystackSelectorIndex === 0 && $hackstackElementIndex === 0 ) ) {
|
||||
$potentialMatches[] = array( 'pathIndex' => $haystackSelectorIndex, 'index' => $hackstackElementIndex, 'matched' => 0, 'initialCombinator' => $haystackElement->combinator );
|
||||
$potentialMatches_len++;
|
||||
}
|
||||
|
||||
for ( $i = 0; $i < $potentialMatches_len; $i++ ) {
|
||||
|
||||
$potentialMatch = &$potentialMatches[$i];
|
||||
$potentialMatch = $this->PotentialMatch( $potentialMatch, $needleElements, $haystackElement, $hackstackElementIndex );
|
||||
|
||||
// if we are still valid and have finished, test whether we have elements after and whether these are allowed
|
||||
if ( $potentialMatch && $potentialMatch['matched'] === $extend->selector->elements_len ) {
|
||||
$potentialMatch['finished'] = true;
|
||||
|
||||
if ( !$extend->allowAfter && ( $hackstackElementIndex + 1 < $haystack_elements_len || $haystackSelectorIndex + 1 < $haystack_path_len ) ) {
|
||||
$potentialMatch = null;
|
||||
}
|
||||
}
|
||||
|
||||
// if null we remove, if not, we are still valid, so either push as a valid match or continue
|
||||
if ( $potentialMatch ) {
|
||||
if ( $potentialMatch['finished'] ) {
|
||||
$potentialMatch['length'] = $extend->selector->elements_len;
|
||||
$potentialMatch['endPathIndex'] = $haystackSelectorIndex;
|
||||
$potentialMatch['endPathElementIndex'] = $hackstackElementIndex + 1; // index after end of match
|
||||
$potentialMatches = array(); // we don't allow matches to overlap, so start matching again
|
||||
$potentialMatches_len = 0;
|
||||
$matches[] = $potentialMatch;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
array_splice( $potentialMatches, $i, 1 );
|
||||
$potentialMatches_len--;
|
||||
$i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
// Before going through all the nested loops, lets check to see if a match is possible
|
||||
// Reduces Bootstrap 3.1 compile time from ~6.5s to ~5.6s
|
||||
private function HasMatches( $extend, $haystackSelectorPath ) {
|
||||
if ( !$extend->selector->cacheable ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$first_el = $extend->selector->_oelements[0];
|
||||
|
||||
foreach ( $haystackSelectorPath as $hackstackSelector ) {
|
||||
if ( !$hackstackSelector->cacheable ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Optimisation: Explicit reference, <https://github.com/wikimedia/less.php/pull/14>
|
||||
if ( \array_key_exists( $first_el, $hackstackSelector->_oelements_assoc ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param integer $hackstackElementIndex
|
||||
*/
|
||||
private function PotentialMatch( $potentialMatch, $needleElements, $haystackElement, $hackstackElementIndex ) {
|
||||
if ( $potentialMatch['matched'] > 0 ) {
|
||||
|
||||
// selectors add " " onto the first element. When we use & it joins the selectors together, but if we don't
|
||||
// then each selector in haystackSelectorPath has a space before it added in the toCSS phase. so we need to work out
|
||||
// what the resulting combinator will be
|
||||
$targetCombinator = $haystackElement->combinator;
|
||||
if ( $targetCombinator === '' && $hackstackElementIndex === 0 ) {
|
||||
$targetCombinator = ' ';
|
||||
}
|
||||
|
||||
if ( $needleElements[ $potentialMatch['matched'] ]->combinator !== $targetCombinator ) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// if we don't match, null our match to indicate failure
|
||||
if ( !$this->isElementValuesEqual( $needleElements[$potentialMatch['matched'] ]->value, $haystackElement->value ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$potentialMatch['finished'] = false;
|
||||
$potentialMatch['matched']++;
|
||||
|
||||
return $potentialMatch;
|
||||
}
|
||||
|
||||
private function isElementValuesEqual( $elementValue1, $elementValue2 ) {
|
||||
if ( $elementValue1 === $elementValue2 ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( is_string( $elementValue1 ) || is_string( $elementValue2 ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $elementValue1 instanceof Less_Tree_Attribute ) {
|
||||
return $this->isAttributeValuesEqual( $elementValue1, $elementValue2 );
|
||||
}
|
||||
|
||||
$elementValue1 = $elementValue1->value;
|
||||
if ( $elementValue1 instanceof Less_Tree_Selector ) {
|
||||
return $this->isSelectorValuesEqual( $elementValue1, $elementValue2 );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Less_Tree_Selector $elementValue1
|
||||
*/
|
||||
private function isSelectorValuesEqual( $elementValue1, $elementValue2 ) {
|
||||
$elementValue2 = $elementValue2->value;
|
||||
if ( !( $elementValue2 instanceof Less_Tree_Selector ) || $elementValue1->elements_len !== $elementValue2->elements_len ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for ( $i = 0; $i < $elementValue1->elements_len; $i++ ) {
|
||||
|
||||
if ( $elementValue1->elements[$i]->combinator !== $elementValue2->elements[$i]->combinator ) {
|
||||
if ( $i !== 0 || ( $elementValue1->elements[$i]->combinator || ' ' ) !== ( $elementValue2->elements[$i]->combinator || ' ' ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !$this->isElementValuesEqual( $elementValue1->elements[$i]->value, $elementValue2->elements[$i]->value ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Less_Tree_Attribute $elementValue1
|
||||
*/
|
||||
private function isAttributeValuesEqual( $elementValue1, $elementValue2 ) {
|
||||
if ( $elementValue1->op !== $elementValue2->op || $elementValue1->key !== $elementValue2->key ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( !$elementValue1->value || !$elementValue2->value ) {
|
||||
if ( $elementValue1->value || $elementValue2->value ) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
$elementValue1 = ( $elementValue1->value->value ? $elementValue1->value->value : $elementValue1->value );
|
||||
$elementValue2 = ( $elementValue2->value->value ? $elementValue2->value->value : $elementValue2->value );
|
||||
|
||||
return $elementValue1 === $elementValue2;
|
||||
}
|
||||
|
||||
private function extendSelector( $matches, $selectorPath, $replacementSelector ) {
|
||||
// for a set of matches, replace each match with the replacement selector
|
||||
|
||||
$currentSelectorPathIndex = 0;
|
||||
$currentSelectorPathElementIndex = 0;
|
||||
$path = array();
|
||||
$selectorPath_len = count( $selectorPath );
|
||||
|
||||
for ( $matchIndex = 0, $matches_len = count( $matches ); $matchIndex < $matches_len; $matchIndex++ ) {
|
||||
|
||||
$match = $matches[$matchIndex];
|
||||
$selector = $selectorPath[ $match['pathIndex'] ];
|
||||
|
||||
$firstElement = new Less_Tree_Element(
|
||||
$match['initialCombinator'],
|
||||
$replacementSelector->elements[0]->value,
|
||||
$replacementSelector->elements[0]->index,
|
||||
$replacementSelector->elements[0]->currentFileInfo
|
||||
);
|
||||
|
||||
if ( $match['pathIndex'] > $currentSelectorPathIndex && $currentSelectorPathElementIndex > 0 ) {
|
||||
$last_path = end( $path );
|
||||
$last_path->elements = array_merge( $last_path->elements, array_slice( $selectorPath[$currentSelectorPathIndex]->elements, $currentSelectorPathElementIndex ) );
|
||||
$currentSelectorPathElementIndex = 0;
|
||||
$currentSelectorPathIndex++;
|
||||
}
|
||||
|
||||
$newElements = array_merge(
|
||||
array_slice( $selector->elements, $currentSelectorPathElementIndex, ( $match['index'] - $currentSelectorPathElementIndex ) ), // last parameter of array_slice is different than the last parameter of javascript's slice
|
||||
array( $firstElement ),
|
||||
array_slice( $replacementSelector->elements, 1 )
|
||||
);
|
||||
|
||||
if ( $currentSelectorPathIndex === $match['pathIndex'] && $matchIndex > 0 ) {
|
||||
$last_key = count( $path ) - 1;
|
||||
$path[$last_key]->elements = array_merge( $path[$last_key]->elements, $newElements );
|
||||
} else {
|
||||
$path = array_merge( $path, array_slice( $selectorPath, $currentSelectorPathIndex, $match['pathIndex'] ) );
|
||||
$path[] = new Less_Tree_Selector( $newElements );
|
||||
}
|
||||
|
||||
$currentSelectorPathIndex = $match['endPathIndex'];
|
||||
$currentSelectorPathElementIndex = $match['endPathElementIndex'];
|
||||
if ( $currentSelectorPathElementIndex >= count( $selectorPath[$currentSelectorPathIndex]->elements ) ) {
|
||||
$currentSelectorPathElementIndex = 0;
|
||||
$currentSelectorPathIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $currentSelectorPathIndex < $selectorPath_len && $currentSelectorPathElementIndex > 0 ) {
|
||||
$last_path = end( $path );
|
||||
$last_path->elements = array_merge( $last_path->elements, array_slice( $selectorPath[$currentSelectorPathIndex]->elements, $currentSelectorPathElementIndex ) );
|
||||
$currentSelectorPathIndex++;
|
||||
}
|
||||
|
||||
$slice_len = $selectorPath_len - $currentSelectorPathIndex;
|
||||
$path = array_merge( $path, array_slice( $selectorPath, $currentSelectorPathIndex, $slice_len ) );
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
protected function visitMedia( $mediaNode ) {
|
||||
$newAllExtends = array_merge( $mediaNode->allExtends, end( $this->allExtendsStack ) );
|
||||
$this->allExtendsStack[] = $this->doExtendChaining( $newAllExtends, $mediaNode->allExtends );
|
||||
}
|
||||
|
||||
protected function visitMediaOut() {
|
||||
array_pop( $this->allExtendsStack );
|
||||
}
|
||||
|
||||
protected function visitDirective( $directiveNode ) {
|
||||
$newAllExtends = array_merge( $directiveNode->allExtends, end( $this->allExtendsStack ) );
|
||||
$this->allExtendsStack[] = $this->doExtendChaining( $newAllExtends, $directiveNode->allExtends );
|
||||
}
|
||||
|
||||
protected function visitDirectiveOut() {
|
||||
array_pop( $this->allExtendsStack );
|
||||
}
|
||||
|
||||
}
|
280
less.php/lib/Less/Visitor/toCSS.php
Normal file
280
less.php/lib/Less/Visitor/toCSS.php
Normal file
@ -0,0 +1,280 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* toCSS Visitor
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage visitor
|
||||
*/
|
||||
class Less_Visitor_toCSS extends Less_VisitorReplacing {
|
||||
|
||||
private $charset;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Less_Tree_Ruleset $root
|
||||
*/
|
||||
public function run( $root ) {
|
||||
return $this->visitObj( $root );
|
||||
}
|
||||
|
||||
public function visitRule( $ruleNode ) {
|
||||
if ( $ruleNode->variable ) {
|
||||
return array();
|
||||
}
|
||||
return $ruleNode;
|
||||
}
|
||||
|
||||
public function visitMixinDefinition( $mixinNode ) {
|
||||
// mixin definitions do not get eval'd - this means they keep state
|
||||
// so we have to clear that state here so it isn't used if toCSS is called twice
|
||||
$mixinNode->frames = array();
|
||||
return array();
|
||||
}
|
||||
|
||||
public function visitExtend() {
|
||||
return array();
|
||||
}
|
||||
|
||||
public function visitComment( $commentNode ) {
|
||||
if ( $commentNode->isSilent() ) {
|
||||
return array();
|
||||
}
|
||||
return $commentNode;
|
||||
}
|
||||
|
||||
public function visitMedia( $mediaNode, &$visitDeeper ) {
|
||||
$mediaNode->accept( $this );
|
||||
$visitDeeper = false;
|
||||
|
||||
if ( !$mediaNode->rules ) {
|
||||
return array();
|
||||
}
|
||||
return $mediaNode;
|
||||
}
|
||||
|
||||
public function visitDirective( $directiveNode ) {
|
||||
if ( isset( $directiveNode->currentFileInfo['reference'] ) && ( !property_exists( $directiveNode, 'isReferenced' ) || !$directiveNode->isReferenced ) ) {
|
||||
return array();
|
||||
}
|
||||
if ( $directiveNode->name === '@charset' ) {
|
||||
// Only output the debug info together with subsequent @charset definitions
|
||||
// a comment (or @media statement) before the actual @charset directive would
|
||||
// be considered illegal css as it has to be on the first line
|
||||
if ( isset( $this->charset ) && $this->charset ) {
|
||||
|
||||
// if( $directiveNode->debugInfo ){
|
||||
// $comment = new Less_Tree_Comment('/* ' . str_replace("\n",'',$directiveNode->toCSS())." */\n");
|
||||
// $comment->debugInfo = $directiveNode->debugInfo;
|
||||
// return $this->visit($comment);
|
||||
//}
|
||||
|
||||
return array();
|
||||
}
|
||||
$this->charset = true;
|
||||
}
|
||||
return $directiveNode;
|
||||
}
|
||||
|
||||
public function checkPropertiesInRoot( $rulesetNode ) {
|
||||
if ( !$rulesetNode->firstRoot ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $rulesetNode->rules as $ruleNode ) {
|
||||
if ( $ruleNode instanceof Less_Tree_Rule && !$ruleNode->variable ) {
|
||||
$msg = "properties must be inside selector blocks, they cannot be in the root. Index ".$ruleNode->index.( $ruleNode->currentFileInfo ? ( ' Filename: '.$ruleNode->currentFileInfo['filename'] ) : null );
|
||||
throw new Less_Exception_Compiler( $msg );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function visitRuleset( $rulesetNode, &$visitDeeper ) {
|
||||
$visitDeeper = false;
|
||||
|
||||
$this->checkPropertiesInRoot( $rulesetNode );
|
||||
|
||||
if ( $rulesetNode->root ) {
|
||||
return $this->visitRulesetRoot( $rulesetNode );
|
||||
}
|
||||
|
||||
$rulesets = array();
|
||||
$rulesetNode->paths = $this->visitRulesetPaths( $rulesetNode );
|
||||
|
||||
// Compile rules and rulesets
|
||||
$nodeRuleCnt = $rulesetNode->rules ? count( $rulesetNode->rules ) : 0;
|
||||
for ( $i = 0; $i < $nodeRuleCnt; ) {
|
||||
$rule = $rulesetNode->rules[$i];
|
||||
|
||||
if ( property_exists( $rule, 'rules' ) ) {
|
||||
// visit because we are moving them out from being a child
|
||||
$rulesets[] = $this->visitObj( $rule );
|
||||
array_splice( $rulesetNode->rules, $i, 1 );
|
||||
$nodeRuleCnt--;
|
||||
continue;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
|
||||
// accept the visitor to remove rules and refactor itself
|
||||
// then we can decide now whether we want it or not
|
||||
if ( $nodeRuleCnt > 0 ) {
|
||||
$rulesetNode->accept( $this );
|
||||
|
||||
if ( $rulesetNode->rules ) {
|
||||
|
||||
if ( count( $rulesetNode->rules ) > 1 ) {
|
||||
$this->_mergeRules( $rulesetNode->rules );
|
||||
$this->_removeDuplicateRules( $rulesetNode->rules );
|
||||
}
|
||||
|
||||
// now decide whether we keep the ruleset
|
||||
if ( $rulesetNode->paths ) {
|
||||
// array_unshift($rulesets, $rulesetNode);
|
||||
array_splice( $rulesets, 0, 0, array( $rulesetNode ) );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( count( $rulesets ) === 1 ) {
|
||||
return $rulesets[0];
|
||||
}
|
||||
return $rulesets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for visitiRuleset
|
||||
*
|
||||
* return array|Less_Tree_Ruleset
|
||||
*/
|
||||
private function visitRulesetRoot( $rulesetNode ) {
|
||||
$rulesetNode->accept( $this );
|
||||
if ( $rulesetNode->firstRoot || $rulesetNode->rules ) {
|
||||
return $rulesetNode;
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for visitRuleset()
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function visitRulesetPaths( $rulesetNode ) {
|
||||
$paths = array();
|
||||
foreach ( $rulesetNode->paths as $p ) {
|
||||
if ( $p[0]->elements[0]->combinator === ' ' ) {
|
||||
$p[0]->elements[0]->combinator = '';
|
||||
}
|
||||
|
||||
foreach ( $p as $pi ) {
|
||||
if ( $pi->getIsReferenced() && $pi->getIsOutput() ) {
|
||||
$paths[] = $p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $paths;
|
||||
}
|
||||
|
||||
protected function _removeDuplicateRules( &$rules ) {
|
||||
// remove duplicates
|
||||
$ruleCache = array();
|
||||
for ( $i = count( $rules ) - 1; $i >= 0; $i-- ) {
|
||||
$rule = $rules[$i];
|
||||
if ( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_NameValue ) {
|
||||
|
||||
if ( !isset( $ruleCache[$rule->name] ) ) {
|
||||
$ruleCache[$rule->name] = $rule;
|
||||
} else {
|
||||
$ruleList =& $ruleCache[$rule->name];
|
||||
|
||||
if ( $ruleList instanceof Less_Tree_Rule || $ruleList instanceof Less_Tree_NameValue ) {
|
||||
$ruleList = $ruleCache[$rule->name] = array( $ruleCache[$rule->name]->toCSS() );
|
||||
}
|
||||
|
||||
$ruleCSS = $rule->toCSS();
|
||||
if ( array_search( $ruleCSS, $ruleList ) !== false ) {
|
||||
array_splice( $rules, $i, 1 );
|
||||
} else {
|
||||
$ruleList[] = $ruleCSS;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function _mergeRules( &$rules ) {
|
||||
$groups = array();
|
||||
|
||||
// obj($rules);
|
||||
|
||||
$rules_len = count( $rules );
|
||||
for ( $i = 0; $i < $rules_len; $i++ ) {
|
||||
$rule = $rules[$i];
|
||||
|
||||
if ( ( $rule instanceof Less_Tree_Rule ) && $rule->merge ) {
|
||||
|
||||
$key = $rule->name;
|
||||
if ( $rule->important ) {
|
||||
$key .= ',!';
|
||||
}
|
||||
|
||||
if ( !isset( $groups[$key] ) ) {
|
||||
$groups[$key] = array();
|
||||
} else {
|
||||
array_splice( $rules, $i--, 1 );
|
||||
$rules_len--;
|
||||
}
|
||||
|
||||
$groups[$key][] = $rule;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $groups as $parts ) {
|
||||
|
||||
if ( count( $parts ) > 1 ) {
|
||||
$rule = $parts[0];
|
||||
$spacedGroups = array();
|
||||
$lastSpacedGroup = array();
|
||||
$parts_mapped = array();
|
||||
foreach ( $parts as $p ) {
|
||||
if ( $p->merge === '+' ) {
|
||||
if ( $lastSpacedGroup ) {
|
||||
$spacedGroups[] = self::toExpression( $lastSpacedGroup );
|
||||
}
|
||||
$lastSpacedGroup = array();
|
||||
}
|
||||
$lastSpacedGroup[] = $p;
|
||||
}
|
||||
|
||||
$spacedGroups[] = self::toExpression( $lastSpacedGroup );
|
||||
$rule->value = self::toValue( $spacedGroups );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static function toExpression( $values ) {
|
||||
$mapped = array();
|
||||
foreach ( $values as $p ) {
|
||||
$mapped[] = $p->value;
|
||||
}
|
||||
return new Less_Tree_Expression( $mapped );
|
||||
}
|
||||
|
||||
public static function toValue( $values ) {
|
||||
// return new Less_Tree_Value($values); ??
|
||||
|
||||
$mapped = array();
|
||||
foreach ( $values as $p ) {
|
||||
$mapped[] = $p;
|
||||
}
|
||||
return new Less_Tree_Value( $mapped );
|
||||
}
|
||||
}
|
70
less.php/lib/Less/VisitorReplacing.php
Normal file
70
less.php/lib/Less/VisitorReplacing.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Replacing Visitor
|
||||
*
|
||||
* @package Less
|
||||
* @subpackage visitor
|
||||
*/
|
||||
class Less_VisitorReplacing extends Less_Visitor {
|
||||
|
||||
public function visitObj( $node ) {
|
||||
$funcName = 'visit'.$node->type;
|
||||
if ( isset( $this->_visitFnCache[$funcName] ) ) {
|
||||
|
||||
$visitDeeper = true;
|
||||
$node = $this->$funcName( $node, $visitDeeper );
|
||||
|
||||
if ( $node ) {
|
||||
if ( $visitDeeper && is_object( $node ) ) {
|
||||
$node->accept( $this );
|
||||
}
|
||||
|
||||
$funcName = $funcName . "Out";
|
||||
if ( isset( $this->_visitFnCache[$funcName] ) ) {
|
||||
$this->$funcName( $node );
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
$node->accept( $this );
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
public function visitArray( $nodes ) {
|
||||
$newNodes = array();
|
||||
foreach ( $nodes as $node ) {
|
||||
$evald = $this->visitObj( $node );
|
||||
if ( $evald ) {
|
||||
if ( is_array( $evald ) ) {
|
||||
self::flatten( $evald, $newNodes );
|
||||
} else {
|
||||
$newNodes[] = $evald;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $newNodes;
|
||||
}
|
||||
|
||||
public function flatten( $arr, &$out ) {
|
||||
foreach ( $arr as $item ) {
|
||||
if ( !is_array( $item ) ) {
|
||||
$out[] = $item;
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( $item as $nestedItem ) {
|
||||
if ( is_array( $nestedItem ) ) {
|
||||
self::flatten( $nestedItem, $out );
|
||||
} else {
|
||||
$out[] = $nestedItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
}
|
@ -1,661 +0,0 @@
|
||||
For ease of distribution, lessphp 0.5.1 is under a dual license.
|
||||
You are free to pick which one suits your needs.
|
||||
|
||||
|
||||
|
||||
|
||||
MIT LICENSE
|
||||
|
||||
|
||||
|
||||
|
||||
Copyright (c) 2013 - 2015 Leaf Corcoran, http://leafo.net/lessphp
|
||||
Copyright (c) 2016 - Marcus Schwarz, https://www.maswaba.de
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
|
||||
|
||||
GPL VERSION 3
|
||||
|
||||
|
||||
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
@ -1,97 +0,0 @@
|
||||
[![Build Status](https://travis-ci.org/MarcusSchwarz/lesserphp.svg)](https://travis-ci.org/MarcusSchwarz/lesserphp)
|
||||
|
||||
# lesserphp v0.5.4
|
||||
### <http://github.com/MarcusSchwarz/lesserphp>
|
||||
|
||||
`lesserphp` is a compiler for LESS written in PHP. It is based on lessphp bei leafo.
|
||||
The documentation is great,
|
||||
so check it out: <http://leafo.net/lessphp/docs/>.
|
||||
|
||||
Here's a quick tutorial:
|
||||
|
||||
### How to use in your PHP project
|
||||
|
||||
The only file required is `lessc.inc.php`, so copy that to your include directory.
|
||||
|
||||
The typical flow of **lesserphp** is to create a new instance of `lessc`,
|
||||
configure it how you like, then tell it to compile something using one built in
|
||||
compile methods.
|
||||
|
||||
The `compile` method compiles a string of LESS code to CSS.
|
||||
|
||||
```php
|
||||
<?php
|
||||
require "lessc.inc.php";
|
||||
|
||||
$less = new lessc;
|
||||
echo $less->compile(".block { padding: 3 + 4px }");
|
||||
```
|
||||
|
||||
The `compileFile` method reads and compiles a file. It will either return the
|
||||
result or write it to the path specified by an optional second argument.
|
||||
|
||||
```php
|
||||
<?php
|
||||
echo $less->compileFile("input.less");
|
||||
```
|
||||
|
||||
The `checkedCompile` method is like `compileFile`, but it only compiles if the output
|
||||
file doesn't exist or it's older than the input file:
|
||||
|
||||
```php
|
||||
<?php
|
||||
$less->checkedCompile("input.less", "output.css");
|
||||
```
|
||||
|
||||
If there any problem compiling your code, an exception is thrown with a helpful message:
|
||||
|
||||
```php
|
||||
<?php
|
||||
try {
|
||||
$less->compile("invalid LESS } {");
|
||||
} catch (\Exception $e) {
|
||||
echo "fatal error: " . $e->getMessage();
|
||||
}
|
||||
```
|
||||
|
||||
The `lessc` object can be configured through an assortment of instance methods.
|
||||
Some possible configuration options include [changing the output format][1],
|
||||
[setting variables from PHP][2], and [controlling the preservation of
|
||||
comments][3], writing [custom functions][4] and much more. It's all described
|
||||
in [the documentation][0].
|
||||
|
||||
|
||||
[0]: http://leafo.net/lessphp/docs/
|
||||
[1]: http://leafo.net/lessphp/docs/#output_formatting
|
||||
[2]: http://leafo.net/lessphp/docs/#setting_variables_from_php
|
||||
[3]: http://leafo.net/lessphp/docs/#preserving_comments
|
||||
[4]: http://leafo.net/lessphp/docs/#custom_functions
|
||||
|
||||
|
||||
### How to use from the command line
|
||||
|
||||
An additional script has been included to use the compiler from the command
|
||||
line. In the simplest invocation, you specify an input file and the compiled
|
||||
css is written to standard out:
|
||||
|
||||
$ plessc input.less > output.css
|
||||
|
||||
Using the -r flag, you can specify LESS code directly as an argument or, if
|
||||
the argument is left off, from standard in:
|
||||
|
||||
$ plessc -r "my less code here"
|
||||
|
||||
Finally, by using the -w flag you can watch a specified input file and have it
|
||||
compile as needed to the output file:
|
||||
|
||||
$ plessc -w input-file output-file
|
||||
|
||||
Errors from watch mode are written to standard out.
|
||||
|
||||
The -f flag sets the [output formatter][1]. For example, to compress the
|
||||
output run this:
|
||||
|
||||
$ plessc -f=compressed myfile.less
|
||||
|
||||
For more help, run `plessc --help`
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user