replace LESS library. fixes #4088

This replaces the abandoned fork with my own fork at splitbrain/lesserphp

That fork has been cleaned up somewhat and the issues in #4088 should be
fixed.
This commit is contained in:
Andreas Gohr 2024-02-02 15:01:14 +01:00
parent b6c5585f4e
commit e6380ba37d
38 changed files with 5573 additions and 4296 deletions

4
.gitignore vendored
View File

@ -83,10 +83,6 @@ vendor/paragonie/random_compat/build-phar.sh
vendor/paragonie/random_compat/dist/*
vendor/paragonie/random_compat/other/*
vendor/simplepie/simplepie/db.sql
vendor/marcusschwarz/lesserphp/package.sh
vendor/marcusschwarz/lesserphp/lessify*
vendor/marcusschwarz/lesserphp/Makefile
vendor/marcusschwarz/lesserphp/plessc
vendor/splitbrain/php-cli/examples/*
vendor/splitbrain/php-cli/screenshot*
vendor/splitbrain/php-cli/generate-api.sh

View File

@ -13,7 +13,7 @@
"geshi/geshi": "dev-master as 1.0.x-dev",
"openpsa/universalfeedcreator": "^1.8",
"aziraphale/email-address-validator": "^2",
"marcusschwarz/lesserphp": "^0.6",
"splitbrain/lesserphp": "^0.10",
"splitbrain/php-cli": "^1.1",
"splitbrain/slika": "^1.0",
"kissifrot/php-ixr": "^1.8",

117
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "9fdb13c8abdf5a5c4124687e6f8d3b16",
"content-hash": "34c61e4640c708005e450f6b5caa5cf8",
"packages": [
{
"name": "aziraphale/email-address-validator",
@ -150,60 +150,6 @@
},
"time": "2023-04-15T08:50:43+00:00"
},
{
"name": "marcusschwarz/lesserphp",
"version": "v0.6.0",
"source": {
"type": "git",
"url": "https://github.com/MarcusSchwarz/lesserphp.git",
"reference": "64ece57ad81ab1fe4d2a1894729e0d293fce09ef"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MarcusSchwarz/lesserphp/zipball/64ece57ad81ab1fe4d2a1894729e0d293fce09ef",
"reference": "64ece57ad81ab1fe4d2a1894729e0d293fce09ef",
"shasum": ""
},
"require": {
"php": "^7.2|^7.3|^7.4|^8.0"
},
"require-dev": {
"phpunit/phpunit": "^8.0|^9.0"
},
"bin": [
"plessc"
],
"type": "library",
"autoload": {
"classmap": [
"lessc.inc.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT",
"GPL-3.0"
],
"authors": [
{
"name": "Leaf Corcoran",
"email": "leafot@gmail.com",
"homepage": "http://leafo.net"
},
{
"name": "Marcus Schwarz",
"email": "github@maswaba.de",
"homepage": "https://www.maswaba.de"
}
],
"description": "lesserphp is a compiler for LESS written in PHP based on leafo's lessphp.",
"homepage": "https://www.maswaba.de/lesserphpdocs/",
"support": {
"issues": "https://github.com/MarcusSchwarz/lesserphp/issues",
"source": "https://github.com/MarcusSchwarz/lesserphp/tree/v0.6.0"
},
"time": "2021-03-10T19:14:23+00:00"
},
{
"name": "openpsa/universalfeedcreator",
"version": "v1.8.6",
@ -562,6 +508,67 @@
},
"time": "2023-01-20T08:37:35+00:00"
},
{
"name": "splitbrain/lesserphp",
"version": "v0.10.0",
"source": {
"type": "git",
"url": "https://github.com/splitbrain/lesserphp.git",
"reference": "2e5d20c4ce9186a34554a137a4a1784fc5c29505"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/splitbrain/lesserphp/zipball/2e5d20c4ce9186a34554a137a4a1784fc5c29505",
"reference": "2e5d20c4ce9186a34554a137a4a1784fc5c29505",
"shasum": ""
},
"require": {
"php": ">=7.4"
},
"require-dev": {
"phpunit/phpunit": "^8.5",
"rector/rector": "^0.19",
"squizlabs/php_codesniffer": "^3.8"
},
"suggest": {
"ext-fileinfo": "For mime type guessing of embedded files"
},
"type": "library",
"autoload": {
"psr-4": {
"LesserPHP\\": "src",
"LesserPHP\\tests\\": "tests"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT",
"GPL-3.0"
],
"authors": [
{
"name": "Leaf Corcoran",
"email": "leafot@gmail.com",
"homepage": "http://leafo.net"
},
{
"name": "Marcus Schwarz",
"email": "github@maswaba.de",
"homepage": "https://www.maswaba.de"
},
{
"name": "Andreas Gohr",
"email": "andi@splitbrain.org",
"homepage": "https://www.splitbrain.org"
}
],
"description": "lesserphp is a compiler for LESS written in PHP based on leafo's lessphp.",
"support": {
"issues": "https://github.com/splitbrain/lesserphp/issues",
"source": "https://github.com/splitbrain/lesserphp/tree/v0.10.0"
},
"time": "2024-02-02T11:47:26+00:00"
},
{
"name": "splitbrain/php-archive",
"version": "1.3.1",

View File

@ -208,12 +208,12 @@ function css_parseless($css)
{
global $conf;
$less = new lessc();
$less->importDir = [DOKU_INC];
$less = new LesserPHP\Lessc();
$less->setImportDir([DOKU_INC]);
$less->setPreserveComments(!$conf['compress']);
if (defined('DOKU_UNITTEST')) {
$less->importDir[] = TMP_DIR;
$less->addImportDir(TMP_DIR);
}
try {

View File

@ -28,9 +28,4 @@ return array(
'RSSCreator10' => $vendorDir . '/openpsa/universalfeedcreator/lib/Creator/RSSCreator10.php',
'RSSCreator20' => $vendorDir . '/openpsa/universalfeedcreator/lib/Creator/RSSCreator20.php',
'UniversalFeedCreator' => $vendorDir . '/openpsa/universalfeedcreator/lib/UniversalFeedCreator.php',
'lessc' => $vendorDir . '/marcusschwarz/lesserphp/lessc.inc.php',
'lessc_formatter_classic' => $vendorDir . '/marcusschwarz/lesserphp/lessc.inc.php',
'lessc_formatter_compressed' => $vendorDir . '/marcusschwarz/lesserphp/lessc.inc.php',
'lessc_formatter_lessjs' => $vendorDir . '/marcusschwarz/lesserphp/lessc.inc.php',
'lessc_parser' => $vendorDir . '/marcusschwarz/lesserphp/lessc.inc.php',
);

View File

@ -15,6 +15,8 @@ return array(
'phpseclib3\\' => array($vendorDir . '/phpseclib/phpseclib/phpseclib'),
'SimplePie\\' => array($vendorDir . '/simplepie/simplepie/src'),
'ParagonIE\\ConstantTime\\' => array($vendorDir . '/paragonie/constant_time_encoding/src'),
'LesserPHP\\tests\\' => array($vendorDir . '/splitbrain/lesserphp/tests'),
'LesserPHP\\' => array($vendorDir . '/splitbrain/lesserphp/src'),
'IXR\\tests\\' => array($vendorDir . '/kissifrot/php-ixr/tests'),
'IXR\\' => array($vendorDir . '/kissifrot/php-ixr/src'),
);

View File

@ -33,6 +33,11 @@ class ComposerStaticInita19a915ee98347a0c787119619d2ff9b
array (
'ParagonIE\\ConstantTime\\' => 23,
),
'L' =>
array (
'LesserPHP\\tests\\' => 16,
'LesserPHP\\' => 10,
),
'I' =>
array (
'IXR\\tests\\' => 10,
@ -77,6 +82,14 @@ class ComposerStaticInita19a915ee98347a0c787119619d2ff9b
array (
0 => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src',
),
'LesserPHP\\tests\\' =>
array (
0 => __DIR__ . '/..' . '/splitbrain/lesserphp/tests',
),
'LesserPHP\\' =>
array (
0 => __DIR__ . '/..' . '/splitbrain/lesserphp/src',
),
'IXR\\tests\\' =>
array (
0 => __DIR__ . '/..' . '/kissifrot/php-ixr/tests',
@ -127,11 +140,6 @@ class ComposerStaticInita19a915ee98347a0c787119619d2ff9b
'RSSCreator10' => __DIR__ . '/..' . '/openpsa/universalfeedcreator/lib/Creator/RSSCreator10.php',
'RSSCreator20' => __DIR__ . '/..' . '/openpsa/universalfeedcreator/lib/Creator/RSSCreator20.php',
'UniversalFeedCreator' => __DIR__ . '/..' . '/openpsa/universalfeedcreator/lib/UniversalFeedCreator.php',
'lessc' => __DIR__ . '/..' . '/marcusschwarz/lesserphp/lessc.inc.php',
'lessc_formatter_classic' => __DIR__ . '/..' . '/marcusschwarz/lesserphp/lessc.inc.php',
'lessc_formatter_compressed' => __DIR__ . '/..' . '/marcusschwarz/lesserphp/lessc.inc.php',
'lessc_formatter_lessjs' => __DIR__ . '/..' . '/marcusschwarz/lesserphp/lessc.inc.php',
'lessc_parser' => __DIR__ . '/..' . '/marcusschwarz/lesserphp/lessc.inc.php',
);
public static function getInitializer(ClassLoader $loader)

View File

@ -149,63 +149,6 @@
},
"install-path": "../kissifrot/php-ixr"
},
{
"name": "marcusschwarz/lesserphp",
"version": "v0.6.0",
"version_normalized": "0.6.0.0",
"source": {
"type": "git",
"url": "https://github.com/MarcusSchwarz/lesserphp.git",
"reference": "64ece57ad81ab1fe4d2a1894729e0d293fce09ef"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MarcusSchwarz/lesserphp/zipball/64ece57ad81ab1fe4d2a1894729e0d293fce09ef",
"reference": "64ece57ad81ab1fe4d2a1894729e0d293fce09ef",
"shasum": ""
},
"require": {
"php": "^7.2|^7.3|^7.4|^8.0"
},
"require-dev": {
"phpunit/phpunit": "^8.0|^9.0"
},
"time": "2021-03-10T19:14:23+00:00",
"bin": [
"plessc"
],
"type": "library",
"installation-source": "dist",
"autoload": {
"classmap": [
"lessc.inc.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT",
"GPL-3.0"
],
"authors": [
{
"name": "Leaf Corcoran",
"email": "leafot@gmail.com",
"homepage": "http://leafo.net"
},
{
"name": "Marcus Schwarz",
"email": "github@maswaba.de",
"homepage": "https://www.maswaba.de"
}
],
"description": "lesserphp is a compiler for LESS written in PHP based on leafo's lessphp.",
"homepage": "https://www.maswaba.de/lesserphpdocs/",
"support": {
"issues": "https://github.com/MarcusSchwarz/lesserphp/issues",
"source": "https://github.com/MarcusSchwarz/lesserphp/tree/v0.6.0"
},
"install-path": "../marcusschwarz/lesserphp"
},
{
"name": "openpsa/universalfeedcreator",
"version": "v1.8.6",
@ -579,6 +522,70 @@
},
"install-path": "../simplepie/simplepie"
},
{
"name": "splitbrain/lesserphp",
"version": "v0.10.0",
"version_normalized": "0.10.0.0",
"source": {
"type": "git",
"url": "https://github.com/splitbrain/lesserphp.git",
"reference": "2e5d20c4ce9186a34554a137a4a1784fc5c29505"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/splitbrain/lesserphp/zipball/2e5d20c4ce9186a34554a137a4a1784fc5c29505",
"reference": "2e5d20c4ce9186a34554a137a4a1784fc5c29505",
"shasum": ""
},
"require": {
"php": ">=7.4"
},
"require-dev": {
"phpunit/phpunit": "^8.5",
"rector/rector": "^0.19",
"squizlabs/php_codesniffer": "^3.8"
},
"suggest": {
"ext-fileinfo": "For mime type guessing of embedded files"
},
"time": "2024-02-02T11:47:26+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"LesserPHP\\": "src",
"LesserPHP\\tests\\": "tests"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT",
"GPL-3.0"
],
"authors": [
{
"name": "Leaf Corcoran",
"email": "leafot@gmail.com",
"homepage": "http://leafo.net"
},
{
"name": "Marcus Schwarz",
"email": "github@maswaba.de",
"homepage": "https://www.maswaba.de"
},
{
"name": "Andreas Gohr",
"email": "andi@splitbrain.org",
"homepage": "https://www.splitbrain.org"
}
],
"description": "lesserphp is a compiler for LESS written in PHP based on leafo's lessphp.",
"support": {
"issues": "https://github.com/splitbrain/lesserphp/issues",
"source": "https://github.com/splitbrain/lesserphp/tree/v0.10.0"
},
"install-path": "../splitbrain/lesserphp"
},
{
"name": "splitbrain/php-archive",
"version": "1.3.1",

View File

@ -3,7 +3,7 @@
'name' => 'dokuwiki/dokuwiki',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => 'e860a4fbf1abc8ba96c4cd6c00e8f6efd6510d50',
'reference' => '87f6c7185502767dd23d1039195eed23f0048dd1',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -22,7 +22,7 @@
'dokuwiki/dokuwiki' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => 'e860a4fbf1abc8ba96c4cd6c00e8f6efd6510d50',
'reference' => '87f6c7185502767dd23d1039195eed23f0048dd1',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -49,15 +49,6 @@
'aliases' => array(),
'dev_requirement' => false,
),
'marcusschwarz/lesserphp' => array(
'pretty_version' => 'v0.6.0',
'version' => '0.6.0.0',
'reference' => '64ece57ad81ab1fe4d2a1894729e0d293fce09ef',
'type' => 'library',
'install_path' => __DIR__ . '/../marcusschwarz/lesserphp',
'aliases' => array(),
'dev_requirement' => false,
),
'openpsa/universalfeedcreator' => array(
'pretty_version' => 'v1.8.6',
'version' => '1.8.6.0',
@ -103,6 +94,15 @@
'aliases' => array(),
'dev_requirement' => false,
),
'splitbrain/lesserphp' => array(
'pretty_version' => 'v0.10.0',
'version' => '0.10.0.0',
'reference' => '2e5d20c4ce9186a34554a137a4a1784fc5c29505',
'type' => 'library',
'install_path' => __DIR__ . '/../splitbrain/lesserphp',
'aliases' => array(),
'dev_requirement' => false,
),
'splitbrain/php-archive' => array(
'pretty_version' => '1.3.1',
'version' => '1.3.1.0',

View File

@ -1,97 +0,0 @@
[![Build Status](https://travis-ci.org/MarcusSchwarz/lesserphp.svg)](https://travis-ci.org/MarcusSchwarz/lesserphp)
# lesserphp v0.6.0
### <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`

View File

@ -1,36 +0,0 @@
{
"name": "marcusschwarz/lesserphp",
"type": "library",
"description": "lesserphp is a compiler for LESS written in PHP based on leafo's lessphp.",
"homepage": "https://www.maswaba.de/lesserphpdocs/",
"license": [
"MIT",
"GPL-3.0"
],
"authors": [
{
"name": "Leaf Corcoran",
"email": "leafot@gmail.com",
"homepage": "http://leafo.net"
},
{
"name": "Marcus Schwarz",
"email": "github@maswaba.de",
"homepage": "https://www.maswaba.de"
}
],
"bin": ["plessc"],
"autoload": {
"classmap": ["lessc.inc.php"]
},
"require": {
"php": "^7.2|^7.3|^7.4|^8.0"
},
"require-dev": {
"phpunit/phpunit": "^8.0|^9.0"
},
"scripts": {
"test": "phpunit"
}
}

File diff suppressed because it is too large Load Diff

13
vendor/splitbrain/lesserphp/.phpcs.xml vendored Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<ruleset>
<rule ref="PSR2">
<exclude name="Generic.ControlStructures.InlineControlStructure.NotAllowed" />
<exclude name="PSR1.Methods.CamelCapsMethodName.NotCamelCaps" />
<exclude name="PSR2.ControlStructures.ControlStructureSpacing.SpaceBeforeCloseBrace" />
<exclude name="PSR2.ControlStructures.ControlStructureSpacing.SpacingAfterOpenBrace" />
</rule>
<file>.</file>
<arg name="encoding" value="UTF-8"/>
<arg name="extensions" value="php"/>
<exclude-pattern>vendor</exclude-pattern>
</ruleset>

View File

@ -1,12 +1,16 @@
# lesserphp v0.6.0
# lesserphp (reloaded)
Originally written by Leaf Corcoran, obviously abandoned circa 2014
https://github.com/leafo/lessphp
Last version provided by Leaf was 0.5.0
### No Version yet
* 2024-01-27: Refactoring based on 0.6-dev branch and some cherry-picks from other repos
### v.0.6.0
* 2021-03-10: adds php8-support and drops php5.x-support, pushing min requirement to php7.2
(with work from @stefanvandekaa)
### v.0.5.5
* 2021-03-10: More PHP 7.4 support for 0.5-dev (@phy25 and @ArtemGoutsoul)

64
vendor/splitbrain/lesserphp/README.md vendored Normal file
View File

@ -0,0 +1,64 @@
# LesserPHP (reloaded)
LesserPHP is a compiler for LESS written in PHP. It is based on `lessphp` by [@leafo](https://github.com/leafo/lessphp). The original has been abandoned in 2014. The fork by [@MarcusSchwarz](https://github.com/MarcusSchwarz/lesserphp) has been mostly abandoned in 2021. There are other forks with dubious status.
This is an opinionated fork with the goal to modernize the code base enough to be somewhat easier to maintain without completely rewriting it. It is meant to be used as a stable base for [DokuWiki](https://www.dokuwiki.org). This means features not needed for this goal are removed.
Please note that this fork is based on the `0.6.0-dev` branch of `MarcusSchwarz/lesserphp`, not the much modernized `master` branch. This has two reasons:
1. The `master` was not up-to-date with all the bug fixes in the `0.6.0-dev` branch (some of which had been contributed by DokuWiki developers)
2. I simply only noticed the considerable refactoring Marcus had done in the `master` branch after I had already started my own refactoring. I did not want to start over again. His approach is much more radical than mine and probably took more than the long weekend I had available for this.
## Contributing and Bugs
Please report bugs to the [issue tracker](https://github.com/splitbrain/lesserphp/issues). Fixes are only likely when DokuWiki needs them, or you provide a pull request.
Feature Requests will be ignored unless accompanied by a pull request.
## How to use in your PHP project
Don't. You really wouldn't want to start a new project using LESS. It simply seems that SASS has won the battle. Or maybe even skip the whole CSS preprocessor thing - modern CSS is quite powerful on its own.
If you are already using `lessphp` in one of it's many forks, using this one isn't too different.
You can still look at the [original documentation](https://leafo.net/lessphp/docs/) for the most part. The API is mostly the same. Refer to the [upstream documentation](https://lesscss.org/features/) the [bundled Documentation](docs/docs.md) for the LESS syntax itself. Keep in mind that some more modern features are not supported by LesserPHP.
To install it, use composer:
```bash
composer require splitbrain/lesserphp
```
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 __DIR__ . '/vendor/autoload.php';
$less = new LesserPHP\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");
```
If there's any problem compiling your code, an exception is thrown with a helpful message:
```php
<?php
try {
$less->compile("invalid LESS } {");
} catch (LesserPHP\ParserException $e) {
echo $e->getMessage();
}
```

View File

@ -0,0 +1,49 @@
{
"name": "splitbrain/lesserphp",
"type": "library",
"description": "lesserphp is a compiler for LESS written in PHP based on leafo's lessphp.",
"license": [
"MIT",
"GPL-3.0"
],
"authors": [
{
"name": "Leaf Corcoran",
"email": "leafot@gmail.com",
"homepage": "http://leafo.net"
},
{
"name": "Marcus Schwarz",
"email": "github@maswaba.de",
"homepage": "https://www.maswaba.de"
},
{
"name": "Andreas Gohr",
"email": "andi@splitbrain.org",
"homepage": "https://www.splitbrain.org"
}
],
"autoload": {
"psr-4": {
"LesserPHP\\": "src",
"LesserPHP\\tests\\": "tests"
}
},
"require": {
"php": ">=7.4"
},
"suggest": {
"ext-fileinfo": "For mime type guessing of embedded files"
},
"require-dev": {
"phpunit/phpunit": "^8.5",
"squizlabs/php_codesniffer": "^3.8",
"rector/rector": "^0.19"
},
"scripts": {
"test": "phpunit --verbose",
"check": "phpcs -p -s",
"fix": "phpcbf",
"rector": "rector process"
}
}

17
vendor/splitbrain/lesserphp/rector.php vendored Normal file
View File

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\LevelSetList;
return static function (RectorConfig $rectorConfig): void {
$rectorConfig->paths([
//__DIR__ . '/tests',
__DIR__ . '/src',
]);
$rectorConfig->sets([
LevelSetList::UP_TO_PHP_74
]);
};

View File

@ -0,0 +1,181 @@
<?php
namespace LesserPHP;
class Constants
{
public const TRUE = ['keyword', 'true'];
public const FALSE = ['keyword', 'false'];
/** @var string prefix of abstract properties */
public const VPREFIX = '@';
/** @var string prefix of abstract blocks */
public const MPREFIX = '$';
/** @var string Parent Selector in LESS */
public const PARENT_SELECTOR = '&';
/** @var string Convertable lenght units */
public const LENGTH_UNITS = ['px', 'm', 'cm', 'mm', 'in', 'pt', 'pc'];
/** @var float[] Above length units converted to pixel */
public const LENGTH_BASES = [1, 3779.52755906, 37.79527559, 3.77952756, 96, 1.33333333, 16];
/** @var string time units */
public const TIME_UNITS = ['s', 'ms'];
/** @var string angle units */
public const ANGLE_UNITS = ['rad', 'deg', 'grad', 'turn'];
/** @var array Named CSS colors ["name" => "r,g,b"] */
public const CSS_COLORS = [
'aliceblue' => '240,248,255',
'antiquewhite' => '250,235,215',
'aqua' => '0,255,255',
'aquamarine' => '127,255,212',
'azure' => '240,255,255',
'beige' => '245,245,220',
'bisque' => '255,228,196',
'black' => '0,0,0',
'blanchedalmond' => '255,235,205',
'blue' => '0,0,255',
'blueviolet' => '138,43,226',
'brown' => '165,42,42',
'burlywood' => '222,184,135',
'cadetblue' => '95,158,160',
'chartreuse' => '127,255,0',
'chocolate' => '210,105,30',
'coral' => '255,127,80',
'cornflowerblue' => '100,149,237',
'cornsilk' => '255,248,220',
'crimson' => '220,20,60',
'cyan' => '0,255,255',
'darkblue' => '0,0,139',
'darkcyan' => '0,139,139',
'darkgoldenrod' => '184,134,11',
'darkgray' => '169,169,169',
'darkgreen' => '0,100,0',
'darkgrey' => '169,169,169',
'darkkhaki' => '189,183,107',
'darkmagenta' => '139,0,139',
'darkolivegreen' => '85,107,47',
'darkorange' => '255,140,0',
'darkorchid' => '153,50,204',
'darkred' => '139,0,0',
'darksalmon' => '233,150,122',
'darkseagreen' => '143,188,143',
'darkslateblue' => '72,61,139',
'darkslategray' => '47,79,79',
'darkslategrey' => '47,79,79',
'darkturquoise' => '0,206,209',
'darkviolet' => '148,0,211',
'deeppink' => '255,20,147',
'deepskyblue' => '0,191,255',
'dimgray' => '105,105,105',
'dimgrey' => '105,105,105',
'dodgerblue' => '30,144,255',
'firebrick' => '178,34,34',
'floralwhite' => '255,250,240',
'forestgreen' => '34,139,34',
'fuchsia' => '255,0,255',
'gainsboro' => '220,220,220',
'ghostwhite' => '248,248,255',
'gold' => '255,215,0',
'goldenrod' => '218,165,32',
'gray' => '128,128,128',
'green' => '0,128,0',
'greenyellow' => '173,255,47',
'grey' => '128,128,128',
'honeydew' => '240,255,240',
'hotpink' => '255,105,180',
'indianred' => '205,92,92',
'indigo' => '75,0,130',
'ivory' => '255,255,240',
'khaki' => '240,230,140',
'lavender' => '230,230,250',
'lavenderblush' => '255,240,245',
'lawngreen' => '124,252,0',
'lemonchiffon' => '255,250,205',
'lightblue' => '173,216,230',
'lightcoral' => '240,128,128',
'lightcyan' => '224,255,255',
'lightgoldenrodyellow' => '250,250,210',
'lightgray' => '211,211,211',
'lightgreen' => '144,238,144',
'lightgrey' => '211,211,211',
'lightpink' => '255,182,193',
'lightsalmon' => '255,160,122',
'lightseagreen' => '32,178,170',
'lightskyblue' => '135,206,250',
'lightslategray' => '119,136,153',
'lightslategrey' => '119,136,153',
'lightsteelblue' => '176,196,222',
'lightyellow' => '255,255,224',
'lime' => '0,255,0',
'limegreen' => '50,205,50',
'linen' => '250,240,230',
'magenta' => '255,0,255',
'maroon' => '128,0,0',
'mediumaquamarine' => '102,205,170',
'mediumblue' => '0,0,205',
'mediumorchid' => '186,85,211',
'mediumpurple' => '147,112,219',
'mediumseagreen' => '60,179,113',
'mediumslateblue' => '123,104,238',
'mediumspringgreen' => '0,250,154',
'mediumturquoise' => '72,209,204',
'mediumvioletred' => '199,21,133',
'midnightblue' => '25,25,112',
'mintcream' => '245,255,250',
'mistyrose' => '255,228,225',
'moccasin' => '255,228,181',
'navajowhite' => '255,222,173',
'navy' => '0,0,128',
'oldlace' => '253,245,230',
'olive' => '128,128,0',
'olivedrab' => '107,142,35',
'orange' => '255,165,0',
'orangered' => '255,69,0',
'orchid' => '218,112,214',
'palegoldenrod' => '238,232,170',
'palegreen' => '152,251,152',
'paleturquoise' => '175,238,238',
'palevioletred' => '219,112,147',
'papayawhip' => '255,239,213',
'peachpuff' => '255,218,185',
'peru' => '205,133,63',
'pink' => '255,192,203',
'plum' => '221,160,221',
'powderblue' => '176,224,230',
'purple' => '128,0,128',
'red' => '255,0,0',
'rosybrown' => '188,143,143',
'royalblue' => '65,105,225',
'saddlebrown' => '139,69,19',
'salmon' => '250,128,114',
'sandybrown' => '244,164,96',
'seagreen' => '46,139,87',
'seashell' => '255,245,238',
'sienna' => '160,82,45',
'silver' => '192,192,192',
'skyblue' => '135,206,235',
'slateblue' => '106,90,205',
'slategray' => '112,128,144',
'slategrey' => '112,128,144',
'snow' => '255,250,250',
'springgreen' => '0,255,127',
'steelblue' => '70,130,180',
'tan' => '210,180,140',
'teal' => '0,128,128',
'thistle' => '216,191,216',
'tomato' => '255,99,71',
'transparent' => '0,0,0,0',
'turquoise' => '64,224,208',
'violet' => '238,130,238',
'wheat' => '245,222,179',
'white' => '255,255,255',
'whitesmoke' => '245,245,245',
'yellow' => '255,255,0',
'yellowgreen' => '154,205,50'
];
}

View File

@ -0,0 +1,114 @@
<?php
/**
* http://leafo.net/lessphp
*
* LESS CSS compiler, adapted from http://lesscss.org
*
* Copyright 2013, Leaf Corcoran <leafot@gmail.com>
* Copyright 2016, Marcus Schwarz <github@maswaba.de>
* Licensed under MIT or GPLv3, see LICENSE
*/
namespace LesserPHP;
class FormatterClassic
{
public $indentChar = ' ';
public $break = "\n";
public $open = ' {';
public $close = '}';
public $selectorSeparator = ', ';
public $assignSeparator = ':';
public $openSingle = ' { ';
public $closeSingle = ' }';
public $disableSingle = false;
public $breakSelectors = false;
public $compressColors = false;
protected int $indentLevel;
public function __construct()
{
$this->indentLevel = 0;
}
public function indentStr($n = 0)
{
return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));
}
public function property($name, $value)
{
return $name . $this->assignSeparator . $value . ';';
}
protected function isEmpty($block)
{
if (empty($block->lines)) {
foreach ($block->children as $child) {
if (!$this->isEmpty($child)) return false;
}
return true;
}
return false;
}
public function block($block)
{
if ($this->isEmpty($block)) return;
$inner = $pre = $this->indentStr();
$isSingle = !$this->disableSingle &&
is_null($block->type) && count($block->lines) == 1;
if (!empty($block->selectors)) {
$this->indentLevel++;
if ($this->breakSelectors) {
$selectorSeparator = $this->selectorSeparator . $this->break . $pre;
} else {
$selectorSeparator = $this->selectorSeparator;
}
echo $pre .
implode($selectorSeparator, $block->selectors);
if ($isSingle) {
echo $this->openSingle;
$inner = '';
} else {
echo $this->open . $this->break;
$inner = $this->indentStr();
}
}
if (!empty($block->lines)) {
$glue = $this->break . $inner;
echo $inner . implode($glue, $block->lines);
if (!$isSingle && !empty($block->children)) {
echo $this->break;
}
}
foreach ($block->children as $child) {
$this->block($child);
}
if (!empty($block->selectors)) {
if (!$isSingle && empty($block->children)) echo $this->break;
if ($isSingle) {
echo $this->closeSingle . $this->break;
} else {
echo $pre . $this->close . $this->break;
}
$this->indentLevel--;
}
}
}

View File

@ -0,0 +1,28 @@
<?php
/**
* http://leafo.net/lessphp
*
* LESS CSS compiler, adapted from http://lesscss.org
*
* Copyright 2013, Leaf Corcoran <leafot@gmail.com>
* Copyright 2016, Marcus Schwarz <github@maswaba.de>
* Licensed under MIT or GPLv3, see LICENSE
*/
namespace LesserPHP;
class FormatterCompressed extends FormatterClassic
{
public $disableSingle = true;
public $open = '{';
public $selectorSeparator = ',';
public $assignSeparator = ':';
public $break = '';
public $compressColors = true;
public function indentStr($n = 0)
{
return '';
}
}

View File

@ -0,0 +1,21 @@
<?php
/**
* http://leafo.net/lessphp
*
* LESS CSS compiler, adapted from http://lesscss.org
*
* Copyright 2013, Leaf Corcoran <leafot@gmail.com>
* Copyright 2016, Marcus Schwarz <github@maswaba.de>
* Licensed under MIT or GPLv3, see LICENSE
*/
namespace LesserPHP;
class FormatterLessJs extends FormatterClassic
{
public $disableSingle = true;
public $breakSelectors = true;
public $assignSeparator = ': ';
public $selectorSeparator = ',';
}

View File

@ -0,0 +1,25 @@
<?php
namespace LesserPHP\Functions;
use LesserPHP\Lessc;
abstract class AbstractFunctionCollection
{
protected Lessc $lessc;
/**
* Constructor
*/
public function __construct(Lessc $lessc)
{
$this->lessc = $lessc;
}
/**
* Get the functions provided by this collection
*
* @return array [name => callable]
*/
abstract public function getFunctions(): array;
}

View File

@ -0,0 +1,132 @@
<?php
namespace LesserPHP\Functions;
use Exception;
use LesserPHP\Utils\Asserts;
use LesserPHP\Utils\Color;
/**
* Implementation of the Color Channel functions for LESS
*
* @link https://lesscss.org/functions/#color-channels
*/
class ColorChannels extends AbstractFunctionCollection
{
/** @inheritdoc */
public function getFunctions(): array
{
return [
'hue' => [$this, 'hue'],
'saturation' => [$this, 'saturation'],
'lightness' => [$this, 'lightness'],
//'hsvhue' => [$this, 'hsvhue'],
//'hsvsaturation' => [$this, 'hsvsaturation'],
//'hsvvalue' => [$this, 'hsvvalue'],
'red' => [$this, 'red'],
'green' => [$this, 'green'],
'blue' => [$this, 'blue'],
'alpha' => [$this, 'alpha'],
'luma' => [$this, 'luma'],
//'luminance' => [$this, 'luminance'],
];
}
/**
* Extracts the hue channel of a color object in the HSL color space
*
* @link https://lesscss.org/functions/#color-channel-hue
* @throws Exception
*/
public function hue(array $color): int
{
$hsl = Color::toHSL(Asserts::assertColor($color));
return round($hsl[1]);
}
/**
* Extracts the saturation channel of a color object in the HSL color space
*
* @link https://lesscss.org/functions/#color-channel-saturation
* @throws Exception
*/
public function saturation(array $color): int
{
$hsl = Color::toHSL(Asserts::assertColor($color));
return round($hsl[2]);
}
/**
* Extracts the lightness channel of a color object in the HSL color space
*
* @link https://lesscss.org/functions/#color-channel-lightness
* @throws Exception
*/
public function lightness(array $color): int
{
$hsl = Color::toHSL(Asserts::assertColor($color));
return round($hsl[3]);
}
// hsvhue is missing
// hsvsaturation is missing
// hsvvalue is missing
/**
* @throws Exception
*/
public function red($color)
{
$color = Asserts::assertColor($color);
return $color[1];
}
/**
* @throws Exception
*/
public function green($color)
{
$color = Asserts::assertColor($color);
return $color[2];
}
/**
* @throws Exception
*/
public function blue($color)
{
$color = Asserts::assertColor($color);
return $color[3];
}
/**
* Extracts the alpha channel of a color object
*
* defaults to 1 for colors without an alpha
* @fixme non-colors return null - should they?
* @link https://lesscss.org/functions/#color-channel-alpha
*/
public function alpha(array $value): ?float
{
if (!is_null($color = Color::coerceColor($value))) {
return $color[4] ?? 1;
}
return null;
}
/**
* Calculates the luma (perceptual brightness) of a color object
*
* @link https://lesscss.org/functions/#color-channel-luma
* @throws Exception
*/
public function luma(array $color): array
{
$color = Asserts::assertColor($color);
return ['number', round(Color::toLuma($color) * 100, 8), '%'];
}
// luminance is missing
}

View File

@ -0,0 +1,71 @@
<?php
namespace LesserPHP\Functions;
use Exception;
use LesserPHP\Utils\Asserts;
/**
* Implements the color definition functions of LESS
*
* @link https://lesscss.org/functions/#color-definition
*/
class ColorDefinition extends AbstractFunctionCollection
{
/** @inheritdoc */
public function getFunctions(): array
{
return [
//'rgb' => [$this, 'rgb'],
//'rgba' => [$this, 'rgba'],
'rgbahex' => [$this, 'rgbahex'],
'argb' => [$this, 'argb'],
//'hsl' => [$this, 'hsl'],
//'hsla' => [$this, 'hsla'],
//'hsv' => [$this, 'hsv'],
//'hsva' => [$this, 'hsva'],
];
}
// rgb is missing
// rgba is missing
/**
* Creates a hex representation of a color in #AARRGGBB format (NOT #RRGGBBAA!)
*
* This method does not exist in the official less.js implementation
* @see lib_argb
* @throws Exception
*/
public function rgbahex(array $color): string
{
$color = Asserts::assertColor($color);
return sprintf(
'#%02x%02x%02x%02x',
isset($color[4]) ? $color[4] * 255 : 255,
$color[1],
$color[2],
$color[3]
);
}
/**
* Creates a hex representation of a color in #AARRGGBB format (NOT #RRGGBBAA!)
*
* @https://lesscss.org/functions/#color-definition-argb
* @throws Exception
*/
public function argb(array $color): string
{
return $this->rgbahex($color);
}
// hsl is missing
// hsla is missing
// hsv is missing
// hsva is missing
}

View File

@ -0,0 +1,315 @@
<?php
namespace LesserPHP\Functions;
use Exception;
use LesserPHP\Utils\Asserts;
use LesserPHP\Utils\Color;
use LesserPHP\Utils\Util;
/**
* Implements the Color Operation functions for LESS
*
* @todo inheritance from ColorChannels is only until we figure out how the alpha() method should work
* @link https://lesscss.org/functions/#color-operations
*/
class ColorOperation extends ColorChannels
{
/** @inheritdoc */
public function getFunctions(): array
{
return [
'saturate' => [$this, 'saturate'],
'desaturate' => [$this, 'desaturate'],
'lighten' => [$this, 'lighten'],
'darken' => [$this, 'darken'],
'fadein' => [$this, 'fadein'],
'fadeout' => [$this, 'fadeout'],
'fade' => [$this, 'fade'],
'spin' => [$this, 'spin'],
'mix' => [$this, 'mix'],
'tint' => [$this, 'tint'],
'shade' => [$this, 'shade'],
//'greyscale' => [$this, 'greyscale'],
'contrast' => [$this, 'contrast'],
];
}
/**
* Increase the saturation of a color in the HSL color space by an absolute amount
*
* @link https://lesscss.org/functions/#color-operations-saturate
* @throws Exception
*/
public function saturate(array $args): array
{
[$color, $delta] = $this->colorArgs($args);
$hsl = Color::toHSL($color);
$hsl[2] = Util::clamp($hsl[2] + $delta, 100);
return Color::toRGB($hsl);
}
/**
* Decrease the saturation of a color in the HSL color space by an absolute amount
*
* @link https://lesscss.org/functions/#color-operations-desaturate
* @throws Exception
*/
public function desaturate(array $args): array
{
[$color, $delta] = $this->colorArgs($args);
$hsl = Color::toHSL($color);
$hsl[2] = Util::clamp($hsl[2] - $delta, 100);
return Color::toRGB($hsl);
}
/**
* Increase the lightness of a color in the HSL color space by an absolute amount
*
* @link https://lesscss.org/functions/#color-operations-lighten
* @throws Exception
*/
public function lighten(array $args): array
{
[$color, $delta] = $this->colorArgs($args);
$hsl = Color::toHSL($color);
$hsl[3] = Util::clamp($hsl[3] + $delta, 100);
return Color::toRGB($hsl);
}
/**
* Decrease the lightness of a color in the HSL color space by an absolute amount
*
* @link https://lesscss.org/functions/#color-operations-darken
* @throws Exception
*/
public function darken(array $args): array
{
[$color, $delta] = $this->colorArgs($args);
$hsl = Color::toHSL($color);
$hsl[3] = Util::clamp($hsl[3] - $delta, 100);
return Color::toRGB($hsl);
}
/**
* Decrease the transparency (or increase the opacity) of a color, making it more opaque
*
* @link https://lesscss.org/functions/#color-operations-fadein
* @throws Exception
*/
public function fadein(array $args): array
{
[$color, $delta] = $this->colorArgs($args);
$color[4] = Util::clamp(($color[4] ?? 1) + $delta / 100);
return $color;
}
/**
* Increase the transparency (or decrease the opacity) of a color, making it less opaque
*
* @link https://lesscss.org/functions/#color-operations-fadeout
* @throws Exception
*/
public function fadeout(array $args): array
{
[$color, $delta] = $this->colorArgs($args);
$color[4] = Util::clamp(($color[4] ?? 1) - $delta / 100);
return $color;
}
/**
* Set the absolute opacity of a color.
* Can be applied to colors whether they already have an opacity value or not.
*
* @link https://lesscss.org/functions/#color-operations-fade
* @throws Exception
*/
public function fade(array $args): array
{
[$color, $alpha] = $this->colorArgs($args);
$color[4] = Util::clamp($alpha / 100.0);
return $color;
}
/**
* Rotate the hue angle of a color in either direction
*
* @link https://lesscss.org/functions/#color-operations-spin
* @throws Exception
*/
public function spin(array $args): array
{
[$color, $delta] = $this->colorArgs($args);
$hsl = Color::toHSL($color);
$hsl[1] = $hsl[1] + $delta % 360;
if ($hsl[1] < 0) $hsl[1] += 360;
return Color::toRGB($hsl);
}
/**
* mixes two colors by weight
* mix(@color1, @color2, [@weight: 50%]);
*
* @link https://lesscss.org/functions/#color-operations-mix
* @throws Exception
*/
public function mix(array $args): array
{
if ($args[0] != 'list' || count($args[2]) < 2) {
throw new Exception('mix expects (color1, color2, weight)');
}
[$first, $second] = $args[2];
$first = Asserts::assertColor($first);
$second = Asserts::assertColor($second);
$first_a = $this->alpha($first);
$second_a = $this->alpha($second);
if (isset($args[2][2])) {
$weight = $args[2][2][1] / 100.0;
} else {
$weight = 0.5;
}
$w = $weight * 2 - 1;
$a = $first_a - $second_a;
$w1 = (($w * $a == -1 ? $w : ($w + $a) / (1 + $w * $a)) + 1) / 2.0;
$w2 = 1.0 - $w1;
$new = [
'color',
$w1 * $first[1] + $w2 * $second[1],
$w1 * $first[2] + $w2 * $second[2],
$w1 * $first[3] + $w2 * $second[3],
];
if ($first_a != 1.0 || $second_a != 1.0) {
$new[] = $first_a * $weight + $second_a * ($weight - 1);
}
return Color::fixColor($new);
}
/**
* Mix color with white in variable proportion.
*
* It is the same as calling `mix(#ffffff, @color, @weight)`.
*
* tint(@color, [@weight: 50%]);
*
* @link https://lesscss.org/functions/#color-operations-tint
* @throws Exception
* @return array Color
*/
public function tint(array $args): array
{
$white = ['color', 255, 255, 255];
if ($args[0] == 'color') {
return $this->mix(['list', ',', [$white, $args]]);
} elseif ($args[0] == 'list' && count($args[2]) == 2) {
return $this->mix([$args[0], $args[1], [$white, $args[2][0], $args[2][1]]]);
} else {
throw new Exception('tint expects (color, weight)');
}
}
/**
* Mix color with black in variable proportion.
*
* It is the same as calling `mix(#000000, @color, @weight)`
*
* shade(@color, [@weight: 50%]);
*
* @link http://lesscss.org/functions/#color-operations-shade
* @return array Color
* @throws Exception
*/
public function shade(array $args): array
{
$black = ['color', 0, 0, 0];
if ($args[0] == 'color') {
return $this->mix(['list', ',', [$black, $args]]);
} elseif ($args[0] == 'list' && count($args[2]) == 2) {
return $this->mix([$args[0], $args[1], [$black, $args[2][0], $args[2][1]]]);
} else {
throw new Exception('shade expects (color, weight)');
}
}
// greyscale is missing
/**
* Choose which of two colors provides the greatest contrast with another
*
* @link https://lesscss.org/functions/#color-operations-contrast
* @throws Exception
*/
public function contrast(array $args): array
{
$darkColor = ['color', 0, 0, 0];
$lightColor = ['color', 255, 255, 255];
$threshold = 0.43;
if ($args[0] == 'list') {
$inputColor = (isset($args[2][0])) ? Asserts::assertColor($args[2][0]) : $lightColor;
$darkColor = (isset($args[2][1])) ? Asserts::assertColor($args[2][1]) : $darkColor;
$lightColor = (isset($args[2][2])) ? Asserts::assertColor($args[2][2]) : $lightColor;
if (isset($args[2][3])) {
if (isset($args[2][3][2]) && $args[2][3][2] == '%') {
$args[2][3][1] /= 100;
unset($args[2][3][2]);
}
$threshold = Asserts::assertNumber($args[2][3]);
}
} else {
$inputColor = Asserts::assertColor($args);
}
$inputColor = Color::coerceColor($inputColor);
$darkColor = Color::coerceColor($darkColor);
$lightColor = Color::coerceColor($lightColor);
//Figure out which is actually light and dark!
if (Color::toLuma($darkColor) > Color::toLuma($lightColor)) {
$t = $lightColor;
$lightColor = $darkColor;
$darkColor = $t;
}
$inputColor_alpha = $this->alpha($inputColor);
if ((Color::toLuma($inputColor) * $inputColor_alpha) < $threshold) {
return $lightColor;
}
return $darkColor;
}
/**
* Helper function to get arguments for color manipulation functions.
* takes a list that contains a color like thing and a percentage
*
* @fixme explanation needs to be improved
* @throws Exception
*/
protected function colorArgs(array $args): array
{
if ($args[0] != 'list' || count($args[2]) < 2) {
return [['color', 0, 0, 0], 0];
}
[$color, $delta] = $args[2];
$color = Asserts::assertColor($color);
$delta = floatval($delta[1]);
return [$color, $delta];
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace LesserPHP\Functions;
use Exception;
use LesserPHP\Lessc;
use LesserPHP\Utils\Asserts;
/**
* Implements the list functions for LESS
*
* @link https://lesscss.org/functions/#list-functions
*/
class Lists extends AbstractFunctionCollection
{
/** @inheritdoc */
public function getFunctions(): array
{
return [
//'length' => [$this, 'length'],
'extract' => [$this, 'extract'],
//'range' => [$this, 'range'],
//'each' => [$this, 'each'],
];
}
// length is missing
/**
* Returns the value at a specified position in a list
*
* @link https://lesscss.org/functions/#list-functions-extract
* @throws Exception
*/
public function extract(array $value)
{
[$list, $idx] = Asserts::assertArgs($value, 2, 'extract');
$idx = Asserts::assertNumber($idx);
// 1 indexed
if ($list[0] == 'list' && isset($list[2][$idx - 1])) {
return $list[2][$idx - 1];
}
// FIXME what is the expected behavior here? Apparently it's not an error?
}
// range is missing
// each is missing
}

View File

@ -0,0 +1,274 @@
<?php
namespace LesserPHP\Functions;
use Exception;
use LesserPHP\Constants;
use LesserPHP\Utils\Asserts;
use LesserPHP\Utils\Util;
/**
* Implements the math functions for LESS
*
* @link https://lesscss.org/functions/#math-functions
*/
class Math extends AbstractFunctionCollection
{
/** @inheritdoc */
public function getFunctions(): array
{
return [
'ceil' => [$this, 'ceil'],
'floor' => [$this, 'floor'],
'percentage' => [$this, 'percentage'],
'round' => [$this, 'round'],
'sqrt' => [$this, 'sqrt'],
'abs' => [$this, 'abs'],
'sin' => [$this, 'sin'],
'asin' => [$this, 'asin'],
'cos' => [$this, 'cos'],
'acos' => [$this, 'acos'],
'tan' => [$this, 'tan'],
'atan' => [$this, 'atan'],
'pi' => [$this, 'pi'],
'pow' => [$this, 'pow'],
'mod' => [$this, 'mod'],
'min' => [$this, 'min'],
'max' => [$this, 'max'],
];
}
/**
* Rounds up to the next highest integer
*
* @link https://lesscss.org/functions/#math-functions-ceil
* @throws Exception
*/
public function ceil(array $arg): array
{
$value = Asserts::assertNumber($arg);
return ['number', ceil($value), $arg[2]];
}
/**
* Rounds down to the next lowest integer
*
* @link https://lesscss.org/functions/#math-functions-floor
* @throws Exception
*/
public function floor(array $arg): array
{
$value = Asserts::assertNumber($arg);
return ['number', floor($value), $arg[2]];
}
/**
* Converts a floating point number into a percentage string
*
* @link https://lesscss.org/functions/#math-functions-percentage
* @throws Exception
*/
public function percentage(array $arg): array
{
$num = Asserts::assertNumber($arg);
return ['number', $num * 100, '%'];
}
/**
* Applies rounding
*
* @link https://lesscss.org/functions/#math-functions-round
* @throws Exception
*/
public function round(array $arg): array
{
if ($arg[0] != 'list') {
$value = Asserts::assertNumber($arg);
return ['number', round($value), $arg[2]];
} else {
$value = Asserts::assertNumber($arg[2][0]);
$precision = Asserts::assertNumber($arg[2][1]);
return ['number', round($value, $precision), $arg[2][0][2]];
}
}
/**
* Calculates square root of a number
*
* @link https://lesscss.org/functions/#math-functions-sqrt
* @throws Exception
*/
public function sqrt(array $num): float
{
return sqrt(Asserts::assertNumber($num));
}
/**
* Calculates absolute value of a number. Keeps units as they are.
*
* @link https://lesscss.org/functions/#math-functions-abs
* @throws Exception
*/
public function abs(array $num): array
{
return ['number', abs(Asserts::assertNumber($num)), $num[2]];
}
/**
* Calculates sine function
*
* @link https://lesscss.org/functions/#math-functions-sin
* @throws Exception
*/
public function sin(array $num): float
{
return sin(Asserts::assertNumber($num));
}
/**
* Calculates arcsine function
*
* @link https://lesscss.org/functions/#math-functions-asin
* @throws Exception
*/
public function asin(array $num): array
{
$num = asin(Asserts::assertNumber($num));
return ['number', $num, 'rad'];
}
/**
* Calculates cosine function
*
* @link https://lesscss.org/functions/#math-functions-cos
* @throws Exception
*/
public function cos(array $num): float
{
return cos(Asserts::assertNumber($num));
}
/**
* Calculates arccosine function
*
* @link https://lesscss.org/functions/#math-functions-acos
* @throws Exception
*/
public function acos(array $num): array
{
$num = acos(Asserts::assertNumber($num));
return ['number', $num, 'rad'];
}
/**
* Calculates tangent function
*
* @link https://lesscss.org/functions/#math-functions-tan
* @throws Exception
*/
public function tan(array $num): float
{
return tan(Asserts::assertNumber($num));
}
/**
* Calculates arctangent function
*
* @link https://lesscss.org/functions/#math-functions-atan
* @throws Exception
*/
public function atan(array $num): array
{
$num = atan(Asserts::assertNumber($num));
return ['number', $num, 'rad'];
}
/**
* Return the value of pi
*
* @link https://lesscss.org/functions/#math-functions-pi
*/
public function pi(): float
{
return pi();
}
/**
* Returns the value of the first argument raised to the power of the second argument.
*
* @link https://lesscss.org/functions/#math-functions-pow
* @throws Exception
*/
public function pow(array $args): array
{
[$base, $exp] = Asserts::assertArgs($args, 2, 'pow');
return ['number', Asserts::assertNumber($base) ** Asserts::assertNumber($exp), $args[2][0][2]];
}
/**
* Returns the value of the first argument modulus second argument.
*
* @link https://lesscss.org/functions/#math-functions-mod
* @throws Exception
*/
public function mod(array $args): array
{
[$a, $b] = Asserts::assertArgs($args, 2, 'mod');
return ['number', Asserts::assertNumber($a) % Asserts::assertNumber($b), $args[2][0][2]];
}
/**
* Returns the lowest of one or more values
*
* @link https://lesscss.org/functions/#math-functions-min
* @throws Exception
*/
public function min(array $args): array
{
$values = Asserts::assertMinArgs($args, 1, 'min');
$first_format = $values[0][2];
$min_index = 0;
$min_value = $values[0][1];
for ($a = 0; $a < sizeof($values); $a++) {
$converted = Util::convert($values[$a], $first_format);
if ($converted[1] < $min_value) {
$min_index = $a;
$min_value = $values[$a][1];
}
}
return $values[$min_index];
}
/**
* Returns the highest of one or more values
*
* @link https://lesscss.org/functions/#math-functions-max
* @throws Exception
*/
public function max(array $args): array
{
$values = Asserts::assertMinArgs($args, 1, 'max');
$first_format = $values[0][2];
$max_index = 0;
$max_value = $values[0][1];
for ($a = 0; $a < sizeof($values); $a++) {
$converted = Util::convert($values[$a], $first_format);
if ($converted[1] > $max_value) {
$max_index = $a;
$max_value = $values[$a][1];
}
}
return $values[$max_index];
}
}

View File

@ -0,0 +1,115 @@
<?php
namespace LesserPHP\Functions;
use Exception;
use LesserPHP\Utils\Asserts;
use LesserPHP\Utils\Util;
/**
* Implements the miscellaneous functions for LESS
*
* @link https://lesscss.org/functions/#misc-functions
*/
class Misc extends AbstractFunctionCollection
{
/** @inheritdoc */
public function getFunctions(): array
{
return [
//'color' => [$this, 'color'],
//'image-size' => [$this, 'imageSize'],
//'image-width' => [$this, 'imageWidth'],
//'image-height' => [$this, 'imageHeight'],
'convert' => [$this, 'convert'],
'data-uri' => [$this, 'dataUri'],
//'default' => [$this, 'default'],
'unit' => [$this, 'unit'],
//'get-unit' => [$this, 'getUnit'],
//'svg-gradient' => [$this, 'svgGradient'],
];
}
// color is missing
// image-size is missing
// image-width is missing
// image-height is missing
/**
* Convert a number from one unit into another
*
* @link https://lesscss.org/functions/#misc-functions-convert
* @throws Exception
*/
public function convert(array $args): array
{
[$value, $to] = Asserts::assertArgs($args, 2, 'convert');
// If it's a keyword, grab the string version instead
if (is_array($to) && $to[0] == 'keyword') {
$to = $to[1];
}
return Util::convert($value, $to);
}
/**
* Given an url, decide whether to output a regular link or the base64-encoded contents of the file
*
* @param array $value either an argument list (two strings) or a single string
* @return string formatted url(), either as a link or base64-encoded
*/
public function dataUri(array $value): string
{
$mime = ($value[0] === 'list') ? $value[2][0][2] : null;
$url = ($value[0] === 'list') ? $value[2][1][2][0] : $value[2][0];
$fullpath = $this->lessc->findImport($url);
if ($fullpath && ($fsize = filesize($fullpath)) !== false) {
// IE8 can't handle data uris larger than 32KB
if ($fsize / 1024 < 32) {
if (is_null($mime)) {
if (class_exists('finfo')) { // php 5.3+
$finfo = new \finfo(FILEINFO_MIME);
$mime = explode('; ', $finfo->file($fullpath));
$mime = $mime[0];
} elseif (function_exists('mime_content_type')) { // PHP 5.2
$mime = mime_content_type($fullpath);
}
}
if (!is_null($mime)) // fallback if the mime type is still unknown
$url = sprintf('data:%s;base64,%s', $mime, base64_encode(file_get_contents($fullpath)));
}
}
return 'url("' . $url . '")';
}
// default is missing
/**
* Remove or change the unit of a dimension
*
* @link https://lesscss.org/functions/#misc-functions-unit
* @throws Exception
*/
public function unit(array $arg): array
{
if ($arg[0] == 'list') {
[$number, $newUnit] = $arg[2];
return [
'number',
Asserts::assertNumber($number),
$this->lessc->compileValue($this->lessc->unwrap($newUnit))
];
} else {
return ['number', Asserts::assertNumber($arg), ''];
}
}
// get-unit is missing
// svg-gradient is missing
}

View File

@ -0,0 +1,85 @@
<?php
namespace LesserPHP\Functions;
use Exception;
use LesserPHP\Utils\Color;
use LesserPHP\Utils\Util;
/**
* Implements the string functions for LESS
*
* @link https://lesscss.org/functions/#string-functions
*/
class Strings extends AbstractFunctionCollection
{
/** @inheritdoc */
public function getFunctions(): array
{
return [
//'escape' => [$this, 'escape'],
'e' => [$this, 'e'],
'%' => [$this, 'format'],
//'replace' => [$this, 'replace'],
];
}
// escape is missing
/**
* String escaping.
*
* It expects string as a parameter and return its content as is, but without quotes. It can be used
* to output CSS value which is either not valid CSS syntax, or uses proprietary syntax which
* Less doesn't recognize.
*
* @link https://lesscss.org/functions/#string-functions-e
* @throws Exception
*/
public function e(array $arg): array
{
return $this->lessc->unwrap($arg);
}
/**
* Formats a string
*
* @link https://lesscss.org/functions/#string-functions--format
* @throws Exception
*/
public function format(array $args) : array
{
if ($args[0] != 'list') return $args;
$values = $args[2];
$string = array_shift($values);
$template = $this->lessc->compileValue($this->lessc->unwrap($string));
$i = 0;
if (preg_match_all('/%[dsa]/', $template, $m)) {
foreach ($m[0] as $match) {
$val = isset($values[$i]) ?
$this->lessc->reduce($values[$i]) : ['keyword', ''];
// lessjs compat, renders fully expanded color, not raw color
if ($color = Color::coerceColor($val)) {
$val = $color;
}
$i++;
$rep = $this->lessc->compileValue($this->lessc->unwrap($val));
$template = preg_replace(
'/' . Util::pregQuote($match) . '/',
$rep,
$template,
1
);
}
}
$d = $string[0] == 'string' ? $string[1] : '"';
return ['string', $d, [$template]];
}
// replace is missing
}

View File

@ -0,0 +1,120 @@
<?php
namespace LesserPHP\Functions;
use LesserPHP\Utils\Color;
use LesserPHP\Utils\Util;
/**
* Implements the type functions for LESS
*
* @link https://lesscss.org/functions/#type-functions
*/
class Type extends AbstractFunctionCollection
{
/** @inheritdoc */
public function getFunctions(): array
{
return [
'isnumber' => [$this, 'isnumber'],
'isstring' => [$this, 'isstring'],
'iscolor' => [$this, 'iscolor'],
'iskeyword' => [$this, 'iskeyword'],
//'isurl' => [$this, 'isurl'],
'ispixel' => [$this, 'ispixel'],
'isem' => [$this, 'isem'],
'isrem' => [$this, 'isrem'],
'ispercentage' => [$this, 'ispercentage'],
//'isunit' => [$this, 'isunit'],
//'isruleset' => [$this, 'isruleset'],
//'isdefined' => [$this, 'isdefined'],
];
}
/**
* Returns true if a value is a number, false otherwise
*
* @link https://lesscss.org/functions/#type-functions-isnumber
*/
public function isnumber(array $value): array
{
return Util::toBool($value[0] == 'number');
}
/**
* Returns true if a value is a string, false otherwise
*
* @link https://lesscss.org/functions/#type-functions-isstring
*/
public function isstring(array $value): array
{
return Util::toBool($value[0] == 'string');
}
/**
* Returns true if a value is a color, false otherwise
*
* @link https://lesscss.org/functions/#type-functions-iscolor
*/
public function iscolor(array $value): array
{
return Util::toBool(Color::coerceColor($value));
}
/**
* Returns true if a value is a keyword, false otherwise
*
* @link https://lesscss.org/functions/#type-functions-iskeyword
*/
public function iskeyword(array $value): array
{
return Util::toBool($value[0] == 'keyword');
}
// isurl is missing
/**
* Returns true if a value is a number in pixels, false otherwise
*
* @link https://lesscss.org/functions/#type-functions-ispixel
*/
public function ispixel(array $value): array
{
return Util::toBool($value[0] == 'number' && $value[2] == 'px');
}
/**
* Returns true if a value is an em value, false otherwise
*
* @link https://lesscss.org/functions/#type-functions-isem
*/
public function isem(array $value): array
{
return Util::toBool($value[0] == 'number' && $value[2] == 'em');
}
/**
* Returns true if a value is an rem value, false otherwise
*
* This method does not exist in the official less.js implementation
*/
public function isrem(array $value): array
{
return Util::toBool($value[0] == 'number' && $value[2] == 'rem');
}
/**
* Returns true if a value is a percentage, false otherwise
*
* @link https://lesscss.org/functions/#type-functions-ispercentage
*/
public function ispercentage(array $value): array
{
return Util::toBool($value[0] == 'number' && $value[2] == '%');
}
// isunit is missing
// isruleset is missing
// isdefined is missing
}

1746
vendor/splitbrain/lesserphp/src/Lessc.php vendored Normal file

File diff suppressed because it is too large Load Diff

1519
vendor/splitbrain/lesserphp/src/Parser.php vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,77 @@
<?php
namespace LesserPHP;
/**
* An exception signalling a problem in the LESS source
*/
class ParserException extends \Exception
{
protected string $error = '';
protected string $culprit = '';
protected string $sourceFile = '';
protected int $sourceLine = -1;
public function __construct(
string $message = '',
?string $culprit = '',
?string $sourceFile = '',
?int $sourceLine = -1,
\Throwable $previous = null
) {
$this->error = $message;
if ($culprit) {
$this->culprit = $culprit;
$message .= " `$culprit`";
}
if ($sourceFile) {
$this->sourceFile = $sourceFile;
$message .= " in $sourceFile";
}
if ($sourceLine !== null && $sourceLine > -1) {
$this->sourceLine = $sourceLine;
$message .= " line: $sourceLine";
}
parent::__construct($message, 0, $previous);
}
/**
* This is the error message without any additional context
*/
public function getError(): string
{
return $this->error;
}
/**
* The LESS code that triggered the error
*
* This is the line the parser choked on. Not always available.
*/
public function getCulprit(): string
{
return $this->culprit;
}
/**
* The LESS source file where the error was triggered
*
* This is the file the parser was parsing, will usually only be available when
* parsing an import or when compileFile() was used.
*/
public function getSourceFile(): string
{
return $this->sourceFile;
}
/**
* The line number where the error was triggered
*/
public function getSourceLine(): int
{
return $this->sourceLine;
}
}

View File

@ -0,0 +1,81 @@
<?php
namespace LesserPHP\Utils;
use Exception;
class Asserts
{
/**
* Check that the number of arguments is correct and return them
*
* @throws Exception
*/
public static function assertArgs($value, $expectedArgs, $name = '')
{
if ($expectedArgs == 1) {
return $value;
} else {
if ($value[0] !== 'list' || $value[1] != ',') {
throw new Exception('expecting list');
}
$values = $value[2];
$numValues = count($values);
if ($expectedArgs != $numValues) {
if ($name) {
$name = $name . ': ';
}
throw new Exception("{$name}expecting $expectedArgs arguments, got $numValues");
}
return $values;
}
}
/**
* Check that the number of arguments is at least the expected number and return them
*
* @throws Exception
*/
public static function assertMinArgs($value, $expectedMinArgs, $name = '')
{
if ($value[0] !== 'list' || $value[1] != ',') {
throw new Exception('expecting list');
}
$values = $value[2];
$numValues = count($values);
if ($expectedMinArgs > $numValues) {
if ($name) {
$name = $name . ': ';
}
throw new Exception("${name}expecting at least $expectedMinArgs arguments, got $numValues");
}
return $values;
}
/**
* Checks that the value is a number and returns it as float
*
* @param array $value The parsed value triplet
* @param string $error The error message to throw
* @throws Exception
*/
public static function assertNumber(array $value, string $error = 'expecting number'): float
{
if ($value[0] == 'number') return (float)$value[1];
throw new Exception($error);
}
/**
* @throws Exception
*/
public static function assertColor(array $value, $error = 'expected color value'): array
{
$color = Color::coerceColor($value);
if (is_null($color)) throw new Exception($error);
return $color;
}
}

View File

@ -0,0 +1,187 @@
<?php
namespace LesserPHP\Utils;
use LesserPHP\Constants;
/**
* Color handling utilities
*/
class Color
{
/**
* coerce a value for use in color operation
* returns null if the value can't be used in color operations
*/
public static function coerceColor(array $value): ?array
{
switch ($value[0]) {
case 'color':
return $value;
case 'raw_color':
$c = ['color', 0, 0, 0];
$colorStr = substr($value[1], 1);
$num = hexdec($colorStr);
$width = strlen($colorStr) == 3 ? 16 : 256;
for ($i = 3; $i > 0; $i--) { // 3 2 1
$t = $num % $width;
$num /= $width;
$c[$i] = $t * (256 / $width) + $t * floor(16 / $width);
}
return $c;
case 'keyword':
$name = $value[1];
if (isset(Constants::CSS_COLORS[$name])) {
$rgba = explode(',', Constants::CSS_COLORS[$name]);
if (isset($rgba[3]))
return ['color', $rgba[0], $rgba[1], $rgba[2], $rgba[3]];
return ['color', $rgba[0], $rgba[1], $rgba[2]];
}
return null;
}
return null;
}
/**
* Calculate the perceptual brightness of a color object
*/
public static function toLuma(array $color): float
{
[, $r, $g, $b] = Color::coerceColor($color);
$r = $r / 255;
$g = $g / 255;
$b = $b / 255;
$r = ($r <= 0.03928) ? $r / 12.92 : (($r + 0.055) / 1.055) ** 2.4;
$g = ($g <= 0.03928) ? $g / 12.92 : (($g + 0.055) / 1.055) ** 2.4;
$b = ($b <= 0.03928) ? $b / 12.92 : (($b + 0.055) / 1.055) ** 2.4;
return (0.2126 * $r) + (0.7152 * $g) + (0.0722 * $b);
}
/**
* Convert a color to HSL color space
*/
public static function toHSL(array $color): array
{
if ($color[0] == 'hsl') return $color;
$r = $color[1] / 255;
$g = $color[2] / 255;
$b = $color[3] / 255;
$min = min($r, $g, $b);
$max = max($r, $g, $b);
$L = ($min + $max) / 2;
if ($min == $max) {
$S = $H = 0;
} else {
if ($L < 0.5) {
$S = ($max - $min) / ($max + $min);
} else {
$S = ($max - $min) / (2.0 - $max - $min);
}
if ($r == $max) {
$H = ($g - $b) / ($max - $min);
} elseif ($g == $max) {
$H = 2.0 + ($b - $r) / ($max - $min);
} elseif ($b == $max) {
$H = 4.0 + ($r - $g) / ($max - $min);
} else {
$H = 0;
}
}
$out = [
'hsl',
($H < 0 ? $H + 6 : $H) * 60,
$S * 100,
$L * 100,
];
if (count($color) > 4) $out[] = $color[4]; // copy alpha
return $out;
}
/**
* Converts a hsl array into a color value in rgb.
* Expects H to be in range of 0 to 360, S and L in 0 to 100
*/
public static function toRGB(array $color): array
{
if ($color[0] == 'color') return $color;
$H = $color[1] / 360;
$S = $color[2] / 100;
$L = $color[3] / 100;
if ($S == 0) {
$r = $g = $b = $L;
} else {
$temp2 = $L < 0.5 ?
$L * (1.0 + $S) :
$L + $S - $L * $S;
$temp1 = 2.0 * $L - $temp2;
$r = self::calculateRGBComponent($H + 1 / 3, $temp1, $temp2);
$g = self::calculateRGBComponent($H, $temp1, $temp2);
$b = self::calculateRGBComponent($H - 1 / 3, $temp1, $temp2);
}
// $out = array('color', round($r*255), round($g*255), round($b*255));
$out = ['color', $r * 255, $g * 255, $b * 255];
if (count($color) > 4) $out[] = $color[4]; // copy alpha
return $out;
}
/**
* make sure a color's components don't go out of bounds
*/
public static function fixColor(array $c): array
{
foreach (range(1, 3) as $i) {
if ($c[$i] < 0) $c[$i] = 0;
if ($c[$i] > 255) $c[$i] = 255;
}
return $c;
}
/**
* Helper function for the HSL to RGB conversion process.
*
* This function normalizes the input component of the HSL color and determines the RGB
* value based on the HSL values.
*
* @param float $comp The component of the HSL color to be normalized and converted.
* @param float $temp1 The first temporary variable used in the conversion process
* @param float $temp2 The second temporary variable used in the conversion process
*
* @return float The calculated RGB value as percentage of the maximum value (255)
*/
protected static function calculateRGBComponent(float $comp, float $temp1, float $temp2): float
{
// Normalize the component value to be within the range [0, 1]
if ($comp < 0) $comp += 1.0;
elseif ($comp > 1) $comp -= 1.0;
// Determine the return value based on the value of the component
if (6 * $comp < 1) return $temp1 + ($temp2 - $temp1) * 6 * $comp;
if (2 * $comp < 1) return $temp2;
if (3 * $comp < 2) return $temp1 + ($temp2 - $temp1) * ((2 / 3) - $comp) * 6;
// Fallback return value, represents the case where the saturation of the color is zero
return $temp1;
}
}

View File

@ -0,0 +1,128 @@
<?php
namespace LesserPHP\Utils;
use Exception;
use LesserPHP\Constants;
/**
* Miscelaneous utility methods
*/
class Util
{
/**
* Clamps a value between a minimum and maximum value.
*
* This function takes a value and two boundary values (maximum and minimum).
* It ensures that the provided value does not exceed the boundaries.
* If the value is less than the minimum, the minimum is returned.
* If the value is greater than the maximum, the maximum is returned.
* Otherwise, the original value is returned.
*/
public static function clamp(float $v, float $max = 1, float $min = 0): float
{
return min($max, max($min, $v));
}
/**
* Quote a string for use in a regular expression
*/
public static function pregQuote(string $what): string
{
return preg_quote($what, '/');
}
/**
* Return a boolean type
*
* @param mixed $a
*/
public static function toBool($a): array
{
if ($a) return Constants::TRUE;
return Constants::FALSE;
}
/**
* Converts numbers between different types of units
*
* @throws Exception
*/
public static function convert($number, $to): array
{
$value = Asserts::assertNumber($number);
$from = $number[2];
// easy out
if ($from == $to)
return $number;
// check if the from value is a length
if (($from_index = array_search($from, Constants::LENGTH_UNITS)) !== false) {
// make sure to value is too
if (in_array($to, Constants::LENGTH_UNITS)) {
// do the actual conversion
$to_index = array_search($to, Constants::LENGTH_UNITS);
$px = $value * Constants::LENGTH_BASES[$from_index];
$result = $px * (1 / Constants::LENGTH_BASES[$to_index]);
$result = round($result, 8);
return ['number', $result, $to];
}
}
// do the same check for times
if (in_array($from, Constants::TIME_UNITS)) {
if (in_array($to, Constants::TIME_UNITS)) {
// currently only ms and s are valid
if ($to == 'ms')
$result = $value * 1000;
else $result = $value / 1000;
$result = round($result, 8);
return ['number', $result, $to];
}
}
// lastly check for an angle
if (in_array($from, Constants::ANGLE_UNITS)) {
// convert whatever angle it is into degrees
switch ($from) {
case 'rad':
$deg = rad2deg($value);
break;
case 'turn':
$deg = $value * 360;
break;
case 'grad':
$deg = $value / (400 / 360);
break;
default:
$deg = $value;
break;
}
// Then convert it from degrees into desired unit
switch ($to) {
case 'rad':
$result = deg2rad($deg);
break;
case 'turn':
$result = $deg / 360;
break;
case 'grad':
$result = $deg * (400 / 360);
break;
default:
$result = $deg;
break;
}
$result = round($result, 8);
return ['number', $result, $to];
}
// we don't know how to convert these
throw new Exception("Cannot convert $from to $to");
}
}