Css-Crush preprocessor
This commit is contained in:
parent
9a1f42908e
commit
c7e0fac35a
83 changed files with 11279 additions and 0 deletions
19
include/thirdparty/css-crush/CssCrush.php
vendored
Normal file
19
include/thirdparty/css-crush/CssCrush.php
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Bootstrap file with autoloader.
|
||||
*
|
||||
*/
|
||||
spl_autoload_register(function ($class) {
|
||||
|
||||
if (stripos($class, 'csscrush') !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$class = str_ireplace('csscrush', 'CssCrush', $class);
|
||||
$subpath = implode('/', array_map('ucfirst', explode('\\', $class)));
|
||||
|
||||
require_once __DIR__ . "/lib/$subpath.php";
|
||||
});
|
||||
|
||||
require_once 'lib/functions.php';
|
19
include/thirdparty/css-crush/LICENSE.txt
vendored
Normal file
19
include/thirdparty/css-crush/LICENSE.txt
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2010-2015 Pete Boere
|
||||
|
||||
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.
|
97
include/thirdparty/css-crush/README.md
vendored
Normal file
97
include/thirdparty/css-crush/README.md
vendored
Normal file
|
@ -0,0 +1,97 @@
|
|||
[![Build Status](https://travis-ci.org/peteboere/css-crush.svg)](https://travis-ci.org/peteboere/css-crush)
|
||||
|
||||
<img src="http://the-echoplex.net/csscrush/images/css-crush-external.svg?v=2" alt="Logo"/>
|
||||
|
||||
A CSS preprocessor designed to enable a modern and uncluttered CSS workflow.
|
||||
|
||||
* Automatic vendor prefixing
|
||||
* Variables
|
||||
* Import inlining
|
||||
* Nesting
|
||||
* Functions (color manipulation, math, data-uris etc.)
|
||||
* Rule inheritance (@extends)
|
||||
* Mixins
|
||||
* Minification
|
||||
* Lightweight plugin system
|
||||
* Source maps
|
||||
|
||||
See the [docs](http://the-echoplex.net/csscrush) for full details.
|
||||
|
||||
********************************
|
||||
|
||||
## Setup (PHP)
|
||||
|
||||
If you're using [Composer](http://getcomposer.org) you can use Crush in your project with the following line in your terminal:
|
||||
|
||||
```shell
|
||||
composer require css-crush/css-crush:dev-master
|
||||
```
|
||||
|
||||
If you're not using Composer yet just download the library into a convenient location and require the bootstrap file:
|
||||
|
||||
```php
|
||||
<?php require_once 'path/to/CssCrush.php'; ?>
|
||||
```
|
||||
|
||||
## Basic usage (PHP)
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
echo csscrush_tag('css/styles.css');
|
||||
|
||||
?>
|
||||
```
|
||||
|
||||
Compiles the CSS file and outputs the following link tag:
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="css/styles.crush.css" media="all" />
|
||||
```
|
||||
|
||||
There are several other [functions](http://the-echoplex.net/csscrush#api) for working with files and strings of CSS:
|
||||
|
||||
* `csscrush_file($file, $options)` - Returns a URL of the compiled file.
|
||||
* `csscrush_string($css, $options)` - Compiles a raw string of css and returns the resulting css.
|
||||
* `csscrush_inline($file, $options, $tag_attributes)` - Returns compiled css in an inline style tag.
|
||||
|
||||
There are a number of [options](http://the-echoplex.net/csscrush#api--options) available for tailoring the output, and a collection of bundled [plugins](http://the-echoplex.net/csscrush#plugins) that cover many workflow issues in contemporary CSS development.
|
||||
|
||||
********************************
|
||||
|
||||
## Setup (JS)
|
||||
|
||||
```shell
|
||||
npm install csscrush
|
||||
```
|
||||
|
||||
## Basic usage (JS)
|
||||
|
||||
```js
|
||||
// All methods can take the standard options (camelCase) as the second argument.
|
||||
const csscrush = require('csscrush');
|
||||
|
||||
// Compile. Returns promise.
|
||||
csscrush.file('./styles.css', {sourceMap: true});
|
||||
|
||||
// Compile string of CSS. Returns promise.
|
||||
csscrush.string('* {box-sizing: border-box;}');
|
||||
|
||||
// Compile and watch file. Returns event emitter (triggers 'data' on compile).
|
||||
csscrush.watch('./styles.css');
|
||||
```
|
||||
|
||||
********************************
|
||||
|
||||
## Contributing
|
||||
|
||||
If you think you've found a bug please create an [issue](https://github.com/peteboere/css-crush/issues) explaining the problem and expected result.
|
||||
|
||||
Likewise, if you'd like to request a feature please create an [issue](https://github.com/peteboere/css-crush/issues) with some explanation of the requested feature and use-cases.
|
||||
|
||||
[Pull requests](https://help.github.com/articles/using-pull-requests) are welcome, though please keep coding style consistent with the project (which is based on [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)).
|
||||
|
||||
|
||||
## Licence
|
||||
|
||||
MIT
|
248
include/thirdparty/css-crush/aliases.ini
vendored
Normal file
248
include/thirdparty/css-crush/aliases.ini
vendored
Normal file
|
@ -0,0 +1,248 @@
|
|||
;----------------------------------------------------------------
|
||||
;
|
||||
; Add or delete aliases to suit your needs.
|
||||
;
|
||||
; Sources:
|
||||
; http://developer.mozilla.org/en-US/docs/CSS/CSS_Reference
|
||||
; http://caniuse.com/#cats=CSS
|
||||
;
|
||||
;----------------------------------------------------------------
|
||||
|
||||
; Property aliases.
|
||||
|
||||
[properties]
|
||||
|
||||
; Animations.
|
||||
animation[] = -webkit-animation
|
||||
animation-delay[] = -webkit-animation-delay
|
||||
animation-direction[] = -webkit-animation-direction
|
||||
animation-duration[] = -webkit-animation-duration
|
||||
animation-fill-mode[] = -webkit-animation-fill-mode
|
||||
animation-iteration-count[] = -webkit-animation-iteration-count
|
||||
animation-name[] = -webkit-animation-name
|
||||
animation-play-state[] = -webkit-animation-play-state
|
||||
animation-timing-function[] = -webkit-animation-timing-function
|
||||
|
||||
; Backface visibility.
|
||||
backface-visibility[] = -webkit-backface-visibility
|
||||
|
||||
; Border-image.
|
||||
border-image[] = -webkit-border-image
|
||||
|
||||
; Box decoration.
|
||||
box-decoration-break[] = -webkit-box-decoration-break
|
||||
|
||||
; Box shadow.
|
||||
box-shadow[] = -webkit-box-shadow
|
||||
|
||||
; Box sizing.
|
||||
box-sizing[] = -webkit-box-sizing
|
||||
box-sizing[] = -moz-box-sizing
|
||||
|
||||
; Columns.
|
||||
columns[] = -webkit-columns
|
||||
column-count[] = -webkit-column-count
|
||||
column-fill[] = -webkit-column-fill
|
||||
column-gap[] = -webkit-column-gap
|
||||
column-rule[] = -webkit-column-rule
|
||||
column-rule-style[] = -webkit-column-rule-style
|
||||
column-rule-width[] = -webkit-column-rule-width
|
||||
column-rule-style[] = -webkit-column-rule-style
|
||||
column-rule-color[] = -webkit-column-rule-color
|
||||
column-span[] = -webkit-column-span
|
||||
column-width[] = -webkit-column-width
|
||||
|
||||
; Filter.
|
||||
filter[] = -webkit-filter
|
||||
|
||||
; Flexbox (2012).
|
||||
;
|
||||
; Merges two similar versions of the flexbox spec:
|
||||
; - September 2012 (for non IE): http://www.w3.org/TR/2012/CR-css3-flexbox-20120918
|
||||
; - March 2012 (for IE10): http://www.w3.org/TR/2012/WD-css3-flexbox-20120322
|
||||
;
|
||||
; The early 2012 spec mostly differs only in syntax to the later one, with the notable
|
||||
; exception of not supporting seperate properties for <flex-grow>, <flex-shrink>
|
||||
; and <flex-basis>. These properties are available in both 2012 implementations via
|
||||
; <flex> shorthand.
|
||||
;
|
||||
; Support for the early 2012 syntax implemented in IE10 is achieved here in part with
|
||||
; property aliases, and in part with declaration aliases later in this file.
|
||||
;
|
||||
align-content[] = -webkit-align-content
|
||||
align-items[] = -webkit-align-items
|
||||
align-self[] = -webkit-align-self
|
||||
flex[] = -webkit-flex
|
||||
flex[] = -ms-flex
|
||||
flex-basis[] = -webkit-flex-basis
|
||||
flex-direction[] = -webkit-flex-direction
|
||||
flex-direction[] = -ms-flex-direction
|
||||
flex-flow[] = -webkit-flex-flow
|
||||
flex-flow[] = -ms-flex-flow
|
||||
flex-grow[] = -webkit-flex-grow
|
||||
flex-shrink[] = -webkit-flex-shrink
|
||||
flex-wrap[] = -webkit-flex-wrap
|
||||
flex-wrap[] = -ms-flex-wrap
|
||||
justify-content[] = -webkit-justify-content
|
||||
order[] = -webkit-order
|
||||
order[] = -ms-flex-order
|
||||
|
||||
; Hyphens.
|
||||
hyphens[] = -webkit-hyphens
|
||||
hyphens[] = -ms-hyphens
|
||||
|
||||
; Outline radius.
|
||||
outline-radius[] = -moz-outline-radius
|
||||
outline-top-left-radius[] = -moz-outline-radius-topleft
|
||||
outline-top-right-radius[] = -moz-outline-radius-topright
|
||||
outline-bottom-left-radius[] = -moz-outline-radius-bottomleft
|
||||
outline-bottom-right-radius[] = -moz-outline-radius-bottomright
|
||||
|
||||
; Perspective.
|
||||
perspective[] = -webkit-perspective
|
||||
perspective-origin[] = -webkit-perspective-origin
|
||||
|
||||
; Shapes
|
||||
shape-image-threshold[] = -webkit-shape-image-threshold
|
||||
shape-outside[] = -webkit-shape-outside
|
||||
shape-margin[] = -webkit-shape-margin
|
||||
|
||||
; Tab size.
|
||||
tab-size[] = -moz-tab-size
|
||||
tab-size[] = -o-tab-size
|
||||
|
||||
; Text decoration.
|
||||
text-decoration-color[] = -webkit-text-decoration-color
|
||||
text-decoration-line[] = -webkit-text-decoration-line
|
||||
text-decoration-style[] = -webkit-text-decoration-style
|
||||
|
||||
; Transforms.
|
||||
transform[] = -webkit-transform
|
||||
transform[] = -ms-transform
|
||||
transform-origin[] = -webkit-transform-origin
|
||||
transform-origin[] = -ms-transform-origin
|
||||
transform-style[] = -webkit-transform-style
|
||||
transform-style[] = -ms-transform-style
|
||||
|
||||
; Transitions.
|
||||
transition[] = -webkit-transition
|
||||
transition-delay[] = -webkit-transition-delay
|
||||
transition-duration[] = -webkit-transition-duration
|
||||
transition-property[] = -webkit-transition-property
|
||||
transition-timing-function[] = -webkit-transition-timing-function
|
||||
|
||||
; User select (non standard).
|
||||
user-select[] = -webkit-user-select
|
||||
user-select[] = -moz-user-select
|
||||
user-select[] = -ms-user-select
|
||||
|
||||
|
||||
;----------------------------------------------------------------
|
||||
; Declaration aliases.
|
||||
|
||||
[declarations]
|
||||
|
||||
; Flexbox (2012).
|
||||
display:flex[] = display:-ms-flexbox
|
||||
display:flex[] = display:-webkit-flex
|
||||
display:inline-flex[] = display:-ms-inline-flexbox
|
||||
display:inline-flex[] = display:-webkit-inline-flex
|
||||
|
||||
; Flexbox (early 2012).
|
||||
align-content:flex-start[] = -ms-flex-line-pack:start
|
||||
align-content:flex-end[] = -ms-flex-line-pack:end
|
||||
align-content:center[] = -ms-flex-line-pack:center
|
||||
align-content:space-between[] = -ms-flex-line-pack:justify
|
||||
align-content:space-around[] = -ms-flex-line-pack:distribute
|
||||
align-content:stretch[] = -ms-flex-line-pack:stretch
|
||||
|
||||
align-items:flex-start[] = -ms-flex-align:start
|
||||
align-items:flex-end[] = -ms-flex-align:end
|
||||
align-items:center[] = -ms-flex-align:center
|
||||
align-items:baseline[] = -ms-flex-align:baseline
|
||||
align-items:stretch[] = -ms-flex-align:stretch
|
||||
|
||||
align-self:auto[] = -ms-flex-item-align:auto
|
||||
align-self:flex-start[] = -ms-flex-item-align:start
|
||||
align-self:flex-end[] = -ms-flex-item-align:end
|
||||
align-self:center[] = -ms-flex-item-align:center
|
||||
align-self:baseline[] = -ms-flex-item-align:baseline
|
||||
align-self:stretch[] = -ms-flex-item-align:stretch
|
||||
|
||||
justify-content:flex-start[] = -ms-flex-pack:start
|
||||
justify-content:flex-end[] = -ms-flex-pack:end
|
||||
justify-content:center[] = -ms-flex-pack:center
|
||||
justify-content:space-between[] = -ms-flex-pack:justify
|
||||
justify-content:space-around[] = -ms-flex-pack:distribute
|
||||
|
||||
; Cursor values (non-standard).
|
||||
cursor:zoom-in[] = cursor:-webkit-zoom-in
|
||||
cursor:zoom-out[] = cursor:-webkit-zoom-out
|
||||
cursor:grab[] = cursor:-webkit-grab
|
||||
cursor:grabbing[] = cursor:-webkit-grabbing
|
||||
|
||||
; Experimental width values.
|
||||
width:max-content[] = width:intrinsic
|
||||
width:max-content[] = width:-webkit-max-content
|
||||
width:max-content[] = width:-moz-max-content
|
||||
width:min-content[] = width:-webkit-min-content
|
||||
width:min-content[] = width:-moz-min-content
|
||||
width:available[] = width:-webkit-available
|
||||
width:available[] = width:-moz-available
|
||||
width:fit-content[] = width:-webkit-fit-content
|
||||
width:fit-content[] = width:-moz-fit-content
|
||||
|
||||
max-width:max-content[] = max-width:intrinsic
|
||||
max-width:max-content[] = max-width:-webkit-max-content
|
||||
max-width:max-content[] = max-width:-moz-max-content
|
||||
max-width:min-content[] = max-width:-webkit-min-content
|
||||
max-width:min-content[] = max-width:-moz-min-content
|
||||
max-width:available[] = max-width:-webkit-available
|
||||
max-width:available[] = max-width:-moz-available
|
||||
max-width:fit-content[] = max-width:-webkit-fit-content
|
||||
max-width:fit-content[] = max-width:-moz-fit-content
|
||||
|
||||
min-width:max-content[] = min-width:intrinsic
|
||||
min-width:max-content[] = min-width:-webkit-max-content
|
||||
min-width:max-content[] = min-width:-moz-max-content
|
||||
min-width:min-content[] = min-width:-webkit-min-content
|
||||
min-width:min-content[] = min-width:-moz-min-content
|
||||
min-width:available[] = min-width:-webkit-available
|
||||
min-width:available[] = min-width:-moz-available
|
||||
min-width:fit-content[] = min-width:-webkit-fit-content
|
||||
min-width:fit-content[] = min-width:-moz-fit-content
|
||||
|
||||
; Appearance (non-standard).
|
||||
appearance:none[] = -webkit-appearance:none
|
||||
appearance:none[] = -moz-appearance:none
|
||||
|
||||
position:sticky[] = position:-webkit-sticky
|
||||
|
||||
|
||||
;----------------------------------------------------------------
|
||||
; Function aliases.
|
||||
|
||||
[functions]
|
||||
|
||||
; Calc.
|
||||
calc[] = -webkit-calc
|
||||
|
||||
|
||||
[functions.gradients]
|
||||
|
||||
; Gradients.
|
||||
linear-gradient[] = -webkit-linear-gradient
|
||||
radial-gradient[] = -webkit-radial-gradient
|
||||
|
||||
; Repeating gradients.
|
||||
repeating-linear-gradient[] = -webkit-repeating-linear-gradient
|
||||
repeating-radial-gradient[] = -webkit-repeating-radial-gradient
|
||||
|
||||
|
||||
;----------------------------------------------------------------
|
||||
; @rule aliases.
|
||||
|
||||
[at-rules]
|
||||
|
||||
; Keyframes.
|
||||
keyframes[] = -webkit-keyframes
|
8
include/thirdparty/css-crush/bin/csscrush
vendored
Normal file
8
include/thirdparty/css-crush/bin/csscrush
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
*
|
||||
* Alias for package managers.
|
||||
*
|
||||
*/
|
||||
require __DIR__ . '/../cli.php';
|
637
include/thirdparty/css-crush/cli.php
vendored
Normal file
637
include/thirdparty/css-crush/cli.php
vendored
Normal file
|
@ -0,0 +1,637 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Command line utility.
|
||||
*
|
||||
*/
|
||||
require_once 'CssCrush.php';
|
||||
|
||||
define('STATUS_OK', 0);
|
||||
define('STATUS_ERROR', 1);
|
||||
|
||||
$version = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION;
|
||||
$requiredVersion = 5.6;
|
||||
|
||||
if ($version < $requiredVersion) {
|
||||
|
||||
stderr(["PHP version $requiredVersion or higher is required to use this tool.",
|
||||
"You are currently running PHP $version"]);
|
||||
|
||||
exit(STATUS_ERROR);
|
||||
}
|
||||
|
||||
try {
|
||||
$args = parse_args();
|
||||
}
|
||||
catch (Exception $ex) {
|
||||
|
||||
stderr(message($ex->getMessage(), ['type'=>'error']));
|
||||
|
||||
exit($ex->getCode());
|
||||
}
|
||||
|
||||
|
||||
##################################################################
|
||||
## Information options.
|
||||
|
||||
if ($args->version) {
|
||||
|
||||
stdout((string) CssCrush\Version::detect());
|
||||
|
||||
exit(STATUS_OK);
|
||||
}
|
||||
elseif ($args->help) {
|
||||
|
||||
stdout(manpage());
|
||||
|
||||
exit(STATUS_OK);
|
||||
}
|
||||
|
||||
|
||||
##################################################################
|
||||
## Resolve input.
|
||||
|
||||
$input = null;
|
||||
|
||||
if ($args->input_file) {
|
||||
|
||||
$input = file_get_contents($args->input_file);
|
||||
}
|
||||
elseif ($stdin = get_stdin_contents()) {
|
||||
|
||||
$input = $stdin;
|
||||
}
|
||||
else {
|
||||
stdout(manpage());
|
||||
|
||||
exit(STATUS_OK);
|
||||
}
|
||||
|
||||
|
||||
if ($args->watch && ! $args->input_file) {
|
||||
|
||||
stderr(message('Watch mode requires an input file.', ['type'=>'error']));
|
||||
|
||||
exit(STATUS_ERROR);
|
||||
}
|
||||
|
||||
|
||||
##################################################################
|
||||
## Resolve process options.
|
||||
|
||||
$configFile = 'crushfile.php';
|
||||
if (file_exists($configFile)) {
|
||||
$options = CssCrush\Util::readConfigFile($configFile);
|
||||
}
|
||||
else {
|
||||
$options = [];
|
||||
}
|
||||
|
||||
if ($args->pretty) {
|
||||
$options['minify'] = false;
|
||||
}
|
||||
|
||||
foreach (['boilerplate', 'formatter', 'newlines',
|
||||
'stat_dump', 'source_map', 'import_path'] as $option) {
|
||||
if ($args->$option) {
|
||||
$options[$option] = $args->$option;
|
||||
}
|
||||
}
|
||||
|
||||
if ($args->enable_plugins) {
|
||||
$options['plugins'] = parse_list($args->enable_plugins);
|
||||
}
|
||||
|
||||
if ($args->vendor_target) {
|
||||
$options['vendor_target'] = parse_list($args->vendor_target);
|
||||
}
|
||||
|
||||
if ($args->vars) {
|
||||
parse_str($args->vars, $in_vars);
|
||||
$options['vars'] = $in_vars;
|
||||
}
|
||||
|
||||
if ($args->output_file) {
|
||||
$options['output_dir'] = dirname($args->output_file);
|
||||
$options['output_file'] = basename($args->output_file);
|
||||
}
|
||||
|
||||
$options += [
|
||||
'doc_root' => getcwd(),
|
||||
'context' => $args->context,
|
||||
];
|
||||
|
||||
|
||||
##################################################################
|
||||
## Output.
|
||||
|
||||
error_reporting(0);
|
||||
|
||||
if ($args->watch) {
|
||||
|
||||
csscrush_set('config', ['io' => 'CssCrush\IO\Watch']);
|
||||
|
||||
stdout('CONTROL-C to quit.');
|
||||
|
||||
$outstandingErrors = false;
|
||||
|
||||
while (true) {
|
||||
|
||||
csscrush_file($args->input_file, $options);
|
||||
$stats = csscrush_stat();
|
||||
|
||||
$changed = $stats['compile_time'] && ! $stats['errors'];
|
||||
$errors = $stats['errors'];
|
||||
$warnings = $stats['warnings'];
|
||||
$showErrors = $errors && (! $outstandingErrors || ($outstandingErrors != $errors));
|
||||
|
||||
if ($errors) {
|
||||
if ($showErrors) {
|
||||
$outstandingErrors = $errors;
|
||||
stderr(message($errors, ['type'=>'error']));
|
||||
}
|
||||
}
|
||||
elseif ($changed) {
|
||||
$outstandingErrors = false;
|
||||
stderr(message(fmt_fileinfo($stats, 'output'), ['type'=>'write']));
|
||||
}
|
||||
|
||||
if (($showErrors || $changed) && $warnings) {
|
||||
stderr(message($warnings, ['type'=>'warning']));
|
||||
}
|
||||
|
||||
if ($changed && $args->stats) {
|
||||
stderr(message($stats, ['type'=>'stats']));
|
||||
}
|
||||
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
$stdOutput = null;
|
||||
|
||||
if ($args->input_file && isset($options['output_dir'])) {
|
||||
$options['cache'] = false;
|
||||
csscrush_file($args->input_file, $options);
|
||||
}
|
||||
else {
|
||||
$stdOutput = csscrush_string($input, $options);
|
||||
}
|
||||
|
||||
$stats = csscrush_stat();
|
||||
$errors = $stats['errors'];
|
||||
$warnings = $stats['warnings'];
|
||||
|
||||
if ($errors) {
|
||||
stderr(message($errors, ['type'=>'error']));
|
||||
|
||||
exit(STATUS_ERROR);
|
||||
}
|
||||
elseif ($args->input_file && ! empty($stats['output_filename'])) {
|
||||
stderr(message(fmt_fileinfo($stats, 'output'), ['type'=>'write']));
|
||||
}
|
||||
|
||||
if ($warnings) {
|
||||
stderr(message($warnings, ['type'=>'warning']));
|
||||
}
|
||||
|
||||
if ($args->stats) {
|
||||
stderr(message($stats, ['type'=>'stats']));
|
||||
}
|
||||
|
||||
if ($stdOutput) {
|
||||
stdout($stdOutput);
|
||||
}
|
||||
|
||||
exit(STATUS_OK);
|
||||
}
|
||||
|
||||
|
||||
##################################################################
|
||||
## Helpers.
|
||||
|
||||
function stderr($lines, $closing_newline = true) {
|
||||
|
||||
$out = implode(PHP_EOL, (array) $lines) . ($closing_newline ? PHP_EOL : '');
|
||||
fwrite(defined('TESTMODE') && TESTMODE ? STDOUT : STDERR, $out);
|
||||
}
|
||||
|
||||
function stdout($lines, $closing_newline = true) {
|
||||
|
||||
$out = implode(PHP_EOL, (array) $lines) . ($closing_newline ? PHP_EOL : '');
|
||||
fwrite(STDOUT, $out);
|
||||
}
|
||||
|
||||
function get_stdin_contents() {
|
||||
|
||||
stream_set_blocking(STDIN, 0);
|
||||
$contents = stream_get_contents(STDIN);
|
||||
stream_set_blocking(STDIN, 1);
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
function parse_list(array $option) {
|
||||
|
||||
$out = [];
|
||||
foreach ($option as $arg) {
|
||||
if (is_string($arg)) {
|
||||
foreach (preg_split('~\s*,\s*~', $arg) as $item) {
|
||||
$out[] = $item;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$out[] = $arg;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
function message($messages, $options = []) {
|
||||
|
||||
$defaults = [
|
||||
'color' => 'b',
|
||||
'label' => null,
|
||||
'indent' => false,
|
||||
'format_label' => false,
|
||||
];
|
||||
$preset = ! empty($options['type']) ? $options['type'] : null;
|
||||
switch ($preset) {
|
||||
case 'error':
|
||||
$defaults['color'] = 'r';
|
||||
$defaults['label'] = 'ERROR';
|
||||
break;
|
||||
case 'warning':
|
||||
$defaults['color'] = 'y';
|
||||
$defaults['label'] = 'WARNING';
|
||||
break;
|
||||
case 'write':
|
||||
$defaults['color'] = 'g';
|
||||
$defaults['label'] = 'WRITE';
|
||||
break;
|
||||
case 'stats':
|
||||
// Making stats concise and readable.
|
||||
$messages['input_file'] = $messages['input_path'];
|
||||
$messages['compile_time'] = round($messages['compile_time'], 5) . ' seconds';
|
||||
foreach (['input_filename', 'input_path', 'output_filename',
|
||||
'output_path', 'vars', 'errors', 'warnings'] as $key) {
|
||||
unset($messages[$key]);
|
||||
}
|
||||
ksort($messages);
|
||||
$defaults['indent'] = true;
|
||||
$defaults['format_label'] = true;
|
||||
break;
|
||||
}
|
||||
extract($options + $defaults);
|
||||
|
||||
$out = [];
|
||||
foreach ((array) $messages as $_label => $value) {
|
||||
$_label = $label ?: $_label;
|
||||
if ($format_label) {
|
||||
$_label = ucfirst(str_replace('_', ' ', $_label));
|
||||
}
|
||||
$prefix = $indent ? '└── ' : '';
|
||||
$colorUp = strtoupper($color);
|
||||
if (is_scalar($value)) {
|
||||
$out[] = colorize("<$color>$prefix<$colorUp>$_label:<$color> $value</>");
|
||||
}
|
||||
}
|
||||
return implode(PHP_EOL, $out);
|
||||
}
|
||||
|
||||
function fmt_fileinfo($stats, $type) {
|
||||
$time = round($stats['compile_time'], 3);
|
||||
return $stats[$type . '_path'] . " ({$time}s)";
|
||||
}
|
||||
|
||||
function pick(array &$arr) {
|
||||
|
||||
$args = func_get_args();
|
||||
array_shift($args);
|
||||
|
||||
foreach ($args as $key) {
|
||||
if (isset($arr[$key])) {
|
||||
// Optional values return false but we want true is argument is present.
|
||||
return is_bool($arr[$key]) ? true : $arr[$key];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function colorize($str) {
|
||||
|
||||
static $color_support;
|
||||
static $tags = [
|
||||
'<b>' => "\033[0;30m",
|
||||
'<r>' => "\033[0;31m",
|
||||
'<g>' => "\033[0;32m",
|
||||
'<y>' => "\033[0;33m",
|
||||
'<b>' => "\033[0;34m",
|
||||
'<v>' => "\033[0;35m",
|
||||
'<c>' => "\033[0;36m",
|
||||
'<w>' => "\033[0;37m",
|
||||
|
||||
'<B>' => "\033[1;30m",
|
||||
'<R>' => "\033[1;31m",
|
||||
'<G>' => "\033[1;32m",
|
||||
'<Y>' => "\033[1;33m",
|
||||
'<B>' => "\033[1;34m",
|
||||
'<V>' => "\033[1;35m",
|
||||
'<C>' => "\033[1;36m",
|
||||
'<W>' => "\033[1;37m",
|
||||
|
||||
'</>' => "\033[m",
|
||||
];
|
||||
|
||||
if (! isset($color_support)) {
|
||||
$color_support = defined('TESTMODE') && TESTMODE ? false : true;
|
||||
if (DIRECTORY_SEPARATOR == '\\') {
|
||||
$color_support = false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI');
|
||||
}
|
||||
}
|
||||
|
||||
$find = array_keys($tags);
|
||||
$replace = $color_support ? array_values($tags) : '';
|
||||
|
||||
return str_replace($find, $replace, $str);
|
||||
}
|
||||
|
||||
function get_trailing_io_args($required_value_opts) {
|
||||
|
||||
$trailing_input_file = null;
|
||||
$trailing_output_file = null;
|
||||
|
||||
// Get raw script args, shift off calling scriptname and reduce to last three.
|
||||
$trailing_args = $GLOBALS['argv'];
|
||||
array_shift($trailing_args);
|
||||
$trailing_args = array_slice($trailing_args, -3);
|
||||
|
||||
// Create patterns for detecting options.
|
||||
$required_values = implode('|', $required_value_opts);
|
||||
$value_opt_patt = "~^-{1,2}($required_values)$~";
|
||||
$other_opt_patt = "~^-{1,2}([a-z0-9\-]+)?(=|$)~ix";
|
||||
|
||||
// Step through the args.
|
||||
$filtered = [];
|
||||
for ($i = 0; $i < count($trailing_args); $i++) {
|
||||
|
||||
$current = $trailing_args[$i];
|
||||
|
||||
// If tests as a required value option, reset and skip next.
|
||||
if (preg_match($value_opt_patt, $current)) {
|
||||
$filtered = [];
|
||||
$i++;
|
||||
}
|
||||
// If it looks like any other kind of flag, or optional value option, reset.
|
||||
elseif (preg_match($other_opt_patt, $current)) {
|
||||
$filtered = [];
|
||||
}
|
||||
else {
|
||||
$filtered[] = $current;
|
||||
}
|
||||
}
|
||||
|
||||
// We're only interested in the last two values.
|
||||
$filtered = array_slice($filtered, -2);
|
||||
|
||||
switch (count($filtered)) {
|
||||
case 1:
|
||||
$trailing_input_file = $filtered[0];
|
||||
break;
|
||||
case 2:
|
||||
$trailing_input_file = $filtered[0];
|
||||
$trailing_output_file = $filtered[1];
|
||||
break;
|
||||
}
|
||||
|
||||
return [$trailing_input_file, $trailing_output_file];
|
||||
}
|
||||
|
||||
function parse_args() {
|
||||
|
||||
$required_value_opts = [
|
||||
'i|input|f|file', // Input file. Defaults to STDIN.
|
||||
'o|output', // Output file. Defaults to STDOUT.
|
||||
'E|enable|plugins',
|
||||
'D|disable',
|
||||
'vars|variables',
|
||||
'formatter',
|
||||
'vendor-target',
|
||||
'context',
|
||||
'import-path',
|
||||
'newlines',
|
||||
];
|
||||
|
||||
$optional_value_opts = [
|
||||
'b|boilerplate',
|
||||
'stat-dump',
|
||||
];
|
||||
|
||||
$flag_opts = [
|
||||
'p|pretty',
|
||||
'w|watch',
|
||||
'help',
|
||||
'version',
|
||||
'source-map',
|
||||
'stats',
|
||||
'test',
|
||||
];
|
||||
|
||||
// Create option strings for getopt().
|
||||
$short_opts = [];
|
||||
$long_opts = [];
|
||||
$join_opts = function ($opts_list, $modifier) use (&$short_opts, &$long_opts) {
|
||||
foreach ($opts_list as $opt) {
|
||||
foreach (explode('|', $opt) as $arg) {
|
||||
if (strlen($arg) === 1) {
|
||||
$short_opts[] = "$arg$modifier";
|
||||
}
|
||||
else {
|
||||
$long_opts[] = "$arg$modifier";
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
$join_opts($required_value_opts, ':');
|
||||
$join_opts($optional_value_opts, '::');
|
||||
$join_opts($flag_opts, '');
|
||||
|
||||
$opts = getopt(implode($short_opts), $long_opts);
|
||||
|
||||
$args = new stdClass();
|
||||
|
||||
// Information options.
|
||||
$args->help = isset($opts['h']) ?: isset($opts['help']);
|
||||
$args->version = isset($opts['version']);
|
||||
|
||||
// File arguments.
|
||||
$args->input_file = pick($opts, 'i', 'input', 'f', 'file');
|
||||
$args->output_file = pick($opts, 'o', 'output');
|
||||
$args->context = pick($opts, 'context');
|
||||
|
||||
// Flags.
|
||||
$args->pretty = isset($opts['p']) ?: isset($opts['pretty']);
|
||||
$args->watch = isset($opts['w']) ?: isset($opts['watch']);
|
||||
$args->source_map = isset($opts['source-map']);
|
||||
$args->stats = pick($opts, 'stats');
|
||||
define('TESTMODE', isset($opts['test']));
|
||||
|
||||
// Arguments that optionally accept a single value.
|
||||
$args->boilerplate = pick($opts, 'b', 'boilerplate');
|
||||
$args->stat_dump = pick($opts, 'stat-dump');
|
||||
|
||||
// Arguments that require a single value.
|
||||
$args->formatter = pick($opts, 'formatter');
|
||||
$args->vars = pick($opts, 'vars', 'variables');
|
||||
$args->newlines = pick($opts, 'newlines');
|
||||
|
||||
// Arguments that require a value but accept multiple values.
|
||||
$args->enable_plugins = pick($opts, 'E', 'enable', 'plugins');
|
||||
$args->vendor_target = pick($opts, 'vendor-target');
|
||||
$args->import_path = pick($opts, 'import-path');
|
||||
|
||||
// Run multiple value arguments through array cast.
|
||||
foreach (['enable_plugins', 'vendor_target'] as $arg) {
|
||||
if ($args->$arg) {
|
||||
$args->$arg = (array) $args->$arg;
|
||||
}
|
||||
}
|
||||
|
||||
// Detect trailing IO files from raw script arguments.
|
||||
list($trailing_input_file, $trailing_output_file) = get_trailing_io_args($required_value_opts);
|
||||
|
||||
// If detected apply, not overriding explicit IO file options.
|
||||
if (! $args->input_file && $trailing_input_file) {
|
||||
$args->input_file = $trailing_input_file;
|
||||
}
|
||||
if (! $args->output_file && $trailing_output_file) {
|
||||
$args->output_file = $trailing_output_file;
|
||||
}
|
||||
|
||||
if ($args->input_file) {
|
||||
$inputFile = $args->input_file;
|
||||
if (! ($args->input_file = realpath($args->input_file))) {
|
||||
throw new Exception("Input file '$inputFile' does not exist.", STATUS_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
if ($args->output_file) {
|
||||
$outDir = dirname($args->output_file);
|
||||
if (! realpath($outDir) && ! @mkdir($outDir, 0755, true)) {
|
||||
throw new Exception('Output directory does not exist and could not be created.', STATUS_ERROR);
|
||||
}
|
||||
$args->output_file = realpath($outDir) . '/' . basename($args->output_file);
|
||||
}
|
||||
|
||||
if ($args->context) {
|
||||
if (! ($args->context = realpath($args->context))) {
|
||||
throw new Exception('Context path does not exist.', STATUS_ERROR);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$args->context = $args->input_file ? dirname($args->input_file) : getcwd();
|
||||
}
|
||||
if (is_string($args->boilerplate)) {
|
||||
if (! ($args->boilerplate = realpath($args->boilerplate))) {
|
||||
throw new Exception('Boilerplate file does not exist.', STATUS_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
function manpage() {
|
||||
|
||||
$manpage = <<<TPL
|
||||
|
||||
<B>USAGE:</>
|
||||
<B>csscrush <G>[OPTIONS] <g>[input-file] [output-file]
|
||||
|
||||
<B>OPTIONS:</>
|
||||
<G>-i<g>, --input</>
|
||||
Input file. If omitted takes input from STDIN.
|
||||
|
||||
<G>-o<g>, --output</>
|
||||
Output file. If omitted prints to STDOUT.
|
||||
|
||||
<G>-p<g>, --pretty</>
|
||||
Formatted, un-minified output.
|
||||
|
||||
<G>-w<g>, --watch</>
|
||||
Watch input file for changes.
|
||||
Writes to file specified with -o option or to the input file
|
||||
directory with a '.crush.css' file extension.
|
||||
|
||||
<G>-E<g>, --plugins</>
|
||||
List of plugins (comma separated) to enable.
|
||||
|
||||
<g>--boilerplate</>
|
||||
Whether or not to output a boilerplate. Optionally accepts filepath
|
||||
to a custom boilerplate template.
|
||||
|
||||
<g>--context</>
|
||||
Filepath context for resolving relative import URLs.
|
||||
Only meaningful when taking raw input from STDIN.
|
||||
|
||||
<g>--import-path</>
|
||||
Comma separated list of additional paths to search when resolving
|
||||
relative import URLs.
|
||||
|
||||
<g>--formatter</>
|
||||
Possible values:
|
||||
'block' (default)
|
||||
Rules are block formatted.
|
||||
'single-line'
|
||||
Rules are printed in single lines.
|
||||
'padded'
|
||||
Rules are printed in single lines with right padded selectors.
|
||||
|
||||
<g>--help</>
|
||||
Display this help message.
|
||||
|
||||
<g>--newlines</>
|
||||
Force newline style on output css. Defaults to the current platform
|
||||
newline. Possible values: 'windows' (or 'win'), 'unix', 'use-platform'.
|
||||
|
||||
<g>--source-map</>
|
||||
Create a source map file (compliant with the Source Map v3 proposal).
|
||||
|
||||
<g>--stats</>
|
||||
Display post-compile stats.
|
||||
|
||||
<g>--vars</>
|
||||
Map of variable names in an http query string format.
|
||||
|
||||
<g>--vendor-target</>
|
||||
Possible values:
|
||||
'all'
|
||||
For all vendor prefixes (default).
|
||||
'none'
|
||||
For no vendor prefixing.
|
||||
'moz', 'webkit', 'ms' etc.
|
||||
Limit to a specific vendor prefix (or comma separated list).
|
||||
|
||||
<g>--version</>
|
||||
Display version number.
|
||||
|
||||
<B>EXAMPLES:</>
|
||||
# Restrict vendor prefixing.
|
||||
csscrush --pretty --vendor-target webkit -i styles.css
|
||||
|
||||
# Piped input.
|
||||
cat styles.css | csscrush --vars 'foo=black&bar=white' > alt-styles.css
|
||||
|
||||
# Linting.
|
||||
csscrush --pretty -E property-sorter -i styles.css -o linted.css
|
||||
|
||||
# Watch mode.
|
||||
csscrush --watch -i styles.css -o compiled/styles.css
|
||||
|
||||
# Using custom boilerplate template.
|
||||
csscrush --boilerplate=css/boilerplate.txt css/styles.css
|
||||
|
||||
TPL;
|
||||
|
||||
return colorize($manpage);
|
||||
}
|
3
include/thirdparty/css-crush/docs/README.md
vendored
Normal file
3
include/thirdparty/css-crush/docs/README.md
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
# CSS-Crush Documentation
|
||||
|
||||
Rendered online at http://the-echoplex.net/csscrush
|
84
include/thirdparty/css-crush/docs/api/functions.md
vendored
Normal file
84
include/thirdparty/css-crush/docs/api/functions.md
vendored
Normal file
|
@ -0,0 +1,84 @@
|
|||
<!--{
|
||||
|
||||
"title": "API functions"
|
||||
|
||||
}-->
|
||||
|
||||
## csscrush_file()
|
||||
|
||||
Process CSS file and return the compiled file URL.
|
||||
|
||||
<code>csscrush_file( string $file [, array [$options](#api--options) ] )</code>
|
||||
|
||||
|
||||
***************
|
||||
|
||||
## csscrush_tag()
|
||||
|
||||
Process CSS file and return an html `link` tag with populated href.
|
||||
|
||||
<code>csscrush_tag( string $file [, array [$options](#api--options) [, array $tag\_attributes ]] )</code>
|
||||
|
||||
|
||||
***************
|
||||
|
||||
## csscrush_inline()
|
||||
|
||||
Process CSS file and return CSS as text wrapped in html `style` tags.
|
||||
|
||||
<code>csscrush_inline( string $file [, array [$options](#api--options) [, array $tag\_attributes ]] )</code>
|
||||
|
||||
|
||||
***************
|
||||
|
||||
## csscrush_string()
|
||||
|
||||
Compile a raw string of CSS string and return it.
|
||||
|
||||
<code>csscrush_string( string $string [, array [$options](#api--options) ] )</code>
|
||||
|
||||
|
||||
***************
|
||||
|
||||
## csscrush_get()
|
||||
|
||||
Retrieve a config setting or option default.
|
||||
|
||||
`csscrush_get( string $object_name, string $property )`
|
||||
|
||||
### Parameters
|
||||
|
||||
* `$object_name` Name of object you want to inspect: 'config' or 'options'.
|
||||
* `$property`
|
||||
|
||||
|
||||
***************
|
||||
|
||||
## csscrush_set()
|
||||
|
||||
Set a config setting or option default.
|
||||
|
||||
`csscrush_set( string $object_name, mixed $settings )`
|
||||
|
||||
### Parameters
|
||||
|
||||
* `$object_name` Name of object you want to modify: 'config' or 'options'.
|
||||
* `$settings` Associative array of keys and values to set, or callable which argument is the object specified in `$object_name`.
|
||||
|
||||
|
||||
***************
|
||||
|
||||
## csscrush_plugin()
|
||||
|
||||
Register a plugin.
|
||||
|
||||
`csscrush_plugin( string $name, callable $callback )`
|
||||
|
||||
|
||||
***************
|
||||
|
||||
## csscrush_stat()
|
||||
|
||||
Get compilation stats from the most recent compiled file.
|
||||
|
||||
`csscrush_stat()`
|
103
include/thirdparty/css-crush/docs/api/options.md
vendored
Normal file
103
include/thirdparty/css-crush/docs/api/options.md
vendored
Normal file
|
@ -0,0 +1,103 @@
|
|||
<!--{
|
||||
|
||||
"title": "Options"
|
||||
|
||||
}-->
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th class="option">Option
|
||||
<th class="values">Values (default in bold)
|
||||
<th>Description
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="option">minify
|
||||
<td class="values"><b>true</b> | false | Array
|
||||
<td>Enable or disable minification. Optionally specify an array of advanced minification parameters. Currently the only advanced option is 'colors', which will compress all color values in any notation.
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="option">formatter
|
||||
<td class="values"><b>block</b> | single-line | padded
|
||||
<td>Set the formatting mode. Overrides minify option if both are set.
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="option">newlines
|
||||
<td class="values"><b>use-platform</b> | windows/win | unix
|
||||
<td>Set the output style of newlines
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="option">boilerplate
|
||||
<td class="values"><b>true</b> | false | Path
|
||||
<td>Prepend a boilerplate to the output file
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="option">versioning
|
||||
<td class="values"><b>true</b> | false
|
||||
<td>Append a timestamped querystring to the output filename
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="option">vars
|
||||
<td class="values">Array
|
||||
<td>An associative array of CSS variables to be applied at runtime. These will override variables declared globally or in the CSS.
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="option">cache
|
||||
<td class="values"><b>true</b> | false
|
||||
<td>Turn caching on or off.
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="option">output_dir
|
||||
<td class="values">Path
|
||||
<td>Specify an output directory for compiled files. Defaults to the same directory as the host file.
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="option">output_file
|
||||
<td class="values">Output filename
|
||||
<td>Specify an output filename (suffix is added).
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="option">asset_dir
|
||||
<td class="values">Path
|
||||
<td>Directory for SVG and image files generated by plugins (defaults to the main file output directory).
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="option">stat_dump
|
||||
<td class="values"><b>false</b> | true | Path
|
||||
<td>Save compile stats and variables to a file in json format.
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="option">vendor_target
|
||||
<td class="values"><b>"all"</b> | "moz", "webkit", ... | Array
|
||||
<td>Limit aliasing to a specific vendor, or an array of vendors.
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="option">rewrite_import_urls
|
||||
<td class="values"><b>true</b> | false | "absolute"
|
||||
<td>Rewrite relative URLs inside inlined imported files.
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="option">import_paths
|
||||
<td class="values">Array
|
||||
<td>Additional paths to search when resolving relative import URLs.
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="option">plugins
|
||||
<td class="values">Array
|
||||
<td>An array of plugin names to enable.
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="option">source_map
|
||||
<td class="values">true | <b>false</b>
|
||||
<td>Output a source map (compliant with the Source Map v3 proposal).
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="option">context
|
||||
<td class="values">Path
|
||||
<td>Context for importing resources from relative urls (Only applies to `csscrush_string()` and command line utility).
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="option">doc_root
|
||||
<td class="values">Path
|
||||
<td>Specify an alternative server document root for situations where the CSS is being served behind an alias or url rewritten path.
|
||||
</tr>
|
||||
</table>
|
44
include/thirdparty/css-crush/docs/core/abstract.md
vendored
Normal file
44
include/thirdparty/css-crush/docs/core/abstract.md
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
<!--{
|
||||
|
||||
"title": "Abstract rules"
|
||||
|
||||
}-->
|
||||
|
||||
Abstract rules are generic rules that can be [extended](#core--inheritance) with the `@extend` directive or mixed in (without arguments) like regular [mixins](#core--mixins) with the `@include` directive.
|
||||
|
||||
```crush
|
||||
@abstract ellipsis {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
@abstract heading {
|
||||
font: bold 1rem serif;
|
||||
letter-spacing: .1em;
|
||||
}
|
||||
|
||||
.foo {
|
||||
@extend ellipsis;
|
||||
display: block;
|
||||
}
|
||||
.bar {
|
||||
@extend ellipsis;
|
||||
@include heading;
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
.foo,
|
||||
.bar {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.foo {
|
||||
display: block;
|
||||
}
|
||||
.bar {
|
||||
font: bold 1rem serif;
|
||||
letter-spacing: .1em;
|
||||
}
|
||||
```
|
39
include/thirdparty/css-crush/docs/core/auto-prefixing.md
vendored
Normal file
39
include/thirdparty/css-crush/docs/core/auto-prefixing.md
vendored
Normal file
|
@ -0,0 +1,39 @@
|
|||
<!--{
|
||||
|
||||
"title": "Auto prefixing"
|
||||
|
||||
}-->
|
||||
|
||||
Vendor prefixes for properties, functions, @-rules and declarations are **automatically generated** – based on [trusted](http://caniuse.com) [sources](http://developer.mozilla.org/en-US/docs/CSS/CSS_Reference) – so you can maintain cross-browser support while keeping your source code clean and easy to maintain.
|
||||
|
||||
|
||||
```crush
|
||||
.foo {
|
||||
background: linear-gradient(to right, red, white);
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
.foo {
|
||||
background: -webkit-linear-gradient(to right, red, white);
|
||||
background: linear-gradient(to right, red, white);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
```crush
|
||||
@keyframes bounce {
|
||||
50% { transform: scale(1.4); }
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
@-webkit-keyframes bounce {
|
||||
50% {-webkit-transform: scale(1.4);
|
||||
transform: scale(1.4);}
|
||||
}
|
||||
@keyframes bounce {
|
||||
50% {-webkit-transform: scale(1.4);
|
||||
transform: scale(1.4);}
|
||||
}
|
||||
```
|
25
include/thirdparty/css-crush/docs/core/direct-import.md
vendored
Normal file
25
include/thirdparty/css-crush/docs/core/direct-import.md
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
<!--{
|
||||
|
||||
"title": "Direct @import"
|
||||
|
||||
}-->
|
||||
|
||||
Files referenced with the `@import` directive are inlined directly to save on http requests. Relative URL paths in the CSS are also updated if necessary.
|
||||
|
||||
If you specify a media designation following the import URL — as per the CSS standard — the imported file content is wrapped in a `@media` block.
|
||||
|
||||
|
||||
```crush
|
||||
/* Standard CSS @import statements */
|
||||
@import "print.css" print;
|
||||
@import url( "small-screen.css" ) screen and ( max-width: 500px );
|
||||
```
|
||||
|
||||
```css
|
||||
@media print {
|
||||
/* Contents of print.css */
|
||||
}
|
||||
@media screen and ( max-width: 500px ) {
|
||||
/* Contents of small-screen.css */
|
||||
}
|
||||
```
|
25
include/thirdparty/css-crush/docs/core/fragments.md
vendored
Normal file
25
include/thirdparty/css-crush/docs/core/fragments.md
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
<!--{
|
||||
|
||||
"title": "Fragments"
|
||||
|
||||
}-->
|
||||
|
||||
Fragments – defined and invoked with the <code>@fragment</code> directive – work in a similar way to [mixins](#core--mixins), except that they work at block level:
|
||||
|
||||
```crush
|
||||
@fragment input-placeholder {
|
||||
#(1)::-webkit-input-placeholder { color: #(0); }
|
||||
#(1):-moz-placeholder { color: #(0); }
|
||||
#(1)::placeholder { color: #(0); }
|
||||
#(1).placeholder-state { color: #(0); }
|
||||
}
|
||||
|
||||
@fragment input-placeholder(#777, textarea);
|
||||
```
|
||||
|
||||
```css
|
||||
textarea::-webkit-input-placeholder { color: #777; }
|
||||
textarea:-moz-placeholder { color: #777; }
|
||||
textarea::placeholder { color: #777; }
|
||||
textarea.placeholder-state { color: #777; }
|
||||
```
|
26
include/thirdparty/css-crush/docs/core/functions/a-adjust.md
vendored
Normal file
26
include/thirdparty/css-crush/docs/core/functions/a-adjust.md
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
<!--{
|
||||
|
||||
"title": "a-adjust()"
|
||||
|
||||
}-->
|
||||
|
||||
Manipulate the opacity (alpha channel) of a color value.
|
||||
|
||||
<code>a-adjust( *color*, *offset* )</code>
|
||||
|
||||
## Parameters
|
||||
|
||||
* *`color`* Any valid CSS color value
|
||||
* *`offset`* The percentage to offset the color opacity
|
||||
|
||||
## Returns
|
||||
|
||||
The modified color value
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
```css
|
||||
/* Reduce color opacity by 10% */
|
||||
color: a-adjust( rgb(50,50,0) -10 );
|
||||
```
|
33
include/thirdparty/css-crush/docs/core/functions/data-uri.md
vendored
Normal file
33
include/thirdparty/css-crush/docs/core/functions/data-uri.md
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
<!--{
|
||||
|
||||
"title": "data-uri()"
|
||||
|
||||
}-->
|
||||
|
||||
Create a data-uri.
|
||||
|
||||
<code>data-uri( *url* )</code>
|
||||
|
||||
## Parameters
|
||||
|
||||
* *`url`* URL of an asset
|
||||
|
||||
`url` cannot be external, and must not be written with an http protocol prefix.
|
||||
|
||||
The following file extensions are supported: jpg, jpeg, gif, png, svg, svgz, ttf, woff
|
||||
|
||||
|
||||
## Returns
|
||||
|
||||
The created data-uri as a string inside a CSS url().
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
```crush
|
||||
background: silver data-uri(../images/stripe.png);
|
||||
```
|
||||
|
||||
```css
|
||||
background: silver url(data:<img-data>);
|
||||
```
|
24
include/thirdparty/css-crush/docs/core/functions/h-adjust.md
vendored
Normal file
24
include/thirdparty/css-crush/docs/core/functions/h-adjust.md
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
<!--{
|
||||
|
||||
"title": "h-adjust()"
|
||||
|
||||
}-->
|
||||
|
||||
Adjust the hue of a color value.
|
||||
|
||||
<code>h-adjust( *color*, *offset* )</code>
|
||||
|
||||
## Parameters
|
||||
|
||||
* *`color`* Any valid CSS color value
|
||||
* *`offset`* The percentage to offset the color hue (percent mark optional)
|
||||
|
||||
## Returns
|
||||
|
||||
The modified color value.
|
||||
|
||||
## Examples
|
||||
|
||||
```css
|
||||
color: h-adjust( deepskyblue -10 );
|
||||
```
|
27
include/thirdparty/css-crush/docs/core/functions/hsl-adjust.md
vendored
Normal file
27
include/thirdparty/css-crush/docs/core/functions/hsl-adjust.md
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
<!--{
|
||||
|
||||
"title": "hsl-adjust()"
|
||||
|
||||
}-->
|
||||
|
||||
Manipulate the hue, saturation and lightness of a color value
|
||||
|
||||
<code>hsl-adjust( *color*, *hue-offset*, *saturation-offset*, *lightness-offset* )</code>
|
||||
|
||||
## Parameters
|
||||
|
||||
* *`color`* Any valid CSS color value
|
||||
* *`hue-offset`* The percentage to offset the color hue
|
||||
* *`saturation-offset`* The percentage to offset the color saturation
|
||||
* *`lightness-offset`* The percentage to offset the color lightness
|
||||
|
||||
## Returns
|
||||
|
||||
The modified color value
|
||||
|
||||
## Examples
|
||||
|
||||
```css
|
||||
/* Lighten and increase saturation */
|
||||
color: hsl-adjust( red 0 5 5 );
|
||||
```
|
27
include/thirdparty/css-crush/docs/core/functions/hsla-adjust.md
vendored
Normal file
27
include/thirdparty/css-crush/docs/core/functions/hsla-adjust.md
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
<!--{
|
||||
|
||||
"title": "hsla-adjust()"
|
||||
|
||||
}-->
|
||||
|
||||
Manipulate the hue, saturation, lightness and opacity of a color value.
|
||||
|
||||
<code>hsla-adjust( *color*, *hue-offset*, *saturation-offset*, *lightness-offset*, *alpha-offset* )</code>
|
||||
|
||||
## Parameters
|
||||
|
||||
* *`color`* Any valid CSS color value
|
||||
* *`hue-offset`* The percentage to offset the color hue
|
||||
* *`saturation-offset`* The percentage to offset the color saturation
|
||||
* *`lightness-offset`* The percentage to offset the color lightness
|
||||
* *`alpha-offset`* The percentage to offset the color opacity
|
||||
|
||||
## Returns
|
||||
|
||||
The modified color value.
|
||||
|
||||
## Examples
|
||||
|
||||
```css
|
||||
color: hsla-adjust( #f00 0 5 5 -10 );
|
||||
```
|
24
include/thirdparty/css-crush/docs/core/functions/l-adjust.md
vendored
Normal file
24
include/thirdparty/css-crush/docs/core/functions/l-adjust.md
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
<!--{
|
||||
|
||||
"title": "l-adjust()"
|
||||
|
||||
}-->
|
||||
|
||||
Adjust the lightness of a color value.
|
||||
|
||||
<code>l-adjust( *color*, *offset* )</code>
|
||||
|
||||
## Parameters
|
||||
|
||||
* *`color`* Any valid CSS color value
|
||||
* *`offset`* The percentage to offset the color hue (percent mark optional)
|
||||
|
||||
## Returns
|
||||
|
||||
The modified color value.
|
||||
|
||||
## Examples
|
||||
|
||||
```css
|
||||
color: l-adjust( deepskyblue 10 );
|
||||
```
|
19
include/thirdparty/css-crush/docs/core/functions/math.md
vendored
Normal file
19
include/thirdparty/css-crush/docs/core/functions/math.md
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
<!--{
|
||||
|
||||
"title": "math()"
|
||||
|
||||
}-->
|
||||
|
||||
Evaluate a raw mathematical expression.
|
||||
|
||||
<code>math( *expression* [, *unit*] )</code>
|
||||
|
||||
## Examples
|
||||
|
||||
```crush
|
||||
font-size: math( 12 / 16, em );
|
||||
```
|
||||
|
||||
```css
|
||||
font-size: 0.75em;
|
||||
```
|
54
include/thirdparty/css-crush/docs/core/functions/query.md
vendored
Normal file
54
include/thirdparty/css-crush/docs/core/functions/query.md
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
<!--{
|
||||
|
||||
"title": "query()"
|
||||
|
||||
}-->
|
||||
|
||||
Copy a value from another rule.
|
||||
|
||||
<code>query( *target* [, *property-name* = default] [, *fallback*] )</code>
|
||||
|
||||
## Parameters
|
||||
|
||||
* *`target`* A rule selector, an abstract rule name or context keyword: `previous`, `next` (also `parent` and `top` within nested structures)
|
||||
* *`property-name`* The CSS property name to copy, or just `default` to pass over. Defaults to the calling property
|
||||
* *`fallback`* A CSS value to use if the target property does not exist
|
||||
|
||||
|
||||
## Returns
|
||||
|
||||
The referenced property value, or the fallback if it has not been set.
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
```css
|
||||
.foo {
|
||||
width: 40em;
|
||||
height: 100em;
|
||||
}
|
||||
|
||||
.bar {
|
||||
width: query( .foo ); /* 40em */
|
||||
margin-top: query( .foo, height ); /* 100em */
|
||||
margin-bottom: query( .foo, default, 3em ); /* 3em */
|
||||
}
|
||||
```
|
||||
|
||||
Using context keywords:
|
||||
|
||||
```css
|
||||
.foo {
|
||||
width: 40em;
|
||||
.bar {
|
||||
width: 30em;
|
||||
.baz: {
|
||||
width: query( parent ); /* 30em */
|
||||
.qux {
|
||||
width: query( top ); /* 40em */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
25
include/thirdparty/css-crush/docs/core/functions/s-adjust.md
vendored
Normal file
25
include/thirdparty/css-crush/docs/core/functions/s-adjust.md
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
<!--{
|
||||
|
||||
"title": "s-adjust()"
|
||||
|
||||
}-->
|
||||
|
||||
Adjust the saturation of a color value.
|
||||
|
||||
<code>s-adjust( *color*, *offset* )</code>
|
||||
|
||||
## Parameters
|
||||
|
||||
* *`color`* Any valid CSS color value
|
||||
* *`offset`* The percentage to offset the color hue (percent mark optional)
|
||||
|
||||
## Returns
|
||||
|
||||
The modified color value.
|
||||
|
||||
## Examples
|
||||
|
||||
```css
|
||||
/* Desaturate */
|
||||
color: s-adjust( deepskyblue -100 );
|
||||
```
|
39
include/thirdparty/css-crush/docs/core/functions/this.md
vendored
Normal file
39
include/thirdparty/css-crush/docs/core/functions/this.md
vendored
Normal file
|
@ -0,0 +1,39 @@
|
|||
<!--{
|
||||
|
||||
"title": "this()"
|
||||
|
||||
}-->
|
||||
|
||||
Reference another property value from the same containing block.
|
||||
|
||||
Restricted to referencing properties that don't already reference other properties.
|
||||
|
||||
<code>this( *property-name*, *fallback* )</code>
|
||||
|
||||
## Parameters
|
||||
|
||||
* *`property-name`* Property name
|
||||
* *`fallback`* A CSS value
|
||||
|
||||
## Returns
|
||||
|
||||
The referenced property value, or the fallback if it has not been set.
|
||||
|
||||
## Examples
|
||||
|
||||
```css
|
||||
.foo {
|
||||
width: this( height );
|
||||
height: 100em;
|
||||
}
|
||||
```
|
||||
|
||||
********
|
||||
|
||||
```css
|
||||
/* The following both fail because they create circular references. */
|
||||
.bar {
|
||||
height: this( width );
|
||||
width: this( height );
|
||||
}
|
||||
```
|
127
include/thirdparty/css-crush/docs/core/inheritance.md
vendored
Normal file
127
include/thirdparty/css-crush/docs/core/inheritance.md
vendored
Normal file
|
@ -0,0 +1,127 @@
|
|||
<!--{
|
||||
|
||||
"title": "Rule inheritance"
|
||||
|
||||
}-->
|
||||
|
||||
By using the `@extend` directive and passing it a named ruleset or selector from any other rule you can share styles more effectively across a stylesheet.
|
||||
|
||||
[Abstract rules](#core--abstract) can be used if you just need to extend a generic set of declarations.
|
||||
|
||||
```crush
|
||||
.negative-text {
|
||||
overflow: hidden;
|
||||
text-indent: -9999px;
|
||||
}
|
||||
|
||||
.sidebar-headline {
|
||||
@extend .negative-text;
|
||||
background: url( headline.png ) no-repeat;
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
.negative-text,
|
||||
.sidebar-headline {
|
||||
overflow: hidden;
|
||||
text-indent: -9999px;
|
||||
}
|
||||
|
||||
.sidebar-headline {
|
||||
background: url( headline.png ) no-repeat;
|
||||
}
|
||||
```
|
||||
|
||||
Inheritance is recursive:
|
||||
|
||||
```crush
|
||||
.one { color: pink; }
|
||||
.two { @extend .one; }
|
||||
.three { @extend .two; }
|
||||
.four { @extend .three; }
|
||||
```
|
||||
|
||||
```css
|
||||
.one, .two, .three, .four { color: pink; }
|
||||
```
|
||||
|
||||
## Referencing by name
|
||||
|
||||
If you want to reference a rule without being concerned about later changes to the identifying selector use the `@name` directive:
|
||||
|
||||
```crush
|
||||
.foo123 {
|
||||
@name foo;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.bar {
|
||||
@include foo;
|
||||
}
|
||||
.baz {
|
||||
@extend foo;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Extending with pseudo classes/elements
|
||||
|
||||
`@extend` arguments can adopt pseudo classes/elements by appending an exclamation mark:
|
||||
|
||||
```crush
|
||||
.link-base {
|
||||
color: #bada55;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.link-base:hover,
|
||||
.link-base:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.link-footer {
|
||||
@extend .link-base, .link-base:hover!, .link-base:focus!;
|
||||
color: blue;
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
.link-base,
|
||||
.link-footer {
|
||||
color: #bada55;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.link-base:hover,
|
||||
.link-base:focus,
|
||||
.link-footer:hover,
|
||||
.link-footer:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.link-footer {
|
||||
color: blue;
|
||||
}
|
||||
```
|
||||
|
||||
The same outcome can also be achieved with an [Abstract rule](#core--abstract) wrapper to simplify repeated use:
|
||||
|
||||
```crush
|
||||
.link-base {
|
||||
color: #bada55;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.link-base:hover,
|
||||
.link-base:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@abstract link-base {
|
||||
@extend .link-base, .link-base:hover!, .link-base:focus!;
|
||||
}
|
||||
|
||||
.link-footer {
|
||||
@extend link-base;
|
||||
color: blue;
|
||||
}
|
||||
```
|
||||
|
41
include/thirdparty/css-crush/docs/core/loop.md
vendored
Normal file
41
include/thirdparty/css-crush/docs/core/loop.md
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
<!--{
|
||||
|
||||
"title": "Loops"
|
||||
|
||||
}-->
|
||||
|
||||
For...in loops with lists and generator functions.
|
||||
|
||||
```crush
|
||||
@for fruit in apple, orange, pear {
|
||||
.#(fruit) {
|
||||
background-image: url("images/#(fruit).jpg");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
.apple { background-image: url(images/apple.jpg); }
|
||||
.orange { background-image: url(images/orange.jpg); }
|
||||
.pear { background-image: url(images/pear.jpg); }
|
||||
```
|
||||
|
||||
```crush
|
||||
@for base in range(2, 24) {
|
||||
@for i in range(1, #(base)) {
|
||||
.grid-#(i)-of-#(base) {
|
||||
width: math(#(i) / #(base) * 100, %);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
.grid-1-of-2 { width: 50%; }
|
||||
.grid-2-of-2 { width: 100%; }
|
||||
/*
|
||||
Intermediate steps ommited.
|
||||
*/
|
||||
.grid-23-of-24 { width: 95.83333%; }
|
||||
.grid-24-of-24 { width: 100%; }
|
||||
```
|
95
include/thirdparty/css-crush/docs/core/mixins.md
vendored
Normal file
95
include/thirdparty/css-crush/docs/core/mixins.md
vendored
Normal file
|
@ -0,0 +1,95 @@
|
|||
<!--{
|
||||
|
||||
"title": "Mixins"
|
||||
|
||||
}-->
|
||||
|
||||
Mixins make reusing small snippets of CSS much simpler. You define them with the `@mixin` directive.
|
||||
|
||||
Positional arguments via the argument function `#()` extend the capability of mixins for repurposing in different contexts.
|
||||
|
||||
```crush
|
||||
@mixin display-font {
|
||||
font-family: "Arial Black", sans-serif;
|
||||
font-size: #(0);
|
||||
letter-spacing: #(1);
|
||||
}
|
||||
|
||||
/* Another mixin with default arguments */
|
||||
@mixin blue-theme {
|
||||
color: #(0 navy);
|
||||
background-image: url("images/#(1 cross-hatch).png");
|
||||
}
|
||||
|
||||
/* Applying the mixins */
|
||||
.foo {
|
||||
@include display-font(100%, .1em), blue-theme;
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
.foo {
|
||||
font-family: "Arial Black", sans-serif;
|
||||
font-size: 100%;
|
||||
letter-spacing: .1em;
|
||||
color: navy;
|
||||
background-image: url("images/cross-hatch.png");
|
||||
}
|
||||
```
|
||||
|
||||
## Skipping arguments
|
||||
|
||||
Mixin arguments can be skipped by using the **default** keyword:
|
||||
|
||||
```crush
|
||||
@mixin display-font {
|
||||
font-size: #(0 100%);
|
||||
letter-spacing: #(1);
|
||||
}
|
||||
|
||||
/* Applying the mixin skipping the first argument so the
|
||||
default value is used instead */
|
||||
#foo {
|
||||
@include display-font(default, .3em);
|
||||
}
|
||||
```
|
||||
|
||||
Sometimes you may need to use the same positional argument more than once. In this case the default value only needs to be specified once:
|
||||
|
||||
```crush
|
||||
@mixin square {
|
||||
width: #(0 10px);
|
||||
height: #(0);
|
||||
}
|
||||
|
||||
.foo {
|
||||
@include square;
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
#foo {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Mixing-in from other sources
|
||||
|
||||
Normal rules and [abstract rules](#core--abstract) can also be used as static mixins without arguments:
|
||||
|
||||
```crush
|
||||
@abstract negative-text {
|
||||
text-indent: -9999px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#main-content .theme-border {
|
||||
border: 1px solid maroon;
|
||||
}
|
||||
|
||||
.foo {
|
||||
@include negative-text, #main-content .theme-border;
|
||||
}
|
||||
```
|
49
include/thirdparty/css-crush/docs/core/nesting.md
vendored
Normal file
49
include/thirdparty/css-crush/docs/core/nesting.md
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
<!--{
|
||||
|
||||
"title": "Nesting"
|
||||
|
||||
}-->
|
||||
|
||||
Rules can be nested to avoid repetitive typing when scoping to a common parent selector.
|
||||
|
||||
```crush
|
||||
.homepage {
|
||||
color: #333;
|
||||
background: white;
|
||||
.content {
|
||||
p {
|
||||
font-size: 110%;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
.homepage {
|
||||
color: #333;
|
||||
background: white;
|
||||
}
|
||||
.homepage .content p {
|
||||
font-size: 110%;
|
||||
}
|
||||
```
|
||||
|
||||
## Parent referencing
|
||||
|
||||
You can use the parent reference symbol `&` for placing the parent selector explicitly.
|
||||
|
||||
```crush
|
||||
.homepage {
|
||||
.no-js & {
|
||||
p {
|
||||
font-size: 110%;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
.no-js .homepage p {
|
||||
font-size: 110%;
|
||||
}
|
||||
```
|
80
include/thirdparty/css-crush/docs/core/selector-aliases.md
vendored
Normal file
80
include/thirdparty/css-crush/docs/core/selector-aliases.md
vendored
Normal file
|
@ -0,0 +1,80 @@
|
|||
<!--{
|
||||
|
||||
"title": "Selector aliases"
|
||||
|
||||
}-->
|
||||
|
||||
Selector aliases can be useful for grouping together common selector chains for reuse.
|
||||
|
||||
They're defined with the `@selector` directive, and can be used anywhere you might use a pseudo class.
|
||||
|
||||
|
||||
```crush
|
||||
@selector heading :any(h1, h2, h3, h4, h5, h6);
|
||||
@selector radio input[type="radio"];
|
||||
@selector hocus :any(:hover, :focus);
|
||||
|
||||
/* Selector aliases with arguments */
|
||||
@selector class-prefix :any([class^="#(0)"], [class*=" #(0)"]);
|
||||
@selector col :class-prefix(-col);
|
||||
|
||||
.sidebar :heading {
|
||||
color: honeydew;
|
||||
}
|
||||
|
||||
:radio {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
:col {
|
||||
float: left;
|
||||
}
|
||||
|
||||
p a:hocus {
|
||||
text-decoration: none;
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
.sidebar h1, .sidebar h2,
|
||||
.sidebar h3, .sidebar h4,
|
||||
.sidebar h5, .sidebar h6 {
|
||||
color: honeydew;
|
||||
}
|
||||
|
||||
input[type="radio"] {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
[class^="col-"],
|
||||
[class*=" col-"] {
|
||||
border: 1px solid rgba(0,0,0,.5);
|
||||
}
|
||||
|
||||
p a:hover,
|
||||
p a:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
```
|
||||
|
||||
## Selector splatting
|
||||
|
||||
Selector splats are a special kind of selector alias that expand using passed arguments.
|
||||
|
||||
```crush
|
||||
@selector-splat input input[type="#(text)"];
|
||||
|
||||
form :input(time, text, url, email, number) {
|
||||
border: 1px solid;
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
form input[type="time"],
|
||||
form input[type="text"],
|
||||
form input[type="url"],
|
||||
form input[type="email"],
|
||||
form input[type="number"] {
|
||||
border: 1px solid;
|
||||
}
|
||||
```
|
22
include/thirdparty/css-crush/docs/core/selector-grouping.md
vendored
Normal file
22
include/thirdparty/css-crush/docs/core/selector-grouping.md
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
<!--{
|
||||
|
||||
"title": "Selector grouping"
|
||||
|
||||
}-->
|
||||
|
||||
Selector grouping with the `:any` pseudo class (modelled after CSS4 :matches) simplifies the creation of complex selector chains.
|
||||
|
||||
```crush
|
||||
:any( .sidebar, .block ) a:any( :hover, :focus ) {
|
||||
color: lemonchiffon;
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
.block a:hover,
|
||||
.block a:focus,
|
||||
.sidebar a:hover,
|
||||
.sidebar a:focus {
|
||||
color: lemonchiffon;
|
||||
}
|
||||
```
|
62
include/thirdparty/css-crush/docs/core/variables.md
vendored
Normal file
62
include/thirdparty/css-crush/docs/core/variables.md
vendored
Normal file
|
@ -0,0 +1,62 @@
|
|||
<!--{
|
||||
|
||||
"title": "Variables"
|
||||
|
||||
}-->
|
||||
|
||||
Declare variables in your CSS with a `@set` directive and use them with the `$()` function.
|
||||
|
||||
Variables can also be injected at runtime with the [vars option](#api--options).
|
||||
|
||||
|
||||
```crush
|
||||
/* Defining variables */
|
||||
@set {
|
||||
dark: #333;
|
||||
light: #F4F2E2;
|
||||
smaller-screen: screen and (max-width: 800px);
|
||||
}
|
||||
|
||||
/* Using variables */
|
||||
@media $(smaller-screen) {
|
||||
ul, p {
|
||||
color: $(dark);
|
||||
/* Using a fallback value with an undefined variable */
|
||||
background-color: $(accent-color, #ff0);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
*******
|
||||
|
||||
```css
|
||||
/* Interpolation */
|
||||
.username::before {
|
||||
content: "$(greeting)";
|
||||
}
|
||||
```
|
||||
|
||||
## Conditionals
|
||||
|
||||
Sections of CSS can be included and excluded on the basis of variable existence with the `@ifset` directive:
|
||||
|
||||
```crush
|
||||
@set foo #f00;
|
||||
@set bar true;
|
||||
|
||||
@ifset foo {
|
||||
p {
|
||||
color: $(foo);
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 12px;
|
||||
@ifset not foo {
|
||||
line-height: 1.5;
|
||||
}
|
||||
@ifset bar(true) {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
```
|
26
include/thirdparty/css-crush/docs/getting-started/js.md
vendored
Normal file
26
include/thirdparty/css-crush/docs/getting-started/js.md
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
<!--{
|
||||
|
||||
"title": "JavaScript"
|
||||
|
||||
}-->
|
||||
|
||||
This preprocessor is written in PHP, so as prerequisite you will need to have PHP installed on your system to use the JS api.
|
||||
|
||||
```shell
|
||||
npm install csscrush
|
||||
```
|
||||
|
||||
All methods can take the standard options (camelCase) as the second argument.
|
||||
|
||||
```php
|
||||
const csscrush = require('csscrush');
|
||||
|
||||
// Compile. Returns promise.
|
||||
csscrush.file('./styles.css', {sourceMap: true});
|
||||
|
||||
// Compile string of CSS. Returns promise.
|
||||
csscrush.string('* {box-sizing: border-box;}');
|
||||
|
||||
// Compile and watch file. Returns event emitter (triggers 'data' on compile).
|
||||
csscrush.watch('./styles.css');
|
||||
```
|
17
include/thirdparty/css-crush/docs/getting-started/php.md
vendored
Normal file
17
include/thirdparty/css-crush/docs/getting-started/php.md
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
<!--{
|
||||
|
||||
"title": "PHP"
|
||||
|
||||
}-->
|
||||
|
||||
If you're using [Composer](http://getcomposer.org) you can use Crush in your project with the following line in your terminal:
|
||||
|
||||
```shell
|
||||
composer require css-crush/css-crush
|
||||
```
|
||||
|
||||
If you're not using Composer yet just download the library ([zip](http://github.com/peteboere/css-crush/zipball/master) or [tar](http://github.com/peteboere/css-crush/tarball/master)) into a convenient location and require the bootstrap file:
|
||||
|
||||
```php
|
||||
<?php require_once 'path/to/CssCrush.php'; ?>
|
||||
```
|
21
include/thirdparty/css-crush/docs/plugins/aria.md
vendored
Normal file
21
include/thirdparty/css-crush/docs/plugins/aria.md
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
|
||||
Pseudo classes for working with ARIA roles, states and properties.
|
||||
|
||||
* [ARIA roles spec](http://www.w3.org/TR/wai-aria/roles)
|
||||
* [ARIA states and properties spec](http://www.w3.org/TR/wai-aria/states_and_properties)
|
||||
|
||||
````crush
|
||||
:role(tablist) {...}
|
||||
:aria-expanded {...}
|
||||
:aria-expanded(false) {...}
|
||||
:aria-label {...}
|
||||
:aria-label(foobarbaz) {...}
|
||||
````
|
||||
|
||||
````css
|
||||
[role="tablist"] {...}
|
||||
[aria-expanded="true"] {...}
|
||||
[aria-expanded="false"] {...}
|
||||
[aria-label] {...}
|
||||
[aria-label="foobarbaz"] {...}
|
||||
````
|
55
include/thirdparty/css-crush/docs/plugins/canvas.md
vendored
Normal file
55
include/thirdparty/css-crush/docs/plugins/canvas.md
vendored
Normal file
|
@ -0,0 +1,55 @@
|
|||
|
||||
Bitmap image generator.
|
||||
|
||||
Requires the GD image library bundled with PHP.
|
||||
|
||||
```crush
|
||||
/* Create square semi-opaque png. */
|
||||
@canvas foo {
|
||||
width: 50;
|
||||
height: 50;
|
||||
fill: rgba(255, 0, 0, .5);
|
||||
}
|
||||
|
||||
body {
|
||||
background: white canvas(foo);
|
||||
}
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
```crush
|
||||
/* White to transparent east facing gradient with 10px
|
||||
margin and background fill. */
|
||||
@canvas horz-gradient {
|
||||
width: #(0);
|
||||
height: 150;
|
||||
fill: canvas-linear-gradient(to right, #(1 white), #(2 rgba(255,255,255,0)));
|
||||
background-fill: powderblue;
|
||||
margin: 10;
|
||||
}
|
||||
|
||||
/* Rectangle 300x150. */
|
||||
body {
|
||||
background: canvas(horz-gradient, 300);
|
||||
}
|
||||
/* Flipped gradient, using canvas-data() to generate a data URI. */
|
||||
.bar {
|
||||
background: canvas-data(horz-gradient, 100, rgba(255,255,255,0), white);
|
||||
}
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
```crush
|
||||
/* Google logo resized to 400px width and given a sepia effect. */
|
||||
@canvas sepia {
|
||||
src: url(http://www.google.com/images/logo.png);
|
||||
width: 400;
|
||||
canvas-filter: greyscale() colorize(45, 45, 0);
|
||||
}
|
||||
|
||||
.bar {
|
||||
background: canvas(sepia);
|
||||
}
|
||||
```
|
37
include/thirdparty/css-crush/docs/plugins/ease.md
vendored
Normal file
37
include/thirdparty/css-crush/docs/plugins/ease.md
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
|
||||
Expanded easing keywords for transitions.
|
||||
|
||||
* ease-in-out-back
|
||||
* ease-in-out-circ
|
||||
* ease-in-out-expo
|
||||
* ease-in-out-sine
|
||||
* ease-in-out-quint
|
||||
* ease-in-out-quart
|
||||
* ease-in-out-cubic
|
||||
* ease-in-out-quad
|
||||
* ease-out-back
|
||||
* ease-out-circ
|
||||
* ease-out-expo
|
||||
* ease-out-sine
|
||||
* ease-out-quint
|
||||
* ease-out-quart
|
||||
* ease-out-cubic
|
||||
* ease-out-quad
|
||||
* ease-in-back
|
||||
* ease-in-circ
|
||||
* ease-in-expo
|
||||
* ease-in-sine
|
||||
* ease-in-quint
|
||||
* ease-in-quart
|
||||
* ease-in-cubic
|
||||
* ease-in-quad
|
||||
|
||||
See [easing demos](http://easings.net) for live examples.
|
||||
|
||||
```crush
|
||||
transition: .2s ease-in-quad;
|
||||
```
|
||||
|
||||
```css
|
||||
transition: .2s cubic-bezier(.550,.085,.680,.530);
|
||||
```
|
16
include/thirdparty/css-crush/docs/plugins/forms.md
vendored
Normal file
16
include/thirdparty/css-crush/docs/plugins/forms.md
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
|
||||
Pseudo classes for working with forms.
|
||||
|
||||
```crush
|
||||
:input(date, search, email) {...}
|
||||
:checkbox {...}
|
||||
:radio {...}
|
||||
:text {...}
|
||||
```
|
||||
|
||||
```css
|
||||
input[type="date"], input[type="search"], input[type="email"] {...}
|
||||
input[type="checkbox"] {...}
|
||||
input[type="radio"] {...}
|
||||
input[type="text"] {...}
|
||||
```
|
12
include/thirdparty/css-crush/docs/plugins/hocus-pocus.md
vendored
Normal file
12
include/thirdparty/css-crush/docs/plugins/hocus-pocus.md
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
|
||||
Composite :hover/:focus/:active pseudo classes.
|
||||
|
||||
```crush
|
||||
a:hocus { color: red; }
|
||||
a:pocus { color: red; }
|
||||
```
|
||||
|
||||
```css
|
||||
a:hover, a:focus { color: red; }
|
||||
a:hover, a:focus, a:active { color: red; }
|
||||
```
|
21
include/thirdparty/css-crush/docs/plugins/property-sorter.md
vendored
Normal file
21
include/thirdparty/css-crush/docs/plugins/property-sorter.md
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
|
||||
Property sorting.
|
||||
|
||||
Examples use the predefined property sorting table. To define a custom sorting order pass an array to `csscrush_set_property_sort_order()`
|
||||
|
||||
|
||||
```crush
|
||||
color: red;
|
||||
background: #000;
|
||||
opacity: .5;
|
||||
display: block;
|
||||
position: absolute;
|
||||
```
|
||||
|
||||
```css
|
||||
position: absolute;
|
||||
display: block;
|
||||
opacity: .5;
|
||||
color: red;
|
||||
background: #000;
|
||||
```
|
48
include/thirdparty/css-crush/docs/plugins/svg-gradients.md
vendored
Normal file
48
include/thirdparty/css-crush/docs/plugins/svg-gradients.md
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
|
||||
Functions for creating SVG gradients with a CSS gradient like syntax.
|
||||
|
||||
Primarily useful for supporting Internet Explorer 9.
|
||||
|
||||
## svg-linear-gradent()
|
||||
|
||||
Syntax is the same as [linear-gradient()](http://dev.w3.org/csswg/css3-images/#linear-gradient)
|
||||
|
||||
```syntax
|
||||
svg-linear-gradent( [ <angle> | to <side-or-corner> ,]? <color-stop> [, <color-stop>]+ )
|
||||
```
|
||||
|
||||
### Returns
|
||||
|
||||
A base64 encoded svg data-uri.
|
||||
|
||||
### Known issues
|
||||
|
||||
Color stops can only take percentage value offsets.
|
||||
|
||||
```css
|
||||
background-image: svg-linear-gradient( to top left, #fff, rgba(255,255,255,0) 80% );
|
||||
background-image: svg-linear-gradient( 35deg, red, gold 20%, powderblue );
|
||||
```
|
||||
|
||||
|
||||
## svg-radial-gradent()
|
||||
|
||||
Syntax is similar to but more limited than [radial-gradient()](http://dev.w3.org/csswg/css3-images/#radial-gradient)
|
||||
|
||||
```syntax
|
||||
svg-radial-gradent( [ <origin> | at <position> ,]? <color-stop> [, <color-stop>]+ )
|
||||
```
|
||||
|
||||
### Returns
|
||||
|
||||
A base64 encoded svg data-uri.
|
||||
|
||||
### Known issues
|
||||
|
||||
Color stops can only take percentage value offsets.
|
||||
No control over shape - only circular gradients - however, the generated image can be stretched with background-size.
|
||||
|
||||
```css
|
||||
background-image: svg-radial-gradient( at center, red, blue 50%, yellow );
|
||||
background-image: svg-radial-gradient( 100% 50%, rgba(255,255,255,.5), rgba(255,255,255,0) );
|
||||
```
|
74
include/thirdparty/css-crush/docs/plugins/svg.md
vendored
Normal file
74
include/thirdparty/css-crush/docs/plugins/svg.md
vendored
Normal file
|
@ -0,0 +1,74 @@
|
|||
|
||||
Define and embed simple SVG elements, paths and effects inside CSS
|
||||
|
||||
|
||||
```crush
|
||||
@svg foo {
|
||||
type: star;
|
||||
star-points: #(0 5);
|
||||
radius: 100 50;
|
||||
margin: 20;
|
||||
stroke: black;
|
||||
fill: red;
|
||||
fill-opacity: .5;
|
||||
}
|
||||
|
||||
/* Embed SVG with svg() function (generates an svg file). */
|
||||
body {
|
||||
background: svg(foo);
|
||||
}
|
||||
/* As above but a 3 point star creating a data URI instead of a file. */
|
||||
body {
|
||||
background: svg-data(foo, 3);
|
||||
}
|
||||
```
|
||||
|
||||
*******
|
||||
|
||||
```crush
|
||||
/* Using path data and stroke styles to create a plus sign. */
|
||||
@svg plus {
|
||||
d: "M0,5 h10 M5,0 v10";
|
||||
width: 10;
|
||||
height: 10;
|
||||
stroke: white;
|
||||
stroke-linecap: round;
|
||||
stroke-width: 2;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
*******
|
||||
|
||||
```crush
|
||||
/* Skewed circle with radial gradient fill and drop shadow. */
|
||||
@svg circle {
|
||||
type: circle;
|
||||
transform: skewX(30);
|
||||
diameter: 60;
|
||||
margin: 20;
|
||||
fill: svg-radial-gradient(at top right, gold 50%, red);
|
||||
drop-shadow: 2 2 0 rgba(0,0,0,1);
|
||||
}
|
||||
```
|
||||
|
||||
*******
|
||||
|
||||
```crush
|
||||
/* 8-sided polygon with an image fill.
|
||||
Note: images usually have to be converted to data URIs, see known issues below. */
|
||||
@svg pattern {
|
||||
type: polygon;
|
||||
sides: 8;
|
||||
diameter: 180;
|
||||
margin: 20;
|
||||
fill: pattern(data-uri(kitten.jpg), scale(1) translate(-100 0));
|
||||
fill-opacity: .8;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Known issues
|
||||
|
||||
Firefox [does not allow linked images](https://bugzilla.mozilla.org/show_bug.cgi?id=628747#c0) (or other svg) when svg is in "svg as image" mode.
|
||||
|
153
include/thirdparty/css-crush/index.js
vendored
Normal file
153
include/thirdparty/css-crush/index.js
vendored
Normal file
|
@ -0,0 +1,153 @@
|
|||
/*eslint no-control-regex: 0*/
|
||||
|
||||
const path = require('path');
|
||||
const querystring = require('querystring');
|
||||
const {EventEmitter} = require('events');
|
||||
const cliPath = path.resolve(__dirname, './cli.php');
|
||||
|
||||
const processes = [];
|
||||
const processExec = (...args) => {
|
||||
processes.push(require('child_process').exec(...args));
|
||||
return processes[processes.length-1];
|
||||
};
|
||||
|
||||
process.on('exit', () => {
|
||||
processes.filter(it => it).forEach(proc => proc.kill());
|
||||
});
|
||||
|
||||
const self = module.exports = {};
|
||||
|
||||
class Process extends EventEmitter {
|
||||
|
||||
exec(options) {
|
||||
return new Promise(resolve => {
|
||||
let command = this.assembleCommand(options);
|
||||
const {stdIn} = options;
|
||||
if (stdIn) {
|
||||
command = `echo '${stdIn.replace(/'/g, "\\'")}' | ${command}`;
|
||||
}
|
||||
processExec(command, (error, stdout, stderr) => {
|
||||
process.stderr.write(stderr.toString());
|
||||
if (error) {
|
||||
return resolve(false);
|
||||
}
|
||||
const stdOut = stdout.toString();
|
||||
if (stdIn) {
|
||||
process.stdout.write(stdOut);
|
||||
}
|
||||
return resolve(stdOut || true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
watch(options) {
|
||||
options.watch = true;
|
||||
const command = this.assembleCommand(options);
|
||||
const proc = processExec(command);
|
||||
|
||||
/*
|
||||
* Emitting 'error' events from EventEmitter without
|
||||
* any error listener will throw uncaught exception.
|
||||
*/
|
||||
this.on('error', () => {});
|
||||
|
||||
proc.stderr.on('data', msg => {
|
||||
msg = msg.toString();
|
||||
process.stderr.write(msg);
|
||||
msg = msg.replace(/\x1B\[[^m]*m/g, '').trim();
|
||||
|
||||
const [, signal, detail] = /^([A-Z]+):\s*(.+)/i.exec(msg) || [];
|
||||
const {input, output} = options;
|
||||
const eventData = {
|
||||
signal,
|
||||
options: {
|
||||
input: input ? path.resolve(input) : null,
|
||||
output: output ? path.resolve(output) : null,
|
||||
},
|
||||
};
|
||||
|
||||
if (/^(WARNING|ERROR)$/.test(signal)) {
|
||||
const error = new Error(detail);
|
||||
Object.assign(error, eventData, {severity: signal.toLowerCase()});
|
||||
this.emit('error', error);
|
||||
}
|
||||
else {
|
||||
this.emit('data', {message: detail, ...eventData});
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
assembleCommand(options) {
|
||||
return `${self.phpBin || 'php'} ${cliPath} ${this.stringifyOptions(options)}`;
|
||||
}
|
||||
|
||||
stringifyOptions(options) {
|
||||
const args = [];
|
||||
options = {...options};
|
||||
for (let name in options) {
|
||||
// Normalize to hypenated case.
|
||||
const cssCase = name.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`);
|
||||
if (name !== cssCase) {
|
||||
options[cssCase] = options[name];
|
||||
delete options[name];
|
||||
name = cssCase;
|
||||
}
|
||||
let value = options[name];
|
||||
switch (name) {
|
||||
// Booleans.
|
||||
case 'watch': // fallthrough
|
||||
case 'source-map': // fallthrough
|
||||
case 'boilerplate': // fallthrough
|
||||
if (value) {
|
||||
args.push(`--${name}`);
|
||||
}
|
||||
break;
|
||||
case 'minify':
|
||||
if (! value) {
|
||||
args.push(`--pretty`);
|
||||
}
|
||||
break;
|
||||
// Array/list values.
|
||||
case 'vendor-target': // fallthrough
|
||||
case 'plugins': // fallthrough
|
||||
case 'import-path':
|
||||
if (value) {
|
||||
value = (Array.isArray(value) ? value : [value]).join(',');
|
||||
args.push(`--${name}="${value}"`);
|
||||
}
|
||||
break;
|
||||
// String values.
|
||||
case 'newlines': // fallthrough
|
||||
case 'formatter': // fallthrough
|
||||
case 'input': // fallthrough
|
||||
case 'context': // fallthrough
|
||||
case 'output':
|
||||
if (value) {
|
||||
args.push(`--${name}="${value}"`);
|
||||
}
|
||||
break;
|
||||
case 'vars':
|
||||
args.push(`--${name}="${querystring.stringify(value)}"`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return args.join(' ');
|
||||
}
|
||||
}
|
||||
|
||||
self.watch = (file, options={}) => {
|
||||
options.input = file;
|
||||
return (new Process()).watch(options);
|
||||
};
|
||||
|
||||
self.file = (file, options={}) => {
|
||||
options.input = file;
|
||||
return (new Process()).exec(options);
|
||||
};
|
||||
|
||||
self.string = (string, options={}) => {
|
||||
options.stdIn = string;
|
||||
return (new Process()).exec(options);
|
||||
};
|
66
include/thirdparty/css-crush/lib/CssCrush/BalancedMatch.php
vendored
Normal file
66
include/thirdparty/css-crush/lib/CssCrush/BalancedMatch.php
vendored
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Balanced bracket matching on string objects.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class BalancedMatch
|
||||
{
|
||||
public function __construct(StringObject $string, $offset, $brackets = '{}')
|
||||
{
|
||||
$this->string = $string;
|
||||
$this->offset = $offset;
|
||||
$this->match = null;
|
||||
$this->length = 0;
|
||||
|
||||
list($opener, $closer) = str_split($brackets, 1);
|
||||
|
||||
if (strpos($string->raw, $opener, $this->offset) === false) {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (substr_count($string->raw, $opener) !== substr_count($string->raw, $closer)) {
|
||||
$sample = substr($string->raw, $this->offset, 25);
|
||||
warning("Unmatched token near '$sample'.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$patt = ($opener === '{') ? Regex::$patt->block : Regex::$patt->parens;
|
||||
|
||||
if (preg_match($patt, $string->raw, $m, PREG_OFFSET_CAPTURE, $this->offset)) {
|
||||
|
||||
$this->match = $m;
|
||||
$this->matchLength = strlen($m[0][0]);
|
||||
$this->matchStart = $m[0][1];
|
||||
$this->matchEnd = $this->matchStart + $this->matchLength;
|
||||
$this->length = $this->matchEnd - $this->offset;
|
||||
}
|
||||
else {
|
||||
warning("Could not match '$opener'. Exiting.");
|
||||
}
|
||||
}
|
||||
|
||||
public function inside()
|
||||
{
|
||||
return $this->match[2][0];
|
||||
}
|
||||
|
||||
public function whole()
|
||||
{
|
||||
return substr($this->string->raw, $this->offset, $this->length);
|
||||
}
|
||||
|
||||
public function replace($replacement)
|
||||
{
|
||||
$this->string->splice($replacement, $this->offset, $this->length);
|
||||
}
|
||||
|
||||
public function unWrap()
|
||||
{
|
||||
$this->string->splice($this->inside(), $this->offset, $this->length);
|
||||
}
|
||||
}
|
74
include/thirdparty/css-crush/lib/CssCrush/Collection.php
vendored
Normal file
74
include/thirdparty/css-crush/lib/CssCrush/Collection.php
vendored
Normal file
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
/*
|
||||
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class Collection extends Iterator
|
||||
{
|
||||
public $store;
|
||||
|
||||
public function __construct(array $store)
|
||||
{
|
||||
$this->store = $store;
|
||||
}
|
||||
|
||||
public function get($index = null)
|
||||
{
|
||||
return is_int($index) ? $this->store[$index] : $this->store;
|
||||
}
|
||||
|
||||
static public function value($item, $property)
|
||||
{
|
||||
if (strpos($property, '|') !== false) {
|
||||
$filters = explode('|', $property);
|
||||
$property = array_shift($filters);
|
||||
$value = $item->$property;
|
||||
foreach ($filters as $filter) {
|
||||
switch ($filter) {
|
||||
case 'lower':
|
||||
$value = strtolower($value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
return $item->$property;
|
||||
}
|
||||
|
||||
public function filter($filterer, $op = '===')
|
||||
{
|
||||
if (is_array($filterer)) {
|
||||
|
||||
$ops = [
|
||||
'===' => function ($item) use ($filterer) {
|
||||
foreach ($filterer as $property => $value) {
|
||||
if (Collection::value($item, $property) !== $value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
'!==' => function ($item) use ($filterer) {
|
||||
foreach ($filterer as $property => $value) {
|
||||
if (Collection::value($item, $property) === $value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
];
|
||||
|
||||
$callback = $ops[$op];
|
||||
}
|
||||
elseif (is_callable($filterer)) {
|
||||
$callback = $filterer;
|
||||
}
|
||||
|
||||
if (isset($callback)) {
|
||||
$this->store = array_filter($this->store, $callback);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
472
include/thirdparty/css-crush/lib/CssCrush/Color.php
vendored
Normal file
472
include/thirdparty/css-crush/lib/CssCrush/Color.php
vendored
Normal file
|
@ -0,0 +1,472 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Colour parsing and conversion.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class Color
|
||||
{
|
||||
protected static $minifyableKeywords;
|
||||
|
||||
public static function getKeywords()
|
||||
{
|
||||
static $namedColors;
|
||||
if (! isset($namedColors)) {
|
||||
if ($colors = Util::parseIni(Crush::$dir . '/misc/color-keywords.ini')) {
|
||||
foreach ($colors as $name => $rgb) {
|
||||
$namedColors[$name] = array_map('floatval', explode(',', $rgb)) + [0, 0, 0, 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isset(Crush::$process->colorKeywords) ? Crush::$process->colorKeywords : $namedColors;
|
||||
}
|
||||
|
||||
public static function getMinifyableKeywords()
|
||||
{
|
||||
if (! isset(self::$minifyableKeywords)) {
|
||||
|
||||
// If color name is longer than 4 and less than 8 test to see if its hex
|
||||
// representation could be shortened.
|
||||
$keywords = self::getKeywords();
|
||||
|
||||
foreach ($keywords as $name => $rgba) {
|
||||
$name_len = strlen($name);
|
||||
if ($name_len < 5) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$hex = self::rgbToHex($rgba);
|
||||
|
||||
if ($name_len > 7) {
|
||||
self::$minifyableKeywords[$name] = $hex;
|
||||
}
|
||||
else {
|
||||
if (preg_match(Regex::$patt->cruftyHex, $hex)) {
|
||||
self::$minifyableKeywords[$name] = $hex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self::$minifyableKeywords;
|
||||
}
|
||||
|
||||
public static function parse($str)
|
||||
{
|
||||
if ($test = Color::test($str)) {
|
||||
$color = $test['value'];
|
||||
$type = $test['type'];
|
||||
}
|
||||
else {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$rgba = false;
|
||||
|
||||
switch ($type) {
|
||||
|
||||
case 'hex':
|
||||
$rgba = Color::hexToRgb($color);
|
||||
break;
|
||||
|
||||
case 'rgb':
|
||||
case 'rgba':
|
||||
case 'hsl':
|
||||
case 'hsla':
|
||||
$function = $type;
|
||||
$vals = substr($color, strlen($function) + 1); // Trim function name and start paren.
|
||||
$vals = substr($vals, 0, strlen($vals) - 1); // Trim end paren.
|
||||
$vals = array_map('trim', explode(',', $vals)); // Explode to array of arguments.
|
||||
|
||||
// Always set the alpha channel.
|
||||
$vals[3] = isset($vals[3]) ? floatval($vals[3]) : 1;
|
||||
|
||||
if (strpos($function, 'rgb') === 0) {
|
||||
$rgba = Color::normalizeCssRgb($vals);
|
||||
}
|
||||
else {
|
||||
$rgba = Color::cssHslToRgb($vals);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'keyword':
|
||||
$keywords = self::getKeywords();
|
||||
$rgba = $keywords[$color];
|
||||
break;
|
||||
}
|
||||
|
||||
return $rgba;
|
||||
}
|
||||
|
||||
public static function test($str)
|
||||
{
|
||||
static $color_patt;
|
||||
if (! $color_patt) {
|
||||
$color_patt = Regex::make('~^(
|
||||
\#(?={{hex}}{3}) |
|
||||
\#(?={{hex}}{6}) |
|
||||
rgba?(?=\() |
|
||||
hsla?(?=\()
|
||||
)~ixS');
|
||||
}
|
||||
|
||||
$color_test = [];
|
||||
$str = strtolower(trim($str));
|
||||
|
||||
// First match a hex value or the start of a function.
|
||||
if (preg_match($color_patt, $str, $m)) {
|
||||
|
||||
$type_match = $m[1];
|
||||
|
||||
switch ($type_match) {
|
||||
case '#':
|
||||
$color_test['type'] = 'hex';
|
||||
break;
|
||||
|
||||
case 'hsl':
|
||||
case 'hsla':
|
||||
case 'rgb':
|
||||
case 'rgba':
|
||||
$color_test['type'] = $type_match;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Secondly try to match a color keyword.
|
||||
else {
|
||||
$keywords = self::getKeywords();
|
||||
if (isset($keywords[$str])) {
|
||||
$color_test['type'] = 'keyword';
|
||||
}
|
||||
}
|
||||
|
||||
if ($color_test) {
|
||||
$color_test['value'] = $str;
|
||||
}
|
||||
|
||||
return $color_test ? $color_test : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* http://mjijackson.com/2008/02/
|
||||
* rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
|
||||
*
|
||||
* Converts an RGB color value to HSL. Conversion formula
|
||||
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
|
||||
* Assumes r, g, and b are contained in the set [0, 255] and
|
||||
* returns h, s, and l in the set [0, 1].
|
||||
*/
|
||||
public static function rgbToHsl(array $rgba)
|
||||
{
|
||||
list($r, $g, $b, $a) = $rgba;
|
||||
$r /= 255;
|
||||
$g /= 255;
|
||||
$b /= 255;
|
||||
$max = max($r, $g, $b);
|
||||
$min = min($r, $g, $b);
|
||||
$h = 0;
|
||||
$s = 0;
|
||||
$l = ($max + $min) / 2;
|
||||
|
||||
if ($max == $min) {
|
||||
$h = $s = 0;
|
||||
}
|
||||
else {
|
||||
$d = $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 [$h, $s, $l, $a];
|
||||
}
|
||||
|
||||
/**
|
||||
* http://mjijackson.com/2008/02/
|
||||
* rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
|
||||
*
|
||||
* Converts an HSL color value to RGB. Conversion formula
|
||||
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
|
||||
* Assumes h, s, and l are contained in the set [0, 1] and
|
||||
* returns r, g, and b in the set [0, 255].
|
||||
*/
|
||||
public static function hslToRgb(array $hsla)
|
||||
{
|
||||
// Populate unspecified alpha value.
|
||||
if (! isset($hsla[3])) {
|
||||
$hsla[3] = 1;
|
||||
}
|
||||
|
||||
list($h, $s, $l, $a) = $hsla;
|
||||
$r = 0;
|
||||
$g = 0;
|
||||
$b = 0;
|
||||
if ($s == 0) {
|
||||
$r = $g = $b = $l;
|
||||
}
|
||||
else {
|
||||
$q = $l < 0.5 ? $l * (1 + $s) : $l + $s - $l * $s;
|
||||
$p = 2 * $l - $q;
|
||||
$r = self::hueToRgb($p, $q, $h + 1 / 3);
|
||||
$g = self::hueToRgb($p, $q, $h);
|
||||
$b = self::hueToRgb($p, $q, $h - 1 / 3);
|
||||
}
|
||||
|
||||
return [round($r * 255), round($g * 255), round($b * 255), $a];
|
||||
}
|
||||
|
||||
// Convert percentages to points (0-255).
|
||||
public static function normalizeCssRgb(array $rgba)
|
||||
{
|
||||
foreach ($rgba as &$val) {
|
||||
if (strpos($val, '%') !== false) {
|
||||
$val = str_replace('%', '', $val);
|
||||
$val = round($val * 2.55);
|
||||
}
|
||||
}
|
||||
|
||||
return $rgba;
|
||||
}
|
||||
|
||||
public static function cssHslToRgb(array $hsla)
|
||||
{
|
||||
// Populate unspecified alpha value.
|
||||
if (! isset($hsla[3])) {
|
||||
$hsla[3] = 1;
|
||||
}
|
||||
|
||||
// Alpha is carried over.
|
||||
$a = array_pop($hsla);
|
||||
|
||||
// Normalize the hue degree value then convert to float.
|
||||
$h = array_shift($hsla);
|
||||
$h = $h % 360;
|
||||
if ($h < 0) {
|
||||
$h = 360 + $h;
|
||||
}
|
||||
$h = $h / 360;
|
||||
|
||||
// Convert saturation and lightness to floats.
|
||||
foreach ($hsla as &$val) {
|
||||
$val = str_replace('%', '', $val);
|
||||
$val /= 100;
|
||||
}
|
||||
list($s, $l) = $hsla;
|
||||
|
||||
return self::hslToRgb([$h, $s, $l, $a]);
|
||||
}
|
||||
|
||||
public static function hueToRgb($p, $q, $t)
|
||||
{
|
||||
if ($t < 0) $t += 1;
|
||||
if ($t > 1) $t -= 1;
|
||||
if ($t < 1/6) return $p + ($q - $p) * 6 * $t;
|
||||
if ($t < 1/2) return $q;
|
||||
if ($t < 2/3) return $p + ($q - $p) * (2 / 3 - $t) * 6;
|
||||
return $p;
|
||||
}
|
||||
|
||||
public static function rgbToHex(array $rgba)
|
||||
{
|
||||
// Drop alpha component.
|
||||
if (isset($rgba[3])) {
|
||||
array_pop($rgba);
|
||||
}
|
||||
|
||||
$hex_out = '#';
|
||||
foreach ($rgba as $val) {
|
||||
$hex_out .= str_pad(dechex($val), 2, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
return $hex_out;
|
||||
}
|
||||
|
||||
public static function hexToRgb($hex)
|
||||
{
|
||||
$hex = substr($hex, 1);
|
||||
|
||||
// Handle shortened format.
|
||||
if (strlen($hex) === 3) {
|
||||
$long_hex = [];
|
||||
foreach (str_split($hex) as $val) {
|
||||
$long_hex[] = $val . $val;
|
||||
}
|
||||
$hex = $long_hex;
|
||||
}
|
||||
else {
|
||||
$hex = str_split($hex, 2);
|
||||
}
|
||||
|
||||
// Return RGBa
|
||||
$rgba = array_map('hexdec', $hex);
|
||||
$rgba[] = 1;
|
||||
|
||||
return $rgba;
|
||||
}
|
||||
|
||||
public static function colorAdjust($str, array $adjustments)
|
||||
{
|
||||
$hsla = new Color($str, true);
|
||||
|
||||
// On failure to parse return input.
|
||||
return $hsla->isValid ? $hsla->adjust($adjustments)->__toString() : $str;
|
||||
}
|
||||
|
||||
public static function colorSplit($str)
|
||||
{
|
||||
if ($test = Color::test($str)) {
|
||||
$color = $test['value'];
|
||||
$type = $test['type'];
|
||||
}
|
||||
else {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// If non-alpha color return early.
|
||||
if (! in_array($type, ['hsla', 'rgba'])) {
|
||||
|
||||
return [$color, 1];
|
||||
}
|
||||
|
||||
// Strip all whitespace.
|
||||
$color = preg_replace('~\s+~', '', $color);
|
||||
|
||||
// Extract alpha component if one is matched.
|
||||
$opacity = 1;
|
||||
if (preg_match(
|
||||
Regex::make('~^(rgb|hsl)a\(({{number}}%?,{{number}}%?,{{number}}%?),({{number}})\)$~i'),
|
||||
$color,
|
||||
$m)
|
||||
) {
|
||||
$opacity = floatval($m[3]);
|
||||
$color = "$m[1]($m[2])";
|
||||
}
|
||||
|
||||
return [$color, $opacity];
|
||||
}
|
||||
|
||||
|
||||
#############################
|
||||
# Instances.
|
||||
|
||||
protected $value;
|
||||
protected $hslColorSpace;
|
||||
protected $namedComponents = [
|
||||
'red' => 0,
|
||||
'green' => 1,
|
||||
'blue' => 2,
|
||||
'alpha' => 3,
|
||||
];
|
||||
public $isValid;
|
||||
|
||||
public function __construct($color, $useHslColorSpace = false)
|
||||
{
|
||||
$this->value = is_array($color) ? $color : self::parse($color);
|
||||
$this->isValid = ! empty($this->value);
|
||||
if ($useHslColorSpace && $this->isValid) {
|
||||
$this->toHsl();
|
||||
}
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
// For opaque colors return hex notation as it's the most compact.
|
||||
if ($this->getComponent('alpha') == 1) {
|
||||
|
||||
return $this->getHex();
|
||||
}
|
||||
|
||||
// R, G and B components must be integers.
|
||||
$components = [];
|
||||
foreach (($this->hslColorSpace ? $this->getRgb() : $this->value) as $index => $component) {
|
||||
$components[] = ($index === 3) ? $component : min(round($component), 255);
|
||||
}
|
||||
|
||||
return 'rgba(' . implode(',', $components) . ')';
|
||||
}
|
||||
|
||||
public function toRgb()
|
||||
{
|
||||
if ($this->hslColorSpace) {
|
||||
$this->hslColorSpace = false;
|
||||
$this->value = self::hslToRgb($this->value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toHsl()
|
||||
{
|
||||
if (! $this->hslColorSpace) {
|
||||
$this->hslColorSpace = true;
|
||||
$this->value = self::rgbToHsl($this->value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHex()
|
||||
{
|
||||
return self::rgbToHex($this->getRgb());
|
||||
}
|
||||
|
||||
public function getHsl()
|
||||
{
|
||||
return ! $this->hslColorSpace ? self::rgbToHsl($this->value) : $this->value;
|
||||
}
|
||||
|
||||
public function getRgb()
|
||||
{
|
||||
return $this->hslColorSpace ? self::hslToRgb($this->value) : $this->value;
|
||||
}
|
||||
|
||||
public function getComponent($index)
|
||||
{
|
||||
$index = isset($this->namedComponents[$index]) ? $this->namedComponents[$index] : $index;
|
||||
return $this->value[$index];
|
||||
}
|
||||
|
||||
public function setComponent($index, $newComponentValue)
|
||||
{
|
||||
$index = isset($this->namedComponents[$index]) ? $this->namedComponents[$index] : $index;
|
||||
$this->value[$index] = is_numeric($newComponentValue) ? $newComponentValue : 0;
|
||||
}
|
||||
|
||||
public function adjust(array $adjustments)
|
||||
{
|
||||
$wasHslColor = $this->hslColorSpace;
|
||||
|
||||
$this->toHsl();
|
||||
|
||||
// Normalize percentage adjustment parameters to floating point numbers.
|
||||
foreach ($adjustments as $index => $val) {
|
||||
|
||||
// Normalize argument.
|
||||
$val = $val ? trim(str_replace('%', '', $val)) : 0;
|
||||
|
||||
if ($val) {
|
||||
// Reduce value to float.
|
||||
$val /= 100;
|
||||
// Update the color component.
|
||||
$this->setComponent($index, max(0, min(1, $this->getComponent($index) + $val)));
|
||||
}
|
||||
}
|
||||
|
||||
return ! $wasHslColor ? $this->toRgb() : $this;
|
||||
}
|
||||
}
|
289
include/thirdparty/css-crush/lib/CssCrush/Crush.php
vendored
Normal file
289
include/thirdparty/css-crush/lib/CssCrush/Crush.php
vendored
Normal file
|
@ -0,0 +1,289 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Core public API.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class Crush
|
||||
{
|
||||
// Global settings.
|
||||
public static $config;
|
||||
|
||||
// The current active process.
|
||||
public static $process;
|
||||
|
||||
// Library root directory.
|
||||
public static $dir;
|
||||
|
||||
public static function init()
|
||||
{
|
||||
self::$dir = dirname(dirname(__DIR__));
|
||||
|
||||
self::$config = new \stdClass();
|
||||
|
||||
self::$config->pluginDirs = [self::$dir . '/plugins'];
|
||||
self::$config->scriptDir = dirname(realpath($_SERVER['SCRIPT_FILENAME']));
|
||||
self::$config->docRoot = self::resolveDocRoot();
|
||||
self::$config->logger = new Logger();
|
||||
self::$config->io = 'CssCrush\IO';
|
||||
|
||||
// Shared resources.
|
||||
self::$config->vars = [];
|
||||
self::$config->aliasesFile = self::$dir . '/aliases.ini';
|
||||
self::$config->aliases = [];
|
||||
self::$config->bareAliases = [
|
||||
'properties' => [],
|
||||
'functions' => [],
|
||||
'function_groups' => [],
|
||||
'declarations' => [],
|
||||
'at-rules' => [],
|
||||
];
|
||||
self::$config->options = new Options();
|
||||
|
||||
require_once self::$dir . '/misc/formatters.php';
|
||||
}
|
||||
|
||||
static protected function resolveDocRoot($doc_root = null)
|
||||
{
|
||||
// Get document_root reference
|
||||
// $_SERVER['DOCUMENT_ROOT'] is unreliable in certain CGI/Apache/IIS setups
|
||||
|
||||
if (! $doc_root) {
|
||||
|
||||
$script_filename = $_SERVER['SCRIPT_FILENAME'];
|
||||
$script_name = $_SERVER['SCRIPT_NAME'];
|
||||
|
||||
if ($script_filename && $script_name) {
|
||||
|
||||
$len_diff = strlen($script_filename) - strlen($script_name);
|
||||
|
||||
// We're comparing the two strings so normalize OS directory separators
|
||||
$script_filename = str_replace('\\', '/', $script_filename);
|
||||
$script_name = str_replace('\\', '/', $script_name);
|
||||
|
||||
// Check $script_filename ends with $script_name
|
||||
if (substr($script_filename, $len_diff) === $script_name) {
|
||||
|
||||
$path = substr($script_filename, 0, $len_diff);
|
||||
$doc_root = realpath($path);
|
||||
}
|
||||
}
|
||||
|
||||
if (! $doc_root) {
|
||||
$doc_root = realpath($_SERVER['DOCUMENT_ROOT']);
|
||||
}
|
||||
|
||||
if (! $doc_root) {
|
||||
warning("Could not get a valid DOCUMENT_ROOT reference.");
|
||||
}
|
||||
}
|
||||
|
||||
return Util::normalizePath($doc_root);
|
||||
}
|
||||
|
||||
public static function loadAssets()
|
||||
{
|
||||
static $called;
|
||||
if ($called) {
|
||||
return;
|
||||
}
|
||||
$called = true;
|
||||
|
||||
if (! self::$config->aliases) {
|
||||
$aliases = self::parseAliasesFile(self::$config->aliasesFile);
|
||||
self::$config->aliases = $aliases ?: self::$config->bareAliases;
|
||||
}
|
||||
}
|
||||
|
||||
public static function plugin($name = null, callable $callback = null)
|
||||
{
|
||||
static $plugins = [];
|
||||
|
||||
if (! $callback) {
|
||||
return isset($plugins[$name]) ? $plugins[$name] : null;
|
||||
}
|
||||
|
||||
$plugins[$name] = $callback;
|
||||
}
|
||||
|
||||
public static function enablePlugin($name)
|
||||
{
|
||||
$plugin = self::plugin($name);
|
||||
if (! $plugin) {
|
||||
$path = self::$dir . "/plugins/$name.php";
|
||||
if (! file_exists($path)) {
|
||||
notice("Plugin '$name' not found.");
|
||||
return;
|
||||
}
|
||||
require_once $path;
|
||||
$plugin = self::plugin($name);
|
||||
}
|
||||
|
||||
$plugin(self::$process);
|
||||
}
|
||||
|
||||
public static function parseAliasesFile($file)
|
||||
{
|
||||
if (! ($tree = Util::parseIni($file, true))) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$regex = Regex::$patt;
|
||||
|
||||
// Some alias groups need further parsing to unpack useful information into the tree.
|
||||
foreach ($tree as $section => $items) {
|
||||
|
||||
if ($section === 'declarations') {
|
||||
|
||||
$store = [];
|
||||
foreach ($items as $prop_val => $aliases) {
|
||||
|
||||
list($prop, $value) = array_map('trim', explode(':', $prop_val));
|
||||
|
||||
foreach ($aliases as &$alias) {
|
||||
|
||||
list($p, $v) = explode(':', $alias);
|
||||
$vendor = null;
|
||||
|
||||
// Try to detect the vendor from property and value in turn.
|
||||
if (
|
||||
preg_match($regex->vendorPrefix, $p, $m)
|
||||
|| preg_match($regex->vendorPrefix, $v, $m)
|
||||
) {
|
||||
$vendor = $m[1];
|
||||
}
|
||||
$alias = [$p, $v, $vendor];
|
||||
}
|
||||
$store[$prop][$value] = $aliases;
|
||||
}
|
||||
$tree['declarations'] = $store;
|
||||
}
|
||||
|
||||
// Function groups.
|
||||
elseif (strpos($section, 'functions.') === 0) {
|
||||
|
||||
$group = substr($section, strlen('functions'));
|
||||
|
||||
$vendor_grouped_aliases = [];
|
||||
foreach ($items as $func_name => $aliases) {
|
||||
|
||||
// Assign group name to the aliasable function.
|
||||
$tree['functions'][$func_name] = $group;
|
||||
|
||||
foreach ($aliases as $alias_func) {
|
||||
|
||||
// Only supporting vendor prefixed aliases, for now.
|
||||
if (preg_match($regex->vendorPrefix, $alias_func, $m)) {
|
||||
|
||||
// We'll cache the function matching regex here.
|
||||
$vendor_grouped_aliases[$m[1]]['find'][] = Regex::make("~{{ LB }}$func_name(?=\()~iS");
|
||||
$vendor_grouped_aliases[$m[1]]['replace'][] = $alias_func;
|
||||
}
|
||||
}
|
||||
}
|
||||
$tree['function_groups'][$group] = $vendor_grouped_aliases;
|
||||
unset($tree[$section]);
|
||||
}
|
||||
}
|
||||
|
||||
$tree += self::$config->bareAliases;
|
||||
|
||||
// Persisting dummy aliases for testing purposes.
|
||||
$tree['properties']['foo'] =
|
||||
$tree['at-rules']['foo'] =
|
||||
$tree['functions']['foo'] = ['-webkit-foo', '-moz-foo', '-ms-foo'];
|
||||
|
||||
return $tree;
|
||||
}
|
||||
|
||||
#############################
|
||||
# Logging and stats.
|
||||
|
||||
public static function printLog()
|
||||
{
|
||||
if (! empty(self::$process->debugLog)) {
|
||||
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
$out = [];
|
||||
foreach (self::$process->debugLog as $item) {
|
||||
$out[] = '<pre>' . htmlspecialchars($item) . '</pre>';
|
||||
}
|
||||
echo implode('<hr>', $out);
|
||||
}
|
||||
else {
|
||||
echo implode(PHP_EOL, self::$process->debugLog), PHP_EOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function runStat()
|
||||
{
|
||||
$process = Crush::$process;
|
||||
|
||||
foreach (func_get_args() as $stat_name) {
|
||||
|
||||
switch ($stat_name) {
|
||||
case 'paths':
|
||||
$process->stat['input_filename'] = $process->input->filename;
|
||||
$process->stat['input_path'] = $process->input->path;
|
||||
$process->stat['output_filename'] = $process->output->filename;
|
||||
$process->stat['output_path'] = $process->output->dir . '/' . $process->output->filename;
|
||||
break;
|
||||
|
||||
case 'vars':
|
||||
$process->stat['vars'] = array_map(function ($item) use ($process) {
|
||||
return $process->tokens->restore($process->functions->apply($item), ['s', 'u', 'p']);
|
||||
}, $process->vars);
|
||||
break;
|
||||
|
||||
case 'compile_time':
|
||||
$process->stat['compile_time'] = microtime(true) - $process->stat['compile_start_time'];
|
||||
unset($process->stat['compile_start_time']);
|
||||
break;
|
||||
|
||||
case 'selector_count':
|
||||
$process->stat['selector_count'] = 0;
|
||||
foreach ($process->tokens->store->r as $rule) {
|
||||
$process->stat['selector_count'] += count($rule->selectors);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'rule_count':
|
||||
$process->stat['rule_count'] = count($process->tokens->store->r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function warning($message, $context = []) {
|
||||
Crush::$process->errors[] = $message;
|
||||
$logger = Crush::$config->logger;
|
||||
if ($logger instanceof Logger) {
|
||||
$message = "[CssCrush] $message";
|
||||
}
|
||||
$logger->warning($message, $context);
|
||||
}
|
||||
|
||||
function notice($message, $context = []) {
|
||||
Crush::$process->warnings[] = $message;
|
||||
$logger = Crush::$config->logger;
|
||||
if ($logger instanceof Logger) {
|
||||
$message = "[CssCrush] $message";
|
||||
}
|
||||
$logger->notice($message, $context);
|
||||
}
|
||||
|
||||
function debug($message, $context = []) {
|
||||
Crush::$config->logger->debug($message, $context);
|
||||
}
|
||||
|
||||
function log($message, $context = [], $type = 'debug') {
|
||||
Crush::$config->logger->$type($message, $context);
|
||||
}
|
||||
|
||||
|
||||
Crush::init();
|
135
include/thirdparty/css-crush/lib/CssCrush/Declaration.php
vendored
Normal file
135
include/thirdparty/css-crush/lib/CssCrush/Declaration.php
vendored
Normal file
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Declaration objects.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class Declaration
|
||||
{
|
||||
public $property;
|
||||
public $canonicalProperty;
|
||||
public $vendor;
|
||||
public $functions;
|
||||
public $value;
|
||||
public $index;
|
||||
public $skip = false;
|
||||
public $important = false;
|
||||
public $custom = false;
|
||||
public $valid = true;
|
||||
|
||||
public function __construct($property, $value, $contextIndex = 0)
|
||||
{
|
||||
// Normalize, but preserve case if a custom property.
|
||||
if (strpos($property, '--') === 0) {
|
||||
$this->custom = true;
|
||||
$this->skip = true;
|
||||
}
|
||||
else {
|
||||
$property = strtolower($property);
|
||||
}
|
||||
|
||||
if ($this->skip = strpos($property, '~') === 0) {
|
||||
$property = substr($property, 1);
|
||||
}
|
||||
|
||||
// Store the canonical property name.
|
||||
// Store the vendor mark if one is present.
|
||||
if (preg_match(Regex::$patt->vendorPrefix, $property, $vendor)) {
|
||||
$canonical_property = $vendor[2];
|
||||
$vendor = $vendor[1];
|
||||
}
|
||||
else {
|
||||
$vendor = null;
|
||||
$canonical_property = $property;
|
||||
}
|
||||
|
||||
// Check for !important.
|
||||
if (($important = stripos($value, '!important')) !== false) {
|
||||
$value = rtrim(substr($value, 0, $important));
|
||||
$this->important = true;
|
||||
}
|
||||
|
||||
Crush::$process->emit('declaration_preprocess', ['property' => &$property, 'value' => &$value]);
|
||||
|
||||
// Reject declarations with empty CSS values.
|
||||
if ($value === false || $value === '') {
|
||||
$this->valid = false;
|
||||
}
|
||||
|
||||
$this->property = $property;
|
||||
$this->canonicalProperty = $canonical_property;
|
||||
$this->vendor = $vendor;
|
||||
$this->index = $contextIndex;
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
if (Crush::$process->minifyOutput) {
|
||||
$whitespace = '';
|
||||
}
|
||||
else {
|
||||
$whitespace = ' ';
|
||||
}
|
||||
$important = $this->important ? "$whitespace!important" : '';
|
||||
|
||||
return "$this->property:$whitespace$this->value$important";
|
||||
}
|
||||
|
||||
/*
|
||||
Execute functions on value.
|
||||
Index functions.
|
||||
*/
|
||||
public function process($parentRule)
|
||||
{
|
||||
static $thisFunction;
|
||||
if (! $thisFunction) {
|
||||
$thisFunction = new Functions(['this' => 'CssCrush\fn__this']);
|
||||
}
|
||||
|
||||
if (! $this->skip) {
|
||||
|
||||
// this() function needs to be called exclusively because it is self referencing.
|
||||
$context = (object) [
|
||||
'rule' => $parentRule
|
||||
];
|
||||
$this->value = $thisFunction->apply($this->value, $context);
|
||||
|
||||
if (isset($parentRule->declarations->data)) {
|
||||
$parentRule->declarations->data += [$this->property => $this->value];
|
||||
}
|
||||
|
||||
$context = (object) [
|
||||
'rule' => $parentRule,
|
||||
'property' => $this->property
|
||||
];
|
||||
$this->value = Crush::$process->functions->apply($this->value, $context);
|
||||
}
|
||||
|
||||
// Whitespace may have been introduced by functions.
|
||||
$this->value = trim($this->value);
|
||||
|
||||
if ($this->value === '') {
|
||||
$this->valid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
$parentRule->declarations->queryData[$this->property] = $this->value;
|
||||
|
||||
$this->indexFunctions();
|
||||
}
|
||||
|
||||
public function indexFunctions()
|
||||
{
|
||||
// Create an index of all regular functions in the value.
|
||||
$functions = [];
|
||||
if (preg_match_all(Regex::$patt->functionTest, $this->value, $m)) {
|
||||
foreach ($m['func_name'] as $fn_name) {
|
||||
$functions[strtolower($fn_name)] = true;
|
||||
}
|
||||
}
|
||||
$this->functions = $functions;
|
||||
}
|
||||
}
|
598
include/thirdparty/css-crush/lib/CssCrush/DeclarationList.php
vendored
Normal file
598
include/thirdparty/css-crush/lib/CssCrush/DeclarationList.php
vendored
Normal file
|
@ -0,0 +1,598 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Declaration lists.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class DeclarationList extends Iterator
|
||||
{
|
||||
public $flattened = true;
|
||||
public $processed = false;
|
||||
protected $rule;
|
||||
|
||||
public $properties = [];
|
||||
public $canonicalProperties = [];
|
||||
|
||||
// Declarations hash table for inter-rule this() referencing.
|
||||
public $data = [];
|
||||
|
||||
// Declarations hash table for external query() referencing.
|
||||
public $queryData = [];
|
||||
|
||||
public function __construct($declarationsString, Rule $rule)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->rule = $rule;
|
||||
$pairs = DeclarationList::parse($declarationsString);
|
||||
|
||||
foreach ($pairs as $index => $pair) {
|
||||
|
||||
list($prop, $value) = $pair;
|
||||
|
||||
// Directives.
|
||||
if ($prop === 'extends') {
|
||||
$this->rule->addExtendSelectors($value);
|
||||
unset($pairs[$index]);
|
||||
}
|
||||
elseif ($prop === 'name') {
|
||||
if (! $this->rule->name) {
|
||||
$this->rule->name = $value;
|
||||
}
|
||||
unset($pairs[$index]);
|
||||
}
|
||||
}
|
||||
|
||||
// Build declaration list.
|
||||
foreach ($pairs as $index => &$pair) {
|
||||
|
||||
list($prop, $value) = $pair;
|
||||
|
||||
if (trim($value) !== '') {
|
||||
|
||||
if ($prop === 'mixin') {
|
||||
$this->flattened = false;
|
||||
$this->store[] = $pair;
|
||||
}
|
||||
else {
|
||||
// Only store to $this->data if the value does not itself make a
|
||||
// this() call to avoid circular references.
|
||||
if (! preg_match(Regex::$patt->thisFunction, $value)) {
|
||||
$this->data[strtolower($prop)] = $value;
|
||||
}
|
||||
$this->add($prop, $value, $index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function add($property, $value, $contextIndex = 0)
|
||||
{
|
||||
$declaration = new Declaration($property, $value, $contextIndex);
|
||||
|
||||
if ($declaration->valid) {
|
||||
|
||||
$this->index($declaration);
|
||||
$this->store[] = $declaration;
|
||||
return $declaration;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function reset(array $declaration_stack)
|
||||
{
|
||||
$this->store = $declaration_stack;
|
||||
|
||||
$this->updateIndex();
|
||||
}
|
||||
|
||||
public function index($declaration)
|
||||
{
|
||||
$property = $declaration->property;
|
||||
|
||||
if (isset($this->properties[$property])) {
|
||||
$this->properties[$property]++;
|
||||
}
|
||||
else {
|
||||
$this->properties[$property] = 1;
|
||||
}
|
||||
$this->canonicalProperties[$declaration->canonicalProperty] = true;
|
||||
}
|
||||
|
||||
public function updateIndex()
|
||||
{
|
||||
$this->properties = [];
|
||||
$this->canonicalProperties = [];
|
||||
|
||||
foreach ($this->store as $declaration) {
|
||||
$this->index($declaration);
|
||||
}
|
||||
}
|
||||
|
||||
public function propertyCount($property)
|
||||
{
|
||||
return isset($this->properties[$property]) ? $this->properties[$property] : 0;
|
||||
}
|
||||
|
||||
public function join($glue = ';')
|
||||
{
|
||||
return implode($glue, $this->store);
|
||||
}
|
||||
|
||||
/*
|
||||
Aliasing.
|
||||
*/
|
||||
public function aliasProperties($vendor_context = null)
|
||||
{
|
||||
$aliased_properties =& Crush::$process->aliases['properties'];
|
||||
|
||||
// Bail early if nothing doing.
|
||||
if (! array_intersect_key($aliased_properties, $this->properties)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$stack = [];
|
||||
$rule_updated = false;
|
||||
$regex = Regex::$patt;
|
||||
|
||||
foreach ($this->store as $declaration) {
|
||||
|
||||
// Check declaration against vendor context.
|
||||
if ($vendor_context && $declaration->vendor && $declaration->vendor !== $vendor_context) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($declaration->skip) {
|
||||
$stack[] = $declaration;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Shim in aliased properties.
|
||||
if (isset($aliased_properties[$declaration->property])) {
|
||||
|
||||
foreach ($aliased_properties[$declaration->property] as $prop_alias) {
|
||||
|
||||
// If an aliased version already exists do not create one.
|
||||
if ($this->propertyCount($prop_alias)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get property alias vendor.
|
||||
preg_match($regex->vendorPrefix, $prop_alias, $alias_vendor);
|
||||
|
||||
// Check against vendor context.
|
||||
if ($vendor_context && $alias_vendor && $alias_vendor[1] !== $vendor_context) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create the aliased declaration.
|
||||
$copy = clone $declaration;
|
||||
$copy->property = $prop_alias;
|
||||
|
||||
// Set the aliased declaration vendor property.
|
||||
$copy->vendor = null;
|
||||
if ($alias_vendor) {
|
||||
$copy->vendor = $alias_vendor[1];
|
||||
}
|
||||
|
||||
$stack[] = $copy;
|
||||
$rule_updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Un-aliased property or a property alias that has been manually set.
|
||||
$stack[] = $declaration;
|
||||
}
|
||||
|
||||
// Re-assign if any updates have been made.
|
||||
if ($rule_updated) {
|
||||
$this->reset($stack);
|
||||
}
|
||||
}
|
||||
|
||||
public function aliasFunctions($vendor_context = null)
|
||||
{
|
||||
$function_aliases =& Crush::$process->aliases['functions'];
|
||||
$function_alias_groups =& Crush::$process->aliases['function_groups'];
|
||||
|
||||
// The new modified set of declarations.
|
||||
$new_set = [];
|
||||
$rule_updated = false;
|
||||
|
||||
// Shim in aliased functions.
|
||||
foreach ($this->store as $declaration) {
|
||||
|
||||
// No functions, bail.
|
||||
if (! $declaration->functions || $declaration->skip) {
|
||||
$new_set[] = $declaration;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get list of functions used in declaration that are alias-able, bail if none.
|
||||
$intersect = array_intersect_key($declaration->functions, $function_aliases);
|
||||
if (! $intersect) {
|
||||
$new_set[] = $declaration;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Keep record of which groups have been applied.
|
||||
$processed_groups = [];
|
||||
|
||||
foreach (array_keys($intersect) as $fn_name) {
|
||||
|
||||
// Store for all the duplicated declarations.
|
||||
$prefixed_copies = [];
|
||||
|
||||
// Grouped function aliases.
|
||||
if ($function_aliases[$fn_name][0] === '.') {
|
||||
|
||||
$group_id = $function_aliases[$fn_name];
|
||||
|
||||
// If this group has been applied we can skip over.
|
||||
if (isset($processed_groups[$group_id])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Mark group as applied.
|
||||
$processed_groups[$group_id] = true;
|
||||
|
||||
$groups =& $function_alias_groups[$group_id];
|
||||
|
||||
foreach ($groups as $group_key => $replacements) {
|
||||
|
||||
// If the declaration is vendor specific only create aliases for the same vendor.
|
||||
if (
|
||||
($declaration->vendor && $group_key !== $declaration->vendor) ||
|
||||
($vendor_context && $group_key !== $vendor_context)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$copy = clone $declaration;
|
||||
|
||||
// Make swaps.
|
||||
$copy->value = preg_replace(
|
||||
$replacements['find'],
|
||||
$replacements['replace'],
|
||||
$copy->value
|
||||
);
|
||||
$prefixed_copies[] = $copy;
|
||||
$rule_updated = true;
|
||||
}
|
||||
|
||||
// Post fixes.
|
||||
if (isset(PostAliasFix::$functions[$group_id])) {
|
||||
call_user_func(PostAliasFix::$functions[$group_id], $prefixed_copies, $group_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Single function aliases.
|
||||
else {
|
||||
foreach ($function_aliases[$fn_name] as $fn_alias) {
|
||||
|
||||
// If the declaration is vendor specific only create aliases for the same vendor.
|
||||
if ($declaration->vendor) {
|
||||
preg_match(Regex::$patt->vendorPrefix, $fn_alias, $m);
|
||||
if (
|
||||
$m[1] !== $declaration->vendor ||
|
||||
($vendor_context && $m[1] !== $vendor_context)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$copy = clone $declaration;
|
||||
|
||||
// Make swaps.
|
||||
$copy->value = preg_replace(
|
||||
Regex::make("~{{ LB }}$fn_name(?=\()~iS"),
|
||||
$fn_alias,
|
||||
$copy->value
|
||||
);
|
||||
$prefixed_copies[] = $copy;
|
||||
$rule_updated = true;
|
||||
}
|
||||
|
||||
// Post fixes.
|
||||
if (isset(PostAliasFix::$functions[$fn_name])) {
|
||||
call_user_func(PostAliasFix::$functions[$fn_name], $prefixed_copies, $fn_name);
|
||||
}
|
||||
}
|
||||
|
||||
$new_set = array_merge($new_set, $prefixed_copies);
|
||||
}
|
||||
$new_set[] = $declaration;
|
||||
}
|
||||
|
||||
// Re-assign if any updates have been made.
|
||||
if ($rule_updated) {
|
||||
$this->reset($new_set);
|
||||
}
|
||||
}
|
||||
|
||||
public function aliasDeclarations($vendor_context = null)
|
||||
{
|
||||
$declaration_aliases =& Crush::$process->aliases['declarations'];
|
||||
|
||||
// First test for the existence of any aliased properties.
|
||||
if (! ($intersect = array_intersect_key($declaration_aliases, $this->properties))) {
|
||||
return;
|
||||
}
|
||||
|
||||
$intersect = array_flip(array_keys($intersect));
|
||||
$new_set = [];
|
||||
$rule_updated = false;
|
||||
|
||||
foreach ($this->store as $declaration) {
|
||||
|
||||
// Check the current declaration property is actually aliased.
|
||||
if (isset($intersect[$declaration->property]) && ! $declaration->skip) {
|
||||
|
||||
// Iterate on the current declaration property for value matches.
|
||||
foreach ($declaration_aliases[$declaration->property] as $value_match => $replacements) {
|
||||
|
||||
// Create new alias declaration if the property and value match.
|
||||
if ($declaration->value === $value_match) {
|
||||
|
||||
foreach ($replacements as $values) {
|
||||
|
||||
// Check the vendor against context.
|
||||
if ($vendor_context && $vendor_context !== $values[2]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the replacement property is null use the original declaration property.
|
||||
$new = new Declaration(
|
||||
! empty($values[0]) ? $values[0] : $declaration->property,
|
||||
$values[1]
|
||||
);
|
||||
$new->important = $declaration->important;
|
||||
$new_set[] = $new;
|
||||
$rule_updated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$new_set[] = $declaration;
|
||||
}
|
||||
|
||||
// Re-assign if any updates have been made.
|
||||
if ($rule_updated) {
|
||||
$this->reset($new_set);
|
||||
}
|
||||
}
|
||||
|
||||
public static function parse($str, $options = [])
|
||||
{
|
||||
$str = Util::stripCommentTokens($str);
|
||||
$lines = preg_split('~\s*;\s*~', $str, null, PREG_SPLIT_NO_EMPTY);
|
||||
|
||||
$options += [
|
||||
'keyed' => false,
|
||||
'ignore_directives' => false,
|
||||
'lowercase_keys' => false,
|
||||
'context' => null,
|
||||
'flatten' => false,
|
||||
'apply_hooks' => false,
|
||||
];
|
||||
|
||||
$pairs = [];
|
||||
|
||||
foreach ($lines as $line) {
|
||||
|
||||
if (! $options['ignore_directives'] && preg_match(Regex::$patt->ruleDirective, $line, $m)) {
|
||||
|
||||
if (! empty($m[1])) {
|
||||
$property = 'mixin';
|
||||
}
|
||||
elseif (! empty($m[2])) {
|
||||
$property = 'extends';
|
||||
}
|
||||
else {
|
||||
$property = 'name';
|
||||
}
|
||||
$value = trim(substr($line, strlen($m[0])));
|
||||
}
|
||||
elseif (($colon_pos = strpos($line, ':')) !== false) {
|
||||
|
||||
$property = trim(substr($line, 0, $colon_pos));
|
||||
$value = trim(substr($line, $colon_pos + 1));
|
||||
|
||||
if ($options['lowercase_keys']) {
|
||||
$property = strtolower($property);
|
||||
}
|
||||
|
||||
if ($options['apply_hooks']) {
|
||||
Crush::$process->emit('declaration_preprocess', [
|
||||
'property' => &$property,
|
||||
'value' => &$value,
|
||||
]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($property === '' || $value === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($property === 'mixin' && $options['flatten']) {
|
||||
$pairs = Mixin::merge($pairs, $value, [
|
||||
'keyed' => $options['keyed'],
|
||||
'context' => $options['context'],
|
||||
]);
|
||||
}
|
||||
elseif ($options['keyed']) {
|
||||
$pairs[$property] = $value;
|
||||
}
|
||||
else {
|
||||
$pairs[] = [$property, $value];
|
||||
}
|
||||
}
|
||||
|
||||
return $pairs;
|
||||
}
|
||||
|
||||
public function flatten()
|
||||
{
|
||||
if ($this->flattened) {
|
||||
return;
|
||||
}
|
||||
|
||||
$newSet = [];
|
||||
foreach ($this->store as $declaration) {
|
||||
if (is_array($declaration) && $declaration[0] === 'mixin') {
|
||||
foreach (Mixin::merge([], $declaration[1], ['context' => $this->rule]) as $mixable) {
|
||||
if ($mixable instanceof Declaration) {
|
||||
$clone = clone $mixable;
|
||||
$clone->index = count($newSet);
|
||||
$newSet[] = $clone;
|
||||
}
|
||||
elseif ($mixable[0] === 'extends') {
|
||||
$this->rule->addExtendSelectors($mixable[1]);
|
||||
}
|
||||
else {
|
||||
$newSet[] = new Declaration($mixable[0], $mixable[1], count($newSet));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$declaration->index = count($newSet);
|
||||
$newSet[] = $declaration;
|
||||
}
|
||||
}
|
||||
|
||||
$this->reset($newSet);
|
||||
$this->flattened = true;
|
||||
}
|
||||
|
||||
public function process()
|
||||
{
|
||||
if ($this->processed) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->store as $index => $declaration) {
|
||||
|
||||
// Execute functions, store as data etc.
|
||||
$declaration->process($this->rule);
|
||||
|
||||
// Drop declaration if value is now empty.
|
||||
if (! $declaration->valid) {
|
||||
unset($this->store[$index]);
|
||||
}
|
||||
}
|
||||
|
||||
// data is done with, reclaim memory.
|
||||
unset($this->data);
|
||||
|
||||
$this->processed = true;
|
||||
}
|
||||
|
||||
public function expandData($dataset, $property)
|
||||
{
|
||||
// Expand shorthand properties to make them available
|
||||
// as data for this() and query().
|
||||
static $expandables = [
|
||||
'margin-top' => 'margin',
|
||||
'margin-right' => 'margin',
|
||||
'margin-bottom' => 'margin',
|
||||
'margin-left' => 'margin',
|
||||
'padding-top' => 'padding',
|
||||
'padding-right' => 'padding',
|
||||
'padding-bottom' => 'padding',
|
||||
'padding-left' => 'padding',
|
||||
'border-top-width' => 'border-width',
|
||||
'border-right-width' => 'border-width',
|
||||
'border-bottom-width' => 'border-width',
|
||||
'border-left-width' => 'border-width',
|
||||
'border-top-left-radius' => 'border-radius',
|
||||
'border-top-right-radius' => 'border-radius',
|
||||
'border-bottom-right-radius' => 'border-radius',
|
||||
'border-bottom-left-radius' => 'border-radius',
|
||||
'border-top-color' => 'border-color',
|
||||
'border-right-color' => 'border-color',
|
||||
'border-bottom-color' => 'border-color',
|
||||
'border-left-color' => 'border-color',
|
||||
];
|
||||
|
||||
$dataset =& $this->{$dataset};
|
||||
$property_group = isset($expandables[$property]) ? $expandables[$property] : null;
|
||||
|
||||
// Bail if property non-expandable or already set.
|
||||
if (! $property_group || isset($dataset[$property]) || ! isset($dataset[$property_group])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the expandable property value.
|
||||
$value = $dataset[$property_group];
|
||||
|
||||
// Top-Right-Bottom-Left "trbl" expandable properties.
|
||||
$trbl_fmt = null;
|
||||
switch ($property_group) {
|
||||
case 'margin':
|
||||
$trbl_fmt = 'margin-%s';
|
||||
break;
|
||||
case 'padding':
|
||||
$trbl_fmt = 'padding-%s';
|
||||
break;
|
||||
case 'border-width':
|
||||
$trbl_fmt = 'border-%s-width';
|
||||
break;
|
||||
case 'border-radius':
|
||||
$trbl_fmt = 'border-%s-radius';
|
||||
break;
|
||||
case 'border-color':
|
||||
$trbl_fmt = 'border-%s-color';
|
||||
break;
|
||||
}
|
||||
if ($trbl_fmt) {
|
||||
$parts = explode(' ', $value);
|
||||
$placeholders = [];
|
||||
|
||||
// 4 values.
|
||||
if (isset($parts[3])) {
|
||||
$placeholders = $parts;
|
||||
}
|
||||
// 3 values.
|
||||
elseif (isset($parts[2])) {
|
||||
$placeholders = [$parts[0], $parts[1], $parts[2], $parts[1]];
|
||||
}
|
||||
// 2 values.
|
||||
elseif (isset($parts[1])) {
|
||||
$placeholders = [$parts[0], $parts[1], $parts[0], $parts[1]];
|
||||
}
|
||||
// 1 value.
|
||||
else {
|
||||
$placeholders = array_pad($placeholders, 4, $parts[0]);
|
||||
}
|
||||
|
||||
// Set positional variants.
|
||||
if ($property_group === 'border-radius') {
|
||||
$positions = [
|
||||
'top-left',
|
||||
'top-right',
|
||||
'bottom-right',
|
||||
'bottom-left',
|
||||
];
|
||||
}
|
||||
else {
|
||||
$positions = [
|
||||
'top',
|
||||
'right',
|
||||
'bottom',
|
||||
'left',
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($positions as $index => $position) {
|
||||
$prop = sprintf($trbl_fmt, $position);
|
||||
$dataset += [$prop => $placeholders[$index]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
36
include/thirdparty/css-crush/lib/CssCrush/EventEmitter.php
vendored
Normal file
36
include/thirdparty/css-crush/lib/CssCrush/EventEmitter.php
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Event Emitter trait.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
trait EventEmitter {
|
||||
|
||||
private $eventEmitterStorage = [];
|
||||
private $eventEmitterUid = 0;
|
||||
|
||||
public function on($event, callable $function)
|
||||
{
|
||||
if (! isset($this->eventEmitterStorage[$event])) {
|
||||
$this->eventEmitterStorage[$event] = [];
|
||||
}
|
||||
|
||||
$id = ++$this->eventEmitterUid;
|
||||
$this->eventEmitterStorage[$event][$id] = $function;
|
||||
|
||||
return function () use ($event, $id) {
|
||||
unset($this->eventEmitterStorage[$event][$id]);
|
||||
};
|
||||
}
|
||||
|
||||
public function emit($event, $data = null)
|
||||
{
|
||||
if (isset($this->eventEmitterStorage[$event])) {
|
||||
foreach ($this->eventEmitterStorage[$event] as $function) {
|
||||
$function($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
37
include/thirdparty/css-crush/lib/CssCrush/ExtendArg.php
vendored
Normal file
37
include/thirdparty/css-crush/lib/CssCrush/ExtendArg.php
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Extend argument objects.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class ExtendArg
|
||||
{
|
||||
public $pointer;
|
||||
public $name;
|
||||
public $raw;
|
||||
public $pseudo;
|
||||
|
||||
public function __construct($name)
|
||||
{
|
||||
$this->name =
|
||||
$this->raw = $name;
|
||||
|
||||
if (! preg_match(Regex::$patt->rooted_ident, $this->name)) {
|
||||
|
||||
// Not a regular name: Some kind of selector so normalize it for later comparison.
|
||||
$this->name =
|
||||
$this->raw = Selector::makeReadable($this->name);
|
||||
|
||||
// If applying the pseudo on output store.
|
||||
if (substr($this->name, -1) === '!') {
|
||||
|
||||
$this->name = rtrim($this->name, ' !');
|
||||
if (preg_match('~\:\:?[\w-]+$~', $this->name, $m)) {
|
||||
$this->pseudo = $m[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
45
include/thirdparty/css-crush/lib/CssCrush/File.php
vendored
Normal file
45
include/thirdparty/css-crush/lib/CssCrush/File.php
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Output file resources.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class File
|
||||
{
|
||||
public $url;
|
||||
public $path;
|
||||
public $process;
|
||||
|
||||
public function __construct(Process $process)
|
||||
{
|
||||
$this->process = $process;
|
||||
$io = $process->io;
|
||||
|
||||
Crush::runStat('paths');
|
||||
|
||||
if ($process->options->cache) {
|
||||
$process->cacheData = $io->getCacheData();
|
||||
if ($io->validateCache()) {
|
||||
$this->url = $io->getOutputUrl();
|
||||
$this->path = $io->getOutputDir() . '/' . $io->getOutputFilename();
|
||||
$process->release();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$string = $process->compile();
|
||||
|
||||
if ($io->write($string)) {
|
||||
$this->url = $io->getOutputUrl();
|
||||
$this->path = $io->getOutputDir() . '/' . $io->getOutputFilename();
|
||||
}
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->url;
|
||||
}
|
||||
}
|
46
include/thirdparty/css-crush/lib/CssCrush/Fragment.php
vendored
Normal file
46
include/thirdparty/css-crush/lib/CssCrush/Fragment.php
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Fragments.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class Fragment extends Template
|
||||
{
|
||||
public $name;
|
||||
|
||||
public function __construct($str, $options = [])
|
||||
{
|
||||
parent::__construct($str, $options);
|
||||
$this->name = $options['name'];
|
||||
}
|
||||
|
||||
public function __invoke(array $args = null, $str = null)
|
||||
{
|
||||
$str = parent::__invoke($args);
|
||||
|
||||
// Flatten all fragment calls within the template string.
|
||||
while (preg_match(Regex::$patt->fragmentInvoke, $str, $m, PREG_OFFSET_CAPTURE)) {
|
||||
|
||||
$name = strtolower($m['name'][0]);
|
||||
$fragment = isset(Crush::$process->fragments[$name]) ? Crush::$process->fragments[$name] : null;
|
||||
|
||||
$replacement = '';
|
||||
$start = $m[0][1];
|
||||
$length = strlen($m[0][0]);
|
||||
|
||||
// Skip over same named fragments to avoid infinite recursion.
|
||||
if ($fragment && $name !== $this->name) {
|
||||
$args = [];
|
||||
if (isset($m['parens'][1])) {
|
||||
$args = Functions::parseArgs($m['parens_content'][0]);
|
||||
}
|
||||
$replacement = $fragment($args);
|
||||
}
|
||||
$str = substr_replace($str, $replacement, $start, $length);
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
}
|
324
include/thirdparty/css-crush/lib/CssCrush/Functions.php
vendored
Normal file
324
include/thirdparty/css-crush/lib/CssCrush/Functions.php
vendored
Normal file
|
@ -0,0 +1,324 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Custom CSS functions
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class Functions
|
||||
{
|
||||
protected static $builtins = [
|
||||
|
||||
// These functions must come first in this order.
|
||||
'query' => 'CssCrush\fn__query',
|
||||
|
||||
// These functions can be any order.
|
||||
'math' => 'CssCrush\fn__math',
|
||||
'hsla-adjust' => 'CssCrush\fn__hsla_adjust',
|
||||
'hsl-adjust' => 'CssCrush\fn__hsl_adjust',
|
||||
'h-adjust' => 'CssCrush\fn__h_adjust',
|
||||
's-adjust' => 'CssCrush\fn__s_adjust',
|
||||
'l-adjust' => 'CssCrush\fn__l_adjust',
|
||||
'a-adjust' => 'CssCrush\fn__a_adjust',
|
||||
];
|
||||
|
||||
public $register = [];
|
||||
|
||||
protected $pattern;
|
||||
|
||||
protected $patternOptions;
|
||||
|
||||
public function __construct($register = [])
|
||||
{
|
||||
$this->register = $register;
|
||||
}
|
||||
|
||||
public function add($name, $callback)
|
||||
{
|
||||
$this->register[$name] = $callback;
|
||||
}
|
||||
|
||||
public function remove($name)
|
||||
{
|
||||
unset($this->register[$name]);
|
||||
}
|
||||
|
||||
public function setPattern($useAll = false)
|
||||
{
|
||||
if ($useAll) {
|
||||
$this->register = self::$builtins + $this->register;
|
||||
}
|
||||
|
||||
$this->pattern = Functions::makePattern(array_keys($this->register));
|
||||
}
|
||||
|
||||
public function apply($str, \stdClass $context = null)
|
||||
{
|
||||
if (strpos($str, '(') === false) {
|
||||
return $str;
|
||||
}
|
||||
|
||||
if (! $this->pattern) {
|
||||
$this->setPattern();
|
||||
}
|
||||
|
||||
if (! preg_match($this->pattern, $str)) {
|
||||
return $str;
|
||||
}
|
||||
|
||||
$matches = Regex::matchAll($this->pattern, $str);
|
||||
|
||||
while ($match = array_pop($matches)) {
|
||||
|
||||
if (isset($match['function']) && $match['function'][1] !== -1) {
|
||||
list($function, $offset) = $match['function'];
|
||||
}
|
||||
else {
|
||||
list($function, $offset) = $match['simple_function'];
|
||||
}
|
||||
|
||||
if (! preg_match(Regex::$patt->parens, $str, $parens, PREG_OFFSET_CAPTURE, $offset)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$openingParen = $parens[0][1];
|
||||
$closingParen = $openingParen + strlen($parens[0][0]);
|
||||
$rawArgs = trim($parens['parens_content'][0]);
|
||||
|
||||
// Update the context function identifier.
|
||||
if ($context) {
|
||||
$context->function = $function;
|
||||
}
|
||||
|
||||
$returns = '';
|
||||
if (isset($this->register[$function])) {
|
||||
$fn = $this->register[$function];
|
||||
if (is_array($fn) && !empty($fn['parse_args'])) {
|
||||
$returns = $fn['callback'](self::parseArgs($rawArgs), $context);
|
||||
}
|
||||
else {
|
||||
$returns = $fn($rawArgs, $context);
|
||||
}
|
||||
}
|
||||
|
||||
$str = substr_replace($str, $returns, $offset, $closingParen - $offset);
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
|
||||
#############################
|
||||
# API and helpers.
|
||||
|
||||
public static function parseArgs($input, $allowSpaceDelim = false)
|
||||
{
|
||||
$options = [];
|
||||
if ($allowSpaceDelim) {
|
||||
$options['regex'] = Regex::$patt->argListSplit;
|
||||
}
|
||||
|
||||
return Util::splitDelimList($input, $options);
|
||||
}
|
||||
|
||||
/*
|
||||
Quick argument list parsing for functions that take 1 or 2 arguments
|
||||
with the proviso the first argument is an ident.
|
||||
*/
|
||||
public static function parseArgsSimple($input)
|
||||
{
|
||||
return preg_split(Regex::$patt->argListSplit, $input, 2);
|
||||
}
|
||||
|
||||
public static function makePattern($functionNames)
|
||||
{
|
||||
$idents = [];
|
||||
$nonIdents = [];
|
||||
|
||||
foreach ($functionNames as $functionName) {
|
||||
if (preg_match(Regex::$patt->ident, $functionName[0])) {
|
||||
$idents[] = preg_quote($functionName);
|
||||
}
|
||||
else {
|
||||
$nonIdents[] = preg_quote($functionName);
|
||||
}
|
||||
}
|
||||
|
||||
if ($idents) {
|
||||
$idents = '{{ LB }}-?(?<function>' . implode('|', $idents) . ')';
|
||||
}
|
||||
if ($nonIdents) {
|
||||
$nonIdents = '(?<simple_function>' . implode('|', $nonIdents) . ')';
|
||||
}
|
||||
|
||||
if ($idents && $nonIdents) {
|
||||
$patt = "(?:$idents|$nonIdents)";
|
||||
}
|
||||
elseif ($idents) {
|
||||
$patt = $idents;
|
||||
}
|
||||
elseif ($nonIdents) {
|
||||
$patt = $nonIdents;
|
||||
}
|
||||
|
||||
return Regex::make("~$patt\(~iS");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#############################
|
||||
# Stock CSS functions.
|
||||
|
||||
function fn__math($input) {
|
||||
|
||||
list($expression, $unit) = array_pad(Functions::parseArgs($input), 2, '');
|
||||
|
||||
// Swap in math constants.
|
||||
$expression = preg_replace(
|
||||
['~\bpi\b~i'],
|
||||
[M_PI],
|
||||
$expression);
|
||||
|
||||
// If no unit is specified scan expression.
|
||||
if (! $unit) {
|
||||
$numPatt = Regex::$classes->number;
|
||||
if (preg_match("~\b{$numPatt}(?<unit>[A-Za-z]{2,4}\b|%)~", $expression, $m)) {
|
||||
$unit = $m['unit'];
|
||||
}
|
||||
}
|
||||
|
||||
// Filter expression so it's just characters necessary for simple math.
|
||||
$expression = preg_replace("~[^.0-9/*()+-]~S", '', $expression);
|
||||
|
||||
$evalExpression = "return $expression;";
|
||||
$result = false;
|
||||
|
||||
if (class_exists('\\ParseError')) {
|
||||
try {
|
||||
$result = @eval($evalExpression);
|
||||
}
|
||||
catch (\Error $e) {}
|
||||
}
|
||||
else {
|
||||
$result = @eval($evalExpression);
|
||||
}
|
||||
|
||||
return ($result === false ? 0 : round($result, 5)) . $unit;
|
||||
}
|
||||
|
||||
function fn__hsla_adjust($input) {
|
||||
list($color, $h, $s, $l, $a) = array_pad(Functions::parseArgs($input, true), 5, 0);
|
||||
return Color::test($color) ? Color::colorAdjust($color, [$h, $s, $l, $a]) : '';
|
||||
}
|
||||
|
||||
function fn__hsl_adjust($input) {
|
||||
list($color, $h, $s, $l) = array_pad(Functions::parseArgs($input, true), 4, 0);
|
||||
return Color::test($color) ? Color::colorAdjust($color, [$h, $s, $l, 0]) : '';
|
||||
}
|
||||
|
||||
function fn__h_adjust($input) {
|
||||
list($color, $h) = array_pad(Functions::parseArgs($input, true), 2, 0);
|
||||
return Color::test($color) ? Color::colorAdjust($color, [$h, 0, 0, 0]) : '';
|
||||
}
|
||||
|
||||
function fn__s_adjust($input) {
|
||||
list($color, $s) = array_pad(Functions::parseArgs($input, true), 2, 0);
|
||||
return Color::test($color) ? Color::colorAdjust($color, [0, $s, 0, 0]) : '';
|
||||
}
|
||||
|
||||
function fn__l_adjust($input) {
|
||||
list($color, $l) = array_pad(Functions::parseArgs($input, true), 2, 0);
|
||||
return Color::test($color) ? Color::colorAdjust($color, [0, 0, $l, 0]) : '';
|
||||
}
|
||||
|
||||
function fn__a_adjust($input) {
|
||||
list($color, $a) = array_pad(Functions::parseArgs($input, true), 2, 0);
|
||||
return Color::test($color) ? Color::colorAdjust($color, [0, 0, 0, $a]) : '';
|
||||
}
|
||||
|
||||
function fn__this($input, $context) {
|
||||
|
||||
$args = Functions::parseArgsSimple($input);
|
||||
$property = $args[0];
|
||||
|
||||
// Function relies on a context rule, bail if none.
|
||||
if (! isset($context->rule)) {
|
||||
return '';
|
||||
}
|
||||
$rule = $context->rule;
|
||||
|
||||
$rule->declarations->expandData('data', $property);
|
||||
|
||||
if (isset($rule->declarations->data[$property])) {
|
||||
|
||||
return $rule->declarations->data[$property];
|
||||
}
|
||||
|
||||
// Fallback value.
|
||||
elseif (isset($args[1])) {
|
||||
|
||||
return $args[1];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function fn__query($input, $context) {
|
||||
|
||||
$args = Functions::parseArgs($input);
|
||||
|
||||
// Context property is required.
|
||||
if (! count($args) || ! isset($context->property)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
list($target, $property, $fallback) = $args + [null, $context->property, null];
|
||||
|
||||
if (strtolower($property) === 'default') {
|
||||
$property = $context->property;
|
||||
}
|
||||
|
||||
if (! preg_match(Regex::$patt->rooted_ident, $target)) {
|
||||
$target = Selector::makeReadable($target);
|
||||
}
|
||||
|
||||
$targetRule = null;
|
||||
$references =& Crush::$process->references;
|
||||
|
||||
switch (strtolower($target)) {
|
||||
case 'parent':
|
||||
$targetRule = $context->rule->parent;
|
||||
break;
|
||||
case 'previous':
|
||||
$targetRule = $context->rule->previous;
|
||||
break;
|
||||
case 'next':
|
||||
$targetRule = $context->rule->next;
|
||||
break;
|
||||
case 'top':
|
||||
$targetRule = $context->rule->parent;
|
||||
while ($targetRule && $targetRule->parent && $targetRule = $targetRule->parent);
|
||||
break;
|
||||
default:
|
||||
if (isset($references[$target])) {
|
||||
$targetRule = $references[$target];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$result = '';
|
||||
if ($targetRule) {
|
||||
$targetRule->declarations->process();
|
||||
$targetRule->declarations->expandData('queryData', $property);
|
||||
if (isset($targetRule->declarations->queryData[$property])) {
|
||||
$result = $targetRule->declarations->queryData[$property];
|
||||
}
|
||||
}
|
||||
|
||||
if ($result === '' && isset($fallback)) {
|
||||
$result = $fallback;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
228
include/thirdparty/css-crush/lib/CssCrush/IO.php
vendored
Normal file
228
include/thirdparty/css-crush/lib/CssCrush/IO.php
vendored
Normal file
|
@ -0,0 +1,228 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Interface for writing files, retrieving files and checking caches
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class IO
|
||||
{
|
||||
protected $process;
|
||||
|
||||
public function __construct(Process $process)
|
||||
{
|
||||
$this->process = $process;
|
||||
}
|
||||
|
||||
public function init()
|
||||
{
|
||||
$this->process->cacheFile = "{$this->process->output->dir}/.csscrush";
|
||||
}
|
||||
|
||||
public function getOutputDir()
|
||||
{
|
||||
$outputDir = $this->process->options->output_dir;
|
||||
|
||||
return $outputDir ? $outputDir : $this->process->input->dir;
|
||||
}
|
||||
|
||||
public function getOutputFilename()
|
||||
{
|
||||
$options = $this->process->options;
|
||||
|
||||
$inputBasename = basename($this->process->input->filename, '.css');
|
||||
$outputBasename = $inputBasename;
|
||||
|
||||
if (! empty($options->output_file)) {
|
||||
$outputBasename = basename($options->output_file, '.css');
|
||||
}
|
||||
|
||||
if ($this->process->input->dir === $this->getOutputDir() && $inputBasename === $outputBasename) {
|
||||
$outputBasename .= '.crush';
|
||||
}
|
||||
|
||||
return "$outputBasename.css";
|
||||
}
|
||||
|
||||
public function getOutputUrl()
|
||||
{
|
||||
$process = $this->process;
|
||||
$options = $process->options;
|
||||
$filename = $process->output->filename;
|
||||
|
||||
$url = $process->output->dirUrl . '/' . $filename;
|
||||
|
||||
// Make URL relative if the input path was relative.
|
||||
$input_path = new Url($process->input->raw);
|
||||
if ($input_path->isRelative) {
|
||||
$url = Util::getLinkBetweenPaths(Crush::$config->scriptDir, $process->output->dir) . $filename;
|
||||
}
|
||||
|
||||
// Optional query-string timestamp.
|
||||
if ($options->versioning !== false) {
|
||||
$url .= '?';
|
||||
if (isset($process->cacheData[$filename]['datem_sum'])) {
|
||||
$url .= $process->cacheData[$filename]['datem_sum'];
|
||||
}
|
||||
else {
|
||||
$url .= time();
|
||||
}
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
public function validateCache()
|
||||
{
|
||||
$process = $this->process;
|
||||
$options = $process->options;
|
||||
$input = $process->input;
|
||||
|
||||
$dir = $this->getOutputDir();
|
||||
$filename = $this->getOutputFilename();
|
||||
$path = "$dir/$filename";
|
||||
|
||||
if (! file_exists($path)) {
|
||||
debug("File '$path' not cached.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! isset($process->cacheData[$filename])) {
|
||||
debug('Cached file exists but is not registered.');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$data =& $process->cacheData[$filename];
|
||||
|
||||
// Make stack of file mtimes starting with the input file.
|
||||
$file_sums = [$input->mtime];
|
||||
foreach ($data['imports'] as $import_file) {
|
||||
|
||||
// Check if this is docroot relative or input dir relative.
|
||||
$root = strpos($import_file, '/') === 0 ? $process->docRoot : $input->dir;
|
||||
$import_filepath = realpath($root) . "/$import_file";
|
||||
|
||||
if (file_exists($import_filepath)) {
|
||||
$file_sums[] = filemtime($import_filepath);
|
||||
}
|
||||
else {
|
||||
// File has been moved, remove old file and skip to compile.
|
||||
debug('Recompiling - an import file has been moved.');
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$files_changed = $data['datem_sum'] != array_sum($file_sums);
|
||||
if ($files_changed) {
|
||||
debug('Files have been modified. Recompiling.');
|
||||
}
|
||||
|
||||
// Compare runtime options and cached options for differences.
|
||||
// Cast because the cached options may be a \stdClass if an IO adapter has been used.
|
||||
$options_changed = false;
|
||||
$cached_options = (array) $data['options'];
|
||||
$active_options = $options->get();
|
||||
foreach ($cached_options as $key => &$value) {
|
||||
if (isset($active_options[$key]) && $active_options[$key] !== $value) {
|
||||
debug('Options have been changed. Recompiling.');
|
||||
$options_changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $options_changed && ! $files_changed) {
|
||||
debug("Files and options have not been modified, returning cached file.");
|
||||
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
$data['datem_sum'] = array_sum($file_sums);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getCacheData()
|
||||
{
|
||||
$process = $this->process;
|
||||
|
||||
if (file_exists($process->cacheFile) && $process->cacheData) {
|
||||
|
||||
// Already loaded and config file exists in the current directory
|
||||
return;
|
||||
}
|
||||
|
||||
$cache_data_exists = file_exists($process->cacheFile);
|
||||
$cache_data_file_is_writable = $cache_data_exists ? is_writable($process->cacheFile) : false;
|
||||
$cache_data = [];
|
||||
|
||||
if (
|
||||
$cache_data_exists &&
|
||||
$cache_data_file_is_writable &&
|
||||
$cache_data = json_decode(file_get_contents($process->cacheFile), true)
|
||||
) {
|
||||
// Successfully loaded config file.
|
||||
debug('Cache data loaded.');
|
||||
}
|
||||
else {
|
||||
// Config file may exist but not be writable (may not be visible in some ftp situations?)
|
||||
if ($cache_data_exists) {
|
||||
if (! @unlink($process->cacheFile)) {
|
||||
notice('Could not delete cache data file.');
|
||||
}
|
||||
}
|
||||
else {
|
||||
debug('Creating cache data file.');
|
||||
}
|
||||
Util::filePutContents($process->cacheFile, json_encode([]), __METHOD__);
|
||||
}
|
||||
|
||||
return $cache_data;
|
||||
}
|
||||
|
||||
public function saveCacheData()
|
||||
{
|
||||
$process = $this->process;
|
||||
|
||||
debug('Saving config.');
|
||||
|
||||
Util::filePutContents($process->cacheFile, json_encode($process->cacheData, JSON_PRETTY_PRINT), __METHOD__);
|
||||
}
|
||||
|
||||
public function write(StringObject $string)
|
||||
{
|
||||
$process = $this->process;
|
||||
|
||||
$dir = $this->getOutputDir();
|
||||
$filename = $this->getOutputFilename();
|
||||
$sourcemapFilename = "$filename.map";
|
||||
|
||||
if ($process->sourceMap) {
|
||||
$string->append($process->newline . "/*# sourceMappingURL=$sourcemapFilename */");
|
||||
}
|
||||
|
||||
if (Util::filePutContents("$dir/$filename", $string, __METHOD__)) {
|
||||
|
||||
if ($process->sourceMap) {
|
||||
Util::filePutContents("$dir/$sourcemapFilename",
|
||||
json_encode($process->sourceMap, JSON_PRETTY_PRINT), __METHOD__);
|
||||
}
|
||||
|
||||
if ($process->options->stat_dump) {
|
||||
$statFile = is_string($process->options->stat_dump) ?
|
||||
$process->options->stat_dump : "$dir/$filename.json";
|
||||
|
||||
$GLOBALS['CSSCRUSH_STAT_FILE'] = $statFile;
|
||||
Util::filePutContents($statFile, json_encode(csscrush_stat(), JSON_PRETTY_PRINT), __METHOD__);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
48
include/thirdparty/css-crush/lib/CssCrush/IO/Watch.php
vendored
Normal file
48
include/thirdparty/css-crush/lib/CssCrush/IO/Watch.php
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* IO class for command line file watching.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush\IO;
|
||||
|
||||
use CssCrush\Crush;
|
||||
use CssCrush\IO;
|
||||
|
||||
class Watch extends IO
|
||||
{
|
||||
public static $cacheData = [];
|
||||
|
||||
public function getOutputFileName()
|
||||
{
|
||||
$process = $this->process;
|
||||
$options = $process->options;
|
||||
|
||||
$input_basename = $output_basename = basename($process->input->filename, '.css');
|
||||
|
||||
if (! empty($options->output_file)) {
|
||||
$output_basename = basename($options->output_file, '.css');
|
||||
}
|
||||
|
||||
$suffix = '.crush';
|
||||
if (($process->input->dir !== $process->output->dir) || ($input_basename !== $output_basename)) {
|
||||
$suffix = '';
|
||||
}
|
||||
|
||||
return "$output_basename$suffix.css";
|
||||
}
|
||||
|
||||
public function getCacheData()
|
||||
{
|
||||
// Clear results from earlier processes.
|
||||
clearstatcache();
|
||||
$this->process->cacheData = [];
|
||||
|
||||
return self::$cacheData;
|
||||
}
|
||||
|
||||
public function saveCacheData()
|
||||
{
|
||||
self::$cacheData = $this->process->cacheData;
|
||||
}
|
||||
}
|
378
include/thirdparty/css-crush/lib/CssCrush/Importer.php
vendored
Normal file
378
include/thirdparty/css-crush/lib/CssCrush/Importer.php
vendored
Normal file
|
@ -0,0 +1,378 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Recursive file importing
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class Importer
|
||||
{
|
||||
protected $process;
|
||||
|
||||
public function __construct(Process $process)
|
||||
{
|
||||
$this->process = $process;
|
||||
}
|
||||
|
||||
public function collate()
|
||||
{
|
||||
$process = $this->process;
|
||||
$options = $process->options;
|
||||
$regex = Regex::$patt;
|
||||
$input = $process->input;
|
||||
|
||||
$str = '';
|
||||
|
||||
// Keep track of all import file info for cache data.
|
||||
$mtimes = [];
|
||||
$filenames = [];
|
||||
|
||||
// Resolve main input; a string of css or a file.
|
||||
if (isset($input->string)) {
|
||||
$str .= $input->string;
|
||||
$process->sources[] = 'Inline CSS';
|
||||
}
|
||||
else {
|
||||
$str .= file_get_contents($input->path);
|
||||
$process->sources[] = $input->path;
|
||||
}
|
||||
|
||||
// If there's a parsing error go no further.
|
||||
if (! $this->prepareImport($str)) {
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
// This may be set non-zero during the script if an absolute @import URL is encountered.
|
||||
$search_offset = 0;
|
||||
|
||||
// Recurses until the nesting heirarchy is flattened and all import files are inlined.
|
||||
while (preg_match($regex->import, $str, $match, PREG_OFFSET_CAPTURE, $search_offset)) {
|
||||
|
||||
$match_len = strlen($match[0][0]);
|
||||
$match_start = $match[0][1];
|
||||
|
||||
$import = new \stdClass();
|
||||
$import->url = $process->tokens->get($match[1][0]);
|
||||
$import->media = trim($match[2][0]);
|
||||
|
||||
// Protocoled import urls are not processed. Stash for prepending to output.
|
||||
if ($import->url->protocol) {
|
||||
$str = substr_replace($str, '', $match_start, $match_len);
|
||||
$process->absoluteImports[] = $import;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Resolve import path information.
|
||||
$import->path = null;
|
||||
if ($import->url->isRooted) {
|
||||
$import->path = realpath($process->docRoot . $import->url->value);
|
||||
}
|
||||
else {
|
||||
$url =& $import->url;
|
||||
$candidates = ["$input->dir/$url->value"];
|
||||
|
||||
// If `import_path` option is set implicit relative urls
|
||||
// are additionally searched under specified import path(s).
|
||||
if (is_array($options->import_path) && $url->isRelativeImplicit()) {
|
||||
foreach ($options->import_path as $importPath) {
|
||||
$candidates[] = "$importPath/$url->originalValue";
|
||||
}
|
||||
}
|
||||
foreach ($candidates as $candidate) {
|
||||
if (file_exists($candidate)) {
|
||||
$import->path = realpath($candidate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If unsuccessful getting import contents continue with the import line removed.
|
||||
$import->content = $import->path ? @file_get_contents($import->path) : false;
|
||||
if ($import->content === false) {
|
||||
$errDesc = 'was not found';
|
||||
if ($import->path && ! is_readable($import->path)) {
|
||||
$errDesc = 'is not readable';
|
||||
}
|
||||
if (! empty($process->sources)) {
|
||||
$errDesc .= " (from within {$process->sources[0]})";
|
||||
}
|
||||
notice("@import '{$import->url->value}' $errDesc");
|
||||
$str = substr_replace($str, '', $match_start, $match_len);
|
||||
continue;
|
||||
}
|
||||
|
||||
$import->dir = dirname($import->path);
|
||||
$import->relativeDir = Util::getLinkBetweenPaths($input->dir, $import->dir);
|
||||
|
||||
// Import file exists so register it.
|
||||
$process->sources[] = $import->path;
|
||||
$mtimes[] = filemtime($import->path);
|
||||
$filenames[] = $import->relativeDir . basename($import->path);
|
||||
|
||||
// If the import content doesn't pass syntax validation skip to next import.
|
||||
if (! $this->prepareImport($import->content)) {
|
||||
|
||||
$str = substr_replace($str, '', $match_start, $match_len);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Alter all embedded import URLs to be relative to the host-file.
|
||||
foreach (Regex::matchAll($regex->import, $import->content) as $m) {
|
||||
|
||||
$nested_url = $process->tokens->get($m[1][0]);
|
||||
|
||||
// Resolve rooted paths.
|
||||
if ($nested_url->isRooted) {
|
||||
$link = Util::getLinkBetweenPaths(dirname($nested_url->getAbsolutePath()), $import->dir);
|
||||
$nested_url->update($link . basename($nested_url->value));
|
||||
}
|
||||
elseif (strlen($import->relativeDir)) {
|
||||
$nested_url->prepend("$import->relativeDir/");
|
||||
}
|
||||
}
|
||||
|
||||
// Optionally rewrite relative url and custom function data-uri references.
|
||||
if ($options->rewrite_import_urls) {
|
||||
$this->rewriteImportedUrls($import);
|
||||
}
|
||||
|
||||
if ($import->media) {
|
||||
$import->content = "@media $import->media {{$import->content}}";
|
||||
}
|
||||
|
||||
$str = substr_replace($str, $import->content, $match_start, $match_len);
|
||||
}
|
||||
|
||||
// Save only if caching is on and the hostfile object is associated with a real file.
|
||||
if ($input->path && $options->cache) {
|
||||
|
||||
$process->cacheData[$process->output->filename] = [
|
||||
'imports' => $filenames,
|
||||
'datem_sum' => array_sum($mtimes) + $input->mtime,
|
||||
'options' => $options->get(),
|
||||
];
|
||||
$process->io->saveCacheData();
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
protected function rewriteImportedUrls($import)
|
||||
{
|
||||
$link = Util::getLinkBetweenPaths($this->process->input->dir, dirname($import->path));
|
||||
|
||||
if (empty($link)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Match all urls that are not imports.
|
||||
preg_match_all(Regex::make('~(?<!@import ){{u_token}}~iS'), $import->content, $matches);
|
||||
|
||||
foreach ($matches[0] as $token) {
|
||||
|
||||
$url = $this->process->tokens->get($token);
|
||||
|
||||
if ($url->isRelative) {
|
||||
$url->prepend($link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function prepareImport(&$str)
|
||||
{
|
||||
$regex = Regex::$patt;
|
||||
$process = $this->process;
|
||||
$tokens = $process->tokens;
|
||||
|
||||
// Convert all EOL to unix style.
|
||||
$str = preg_replace('~\r\n?~', "\n", $str);
|
||||
|
||||
// Trimming to reduce regex backtracking.
|
||||
$str = rtrim($this->captureCommentAndString(rtrim($str)));
|
||||
|
||||
if (! $this->syntaxCheck($str)) {
|
||||
|
||||
$str = '';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Normalize double-colon pseudo elements for backwards compatability.
|
||||
$str = preg_replace('~::(after|before|first-(?:letter|line))~iS', ':$1', $str);
|
||||
|
||||
// Store @charset if set.
|
||||
if (preg_match($regex->charset, $str, $m)) {
|
||||
$replace = '';
|
||||
if (! $process->charset) {
|
||||
// Keep track of newlines for line numbering.
|
||||
$replace = str_repeat("\n", substr_count($m[0], "\n"));
|
||||
$process->charset = trim($tokens->get($m[1]), '"\'');
|
||||
}
|
||||
$str = preg_replace($regex->charset, $replace, $str);
|
||||
}
|
||||
|
||||
$str = $tokens->captureUrls($str, true);
|
||||
|
||||
$this->addMarkers($str);
|
||||
|
||||
$str = Util::normalizeWhiteSpace($str);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function syntaxCheck(&$str)
|
||||
{
|
||||
// Catch obvious typing errors.
|
||||
$errors = false;
|
||||
$current_file = 'file://' . end($this->process->sources);
|
||||
$balanced_parens = substr_count($str, "(") === substr_count($str, ")");
|
||||
$balanced_curlies = substr_count($str, "{") === substr_count($str, "}");
|
||||
|
||||
$validate_pairings = function ($str, $pairing) use ($current_file)
|
||||
{
|
||||
if ($pairing === '{}') {
|
||||
$opener_patt = '~\{~';
|
||||
$balancer_patt = Regex::make('~^{{block}}~');
|
||||
}
|
||||
else {
|
||||
$opener_patt = '~\(~';
|
||||
$balancer_patt = Regex::make('~^{{parens}}~');
|
||||
}
|
||||
|
||||
// Find unbalanced opening brackets.
|
||||
preg_match_all($opener_patt, $str, $matches, PREG_OFFSET_CAPTURE);
|
||||
foreach ($matches[0] as $m) {
|
||||
$offset = $m[1];
|
||||
if (! preg_match($balancer_patt, substr($str, $offset), $m)) {
|
||||
$substr = substr($str, 0, $offset);
|
||||
$line = substr_count($substr, "\n") + 1;
|
||||
$column = strlen($substr) - strrpos($substr, "\n");
|
||||
return "Unbalanced '{$pairing[0]}' in $current_file, Line $line, Column $column.";
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse the string (and brackets) to find stray closing brackets.
|
||||
$str = strtr(strrev($str), $pairing, strrev($pairing));
|
||||
|
||||
preg_match_all($opener_patt, $str, $matches, PREG_OFFSET_CAPTURE);
|
||||
foreach ($matches[0] as $m) {
|
||||
$offset = $m[1];
|
||||
$substr = substr($str, $offset);
|
||||
if (! preg_match($balancer_patt, $substr, $m)) {
|
||||
$line = substr_count($substr, "\n") + 1;
|
||||
$column = strpos($substr, "\n");
|
||||
return "Stray '{$pairing[1]}' in $current_file, Line $line, Column $column.";
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
if (! $balanced_curlies) {
|
||||
$errors = true;
|
||||
warning($validate_pairings($str, '{}') ?: "Unbalanced '{' in $current_file.");
|
||||
}
|
||||
if (! $balanced_parens) {
|
||||
$errors = true;
|
||||
warning($validate_pairings($str, '()') ?: "Unbalanced '(' in $current_file.");
|
||||
}
|
||||
|
||||
return $errors ? false : true;
|
||||
}
|
||||
|
||||
protected function addMarkers(&$str)
|
||||
{
|
||||
$process = $this->process;
|
||||
$currentFileIndex = count($process->sources) - 1;
|
||||
|
||||
static $patt;
|
||||
if (! $patt) {
|
||||
$patt = Regex::make('~
|
||||
(?:^|(?<=[;{}]))
|
||||
(?<before>
|
||||
(?: \s | {{c_token}} )*
|
||||
)
|
||||
(?<selector>
|
||||
(?:
|
||||
# Some @-rules are treated like standard rule blocks.
|
||||
@(?: (?i)page|abstract|font-face(?-i) ) {{RB}} [^{]*
|
||||
|
|
||||
[^@;{}]+
|
||||
)
|
||||
)
|
||||
\{
|
||||
~xS');
|
||||
}
|
||||
|
||||
$count = preg_match_all($patt, $str, $matches, PREG_OFFSET_CAPTURE);
|
||||
while ($count--) {
|
||||
|
||||
$selectorOffset = $matches['selector'][$count][1];
|
||||
|
||||
$line = 0;
|
||||
$before = substr($str, 0, $selectorOffset);
|
||||
if ($selectorOffset) {
|
||||
$line = substr_count($before, "\n");
|
||||
}
|
||||
|
||||
$pointData = [$currentFileIndex, $line];
|
||||
|
||||
// Source maps require column index too.
|
||||
if ($process->generateMap) {
|
||||
$pointData[] = strlen($before) - (strrpos($before, "\n") ?: 0);
|
||||
}
|
||||
|
||||
// Splice in marker token (packing point_data into string is more memory efficient).
|
||||
$str = substr_replace(
|
||||
$str,
|
||||
$process->tokens->add(implode(',', $pointData), 't'),
|
||||
$selectorOffset,
|
||||
0);
|
||||
}
|
||||
}
|
||||
|
||||
protected function captureCommentAndString($str)
|
||||
{
|
||||
$process = $this->process;
|
||||
$callback = function ($m) use ($process) {
|
||||
|
||||
$fullMatch = $m[0];
|
||||
|
||||
if (strpos($fullMatch, '/*') === 0) {
|
||||
|
||||
// Bail without storing comment if output is minified or a private comment.
|
||||
if ($process->minifyOutput || strpos($fullMatch, '/*$') === 0) {
|
||||
|
||||
$label = '';
|
||||
}
|
||||
else {
|
||||
// Fix broken comments as they will break any subsquent
|
||||
// imported files that are inlined.
|
||||
if (! preg_match('~\*/$~', $fullMatch)) {
|
||||
$fullMatch .= '*/';
|
||||
}
|
||||
$label = $process->tokens->add($fullMatch, 'c');
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Fix broken strings as they will break any subsquent
|
||||
// imported files that are inlined.
|
||||
if ($fullMatch[0] !== $fullMatch[strlen($fullMatch)-1]) {
|
||||
$fullMatch .= $fullMatch[0];
|
||||
}
|
||||
|
||||
// Backticked literals may have been used for custom property values.
|
||||
if ($fullMatch[0] === '`') {
|
||||
$fullMatch = preg_replace('~\x5c`~', '`', trim($fullMatch, '`'));
|
||||
}
|
||||
|
||||
$label = $process->tokens->add($fullMatch, 's');
|
||||
}
|
||||
|
||||
return $process->generateMap ? Tokens::pad($label, $fullMatch) : $label;
|
||||
};
|
||||
|
||||
return preg_replace_callback(Regex::$patt->commentAndString, $callback, $str);
|
||||
}
|
||||
}
|
70
include/thirdparty/css-crush/lib/CssCrush/Iterator.php
vendored
Normal file
70
include/thirdparty/css-crush/lib/CssCrush/Iterator.php
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Base iterator for Declaration and Selector lists.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class Iterator implements \IteratorAggregate, \ArrayAccess, \Countable
|
||||
{
|
||||
public $store;
|
||||
|
||||
public function __construct($items = [])
|
||||
{
|
||||
$this->store = $items;
|
||||
}
|
||||
|
||||
/*
|
||||
IteratorAggregate implementation.
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
return new \ArrayIterator($this->store);
|
||||
}
|
||||
|
||||
/*
|
||||
ArrayAccess implementation.
|
||||
*/
|
||||
public function offsetExists($index)
|
||||
{
|
||||
return array_key_exists($index, $this->store);
|
||||
}
|
||||
|
||||
public function offsetGet($index)
|
||||
{
|
||||
return isset($this->store[$index]) ? $this->store[$index] : null;
|
||||
}
|
||||
|
||||
public function offsetSet($index, $value)
|
||||
{
|
||||
$this->store[$index] = $value;
|
||||
}
|
||||
|
||||
public function offsetUnset($index)
|
||||
{
|
||||
unset($this->store[$index]);
|
||||
}
|
||||
|
||||
public function getContents()
|
||||
{
|
||||
return $this->store;
|
||||
}
|
||||
|
||||
/*
|
||||
Countable implementation.
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return count($this->store);
|
||||
}
|
||||
|
||||
/*
|
||||
Collection interface.
|
||||
*/
|
||||
public function filter($filterer, $op = '===')
|
||||
{
|
||||
$collection = new Collection($this->store);
|
||||
return $collection->filter($filterer, $op);
|
||||
}
|
||||
}
|
149
include/thirdparty/css-crush/lib/CssCrush/Logger.php
vendored
Normal file
149
include/thirdparty/css-crush/lib/CssCrush/Logger.php
vendored
Normal file
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* PSR-3 compatible logger.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class Logger
|
||||
{
|
||||
/**
|
||||
* System is unusable.
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
* @return null
|
||||
*/
|
||||
public function emergency($message, array $context = [])
|
||||
{
|
||||
$this->error($message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Action must be taken immediately.
|
||||
*
|
||||
* Example: Entire website down, database unavailable, etc. This should
|
||||
* trigger the SMS alerts and wake you up.
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
* @return null
|
||||
*/
|
||||
public function alert($message, array $context = [])
|
||||
{
|
||||
$this->error($message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Critical conditions.
|
||||
*
|
||||
* Example: Application component unavailable, unexpected exception.
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
* @return null
|
||||
*/
|
||||
public function critical($message, array $context = [])
|
||||
{
|
||||
$this->error($message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime errors that do not require immediate action but should typically
|
||||
* be logged and monitored.
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
* @return null
|
||||
*/
|
||||
public function error($message, array $context = [])
|
||||
{
|
||||
trigger_error($message, E_USER_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exceptional occurrences that are not errors.
|
||||
*
|
||||
* Example: Use of deprecated APIs, poor use of an API, undesirable things
|
||||
* that are not necessarily wrong.
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
* @return null
|
||||
*/
|
||||
public function warning($message, array $context = [])
|
||||
{
|
||||
trigger_error($message, E_USER_WARNING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normal but significant events.
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
* @return null
|
||||
*/
|
||||
public function notice($message, array $context = [])
|
||||
{
|
||||
trigger_error($message, E_USER_NOTICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interesting events.
|
||||
*
|
||||
* Example: User logs in, SQL logs.
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
* @return null
|
||||
*/
|
||||
public function info($message, array $context = [])
|
||||
{
|
||||
$this->debug($message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detailed debug information.
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
* @return null
|
||||
*/
|
||||
public function debug($message, array $context = [])
|
||||
{
|
||||
if (! empty($context['label'])) {
|
||||
$label = PHP_EOL . "$label" . PHP_EOL . str_repeat('=', strlen($label)) . PHP_EOL;
|
||||
}
|
||||
else {
|
||||
$label = '';
|
||||
}
|
||||
|
||||
if (is_string($message)) {
|
||||
Crush::$process->debugLog[] = "$label$message";
|
||||
}
|
||||
else {
|
||||
ob_start();
|
||||
! empty($context['var_dump']) ? var_dump($message) : print_r($message);
|
||||
Crush::$process->debugLog[] = $label . ob_get_clean();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs with an arbitrary level.
|
||||
*
|
||||
* @param mixed $level
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
* @return null
|
||||
*/
|
||||
public function log($level, $message, array $context = [])
|
||||
{
|
||||
$log_levels = array_flip(get_class_methods(__CLASS__));
|
||||
unset($log_levels['log']);
|
||||
|
||||
if (isset($log_levels[$level])) {
|
||||
return $this->$level($message, $context);
|
||||
}
|
||||
}
|
||||
}
|
106
include/thirdparty/css-crush/lib/CssCrush/Mixin.php
vendored
Normal file
106
include/thirdparty/css-crush/lib/CssCrush/Mixin.php
vendored
Normal file
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Mixin objects.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class Mixin
|
||||
{
|
||||
public $template;
|
||||
|
||||
public function __construct($block)
|
||||
{
|
||||
$this->template = new Template($block);
|
||||
}
|
||||
|
||||
public static function call($message, $context = null)
|
||||
{
|
||||
$process = Crush::$process;
|
||||
$mixable = null;
|
||||
$message = trim($message);
|
||||
|
||||
// Test for mixin or abstract rule. e.g:
|
||||
// named-mixin( 50px, rgba(0,0,0,0), left 100% )
|
||||
// abstract-rule
|
||||
if (preg_match(Regex::make('~^(?<name>{{ident}}) {{parens}}?~xS'), $message, $message_match)) {
|
||||
|
||||
$name = $message_match['name'];
|
||||
|
||||
if (isset($process->mixins[$name])) {
|
||||
|
||||
$mixable = $process->mixins[$name];
|
||||
}
|
||||
elseif (isset($process->references[$name])) {
|
||||
|
||||
$mixable = $process->references[$name];
|
||||
}
|
||||
}
|
||||
|
||||
// If no mixin or abstract rule matched, look for matching selector
|
||||
if (! $mixable) {
|
||||
|
||||
$selector_test = Selector::makeReadable($message);
|
||||
|
||||
if (isset($process->references[$selector_test])) {
|
||||
$mixable = $process->references[$selector_test];
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid infinite recursion.
|
||||
if (! $mixable || $mixable === $context) {
|
||||
|
||||
return false;
|
||||
}
|
||||
elseif ($mixable instanceof Mixin) {
|
||||
|
||||
$args = [];
|
||||
$raw_args = isset($message_match['parens_content']) ? trim($message_match['parens_content']) : null;
|
||||
if ($raw_args) {
|
||||
$args = Util::splitDelimList($raw_args);
|
||||
}
|
||||
|
||||
return DeclarationList::parse($mixable->template->__invoke($args), [
|
||||
'flatten' => true,
|
||||
'context' => $mixable,
|
||||
]);
|
||||
}
|
||||
elseif ($mixable instanceof Rule) {
|
||||
|
||||
return $mixable->declarations->store;
|
||||
}
|
||||
}
|
||||
|
||||
public static function merge(array $input, $message_list, $options = [])
|
||||
{
|
||||
$context = isset($options['context']) ? $options['context'] : null;
|
||||
|
||||
$mixables = [];
|
||||
foreach (Util::splitDelimList($message_list) as $message) {
|
||||
if ($result = self::call($message, $context)) {
|
||||
$mixables = array_merge($mixables, $result);
|
||||
}
|
||||
}
|
||||
|
||||
while ($mixable = array_shift($mixables)) {
|
||||
if ($mixable instanceof Declaration) {
|
||||
$input[] = $mixable;
|
||||
}
|
||||
else {
|
||||
list($property, $value) = $mixable;
|
||||
if ($property === 'mixin') {
|
||||
$input = Mixin::merge($input, $value, $options);
|
||||
}
|
||||
elseif (! empty($options['keyed'])) {
|
||||
$input[$property] = $value;
|
||||
}
|
||||
else {
|
||||
$input[] = [$property, $value];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $input;
|
||||
}
|
||||
}
|
176
include/thirdparty/css-crush/lib/CssCrush/Options.php
vendored
Normal file
176
include/thirdparty/css-crush/lib/CssCrush/Options.php
vendored
Normal file
|
@ -0,0 +1,176 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Options handling.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class Options
|
||||
{
|
||||
protected $computedOptions = [];
|
||||
protected $inputOptions = [];
|
||||
|
||||
protected static $standardOptions = [
|
||||
'minify' => true,
|
||||
'formatter' => null,
|
||||
'versioning' => true,
|
||||
'boilerplate' => true,
|
||||
'vars' => [],
|
||||
'cache' => true,
|
||||
'context' => null,
|
||||
'import_path' => null,
|
||||
'output_file' => null,
|
||||
'output_dir' => null,
|
||||
'asset_dir' => null,
|
||||
'doc_root' => null,
|
||||
'vendor_target' => 'all',
|
||||
'rewrite_import_urls' => true,
|
||||
'plugins' => null,
|
||||
'settings' => [],
|
||||
'stat_dump' => false,
|
||||
'source_map' => false,
|
||||
'newlines' => 'use-platform',
|
||||
];
|
||||
|
||||
public function __construct(array $options = [], Options $defaults = null)
|
||||
{
|
||||
$options = array_change_key_case($options, CASE_LOWER);
|
||||
|
||||
if ($defaults) {
|
||||
$options += $defaults->get();
|
||||
}
|
||||
|
||||
if (! empty($options['enable'])) {
|
||||
if (empty($options['plugins'])) {
|
||||
$options['plugins'] = $options['enable'];
|
||||
}
|
||||
unset($options['enable']);
|
||||
}
|
||||
|
||||
foreach ($options + self::$standardOptions as $name => $value) {
|
||||
$this->__set($name, $value);
|
||||
}
|
||||
}
|
||||
|
||||
public function __set($name, $value)
|
||||
{
|
||||
$this->inputOptions[$name] = $value;
|
||||
|
||||
switch ($name) {
|
||||
|
||||
case 'formatter':
|
||||
if (is_string($value) && isset(Crush::$config->formatters[$value])) {
|
||||
$value = Crush::$config->formatters[$value];
|
||||
}
|
||||
if (! is_callable($value)) {
|
||||
$value = null;
|
||||
}
|
||||
break;
|
||||
|
||||
// Path options.
|
||||
case 'boilerplate':
|
||||
if (is_string($value)) {
|
||||
$value = Util::resolveUserPath($value);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'stat_dump':
|
||||
if (is_string($value)) {
|
||||
$value = Util::resolveUserPath($value, function ($path) {
|
||||
touch($path);
|
||||
return $path;
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'output_dir':
|
||||
case 'asset_dir':
|
||||
if (is_string($value)) {
|
||||
$value = Util::resolveUserPath($value, function ($path) use ($name) {
|
||||
if (! @mkdir($path, 0755, true)) {
|
||||
warning("Could not create directory $path (setting `$name` option).");
|
||||
}
|
||||
else {
|
||||
debug("Created directory $path (setting `$name` option).");
|
||||
}
|
||||
return $path;
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
// Path options that only accept system paths.
|
||||
case 'context':
|
||||
case 'doc_root':
|
||||
if (is_string($value)) {
|
||||
$value = Util::normalizePath(realpath($value));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'import_path':
|
||||
if ($value) {
|
||||
if (is_string($value)) {
|
||||
$value = preg_split('~\s*,\s*~', trim($value));
|
||||
}
|
||||
$value = array_filter(array_map(function ($path) {
|
||||
return Util::normalizePath(realpath($path));
|
||||
}, $value));
|
||||
}
|
||||
break;
|
||||
|
||||
// Options used internally as arrays.
|
||||
case 'plugins':
|
||||
$value = (array) $value;
|
||||
break;
|
||||
}
|
||||
|
||||
$this->computedOptions[$name] = $value;
|
||||
}
|
||||
|
||||
public function __get($name)
|
||||
{
|
||||
switch ($name) {
|
||||
case 'newlines':
|
||||
switch ($this->inputOptions[$name]) {
|
||||
case 'windows':
|
||||
case 'win':
|
||||
return "\r\n";
|
||||
case 'unix':
|
||||
return "\n";
|
||||
case 'use-platform':
|
||||
default:
|
||||
return PHP_EOL;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'minify':
|
||||
if (isset($this->computedOptions['formatter'])) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'formatter':
|
||||
if (empty($this->inputOptions['minify'])) {
|
||||
return isset($this->computedOptions['formatter']) ?
|
||||
$this->computedOptions['formatter'] : 'CssCrush\fmtr_block';
|
||||
}
|
||||
}
|
||||
|
||||
return isset($this->computedOptions[$name]) ? $this->computedOptions[$name] : null;
|
||||
}
|
||||
|
||||
public function __isset($name)
|
||||
{
|
||||
return isset($this->inputOptions[$name]);
|
||||
}
|
||||
|
||||
public function get($computed = false)
|
||||
{
|
||||
return $computed ? $this->computedOptions : self::filter($this->inputOptions);
|
||||
}
|
||||
|
||||
public static function filter(array $optionsArray = null)
|
||||
{
|
||||
return $optionsArray ? array_intersect_key($optionsArray, self::$standardOptions) : self::$standardOptions;
|
||||
}
|
||||
}
|
26
include/thirdparty/css-crush/lib/CssCrush/PostAliasFix.php
vendored
Normal file
26
include/thirdparty/css-crush/lib/CssCrush/PostAliasFix.php
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Fixes for aliasing to legacy syntaxes.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class PostAliasFix
|
||||
{
|
||||
public static $functions = [];
|
||||
|
||||
public static function add($alias_type, $key, $callback)
|
||||
{
|
||||
if ($alias_type === 'function') {
|
||||
self::$functions[$key] = $callback;
|
||||
}
|
||||
}
|
||||
|
||||
public static function remove($alias_type, $key)
|
||||
{
|
||||
if ($alias_type === 'function') {
|
||||
unset(self::$functions[$key]);
|
||||
}
|
||||
}
|
||||
}
|
1102
include/thirdparty/css-crush/lib/CssCrush/Process.php
vendored
Normal file
1102
include/thirdparty/css-crush/lib/CssCrush/Process.php
vendored
Normal file
File diff suppressed because it is too large
Load diff
112
include/thirdparty/css-crush/lib/CssCrush/Regex.php
vendored
Normal file
112
include/thirdparty/css-crush/lib/CssCrush/Regex.php
vendored
Normal file
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Regex management.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class Regex
|
||||
{
|
||||
// Patterns.
|
||||
public static $patt;
|
||||
|
||||
// Character classes.
|
||||
public static $classes;
|
||||
|
||||
public static function init()
|
||||
{
|
||||
self::$patt = $patt = new \stdClass();
|
||||
self::$classes = $classes = new \stdClass();
|
||||
|
||||
// CSS type classes.
|
||||
$classes->ident = '[a-zA-Z0-9_-]+';
|
||||
$classes->number = '[+-]?\d*\.?\d+';
|
||||
$classes->percentage = $classes->number . '%';
|
||||
$classes->length_unit = '(?i)(?:e[mx]|c[hm]|rem|v[hwm]|in|p[tcx])(?-i)';
|
||||
$classes->length = $classes->number . $classes->length_unit;
|
||||
$classes->color_hex = '#[[:xdigit:]]{3}(?:[[:xdigit:]]{3})?';
|
||||
|
||||
// Tokens.
|
||||
$classes->token_id = '[0-9a-z]+';
|
||||
$classes->c_token = '\?c' . $classes->token_id . '\?'; // Comments.
|
||||
$classes->s_token = '\?s' . $classes->token_id . '\?'; // Strings.
|
||||
$classes->r_token = '\?r' . $classes->token_id . '\?'; // Rules.
|
||||
$classes->u_token = '\?u' . $classes->token_id . '\?'; // URLs.
|
||||
$classes->t_token = '\?t' . $classes->token_id . '\?'; // Traces.
|
||||
$classes->a_token = '\?a(' . $classes->token_id . ')\?'; // Args.
|
||||
|
||||
// Boundries.
|
||||
$classes->LB = '(?<![\w-])'; // Left ident boundry.
|
||||
$classes->RB = '(?![\w-])'; // Right ident boundry.
|
||||
|
||||
// Recursive block matching.
|
||||
$classes->block = '(?<block>\{\s*(?<block_content>(?:(?>[^{}]+)|(?&block))*)\})';
|
||||
$classes->parens = '(?<parens>\(\s*(?<parens_content>(?:(?>[^()]+)|(?&parens))*)\))';
|
||||
|
||||
// Misc.
|
||||
$classes->vendor = '-[a-zA-Z]+-';
|
||||
$classes->hex = '[[:xdigit:]]';
|
||||
$classes->newline = '(\r\n?|\n)';
|
||||
|
||||
// Create standalone class patterns, add classes as class swaps.
|
||||
foreach ($classes as $name => $class) {
|
||||
$patt->{$name} = '~' . $class . '~S';
|
||||
}
|
||||
|
||||
// Rooted classes.
|
||||
$patt->rooted_ident = '~^' . $classes->ident . '$~';
|
||||
$patt->rooted_number = '~^' . $classes->number . '$~';
|
||||
|
||||
// @-rules.
|
||||
$patt->import = Regex::make('~@import \s+ ({{u_token}}) \s? ([^;]*);~ixS');
|
||||
$patt->charset = Regex::make('~@charset \s+ ({{s_token}}) \s*;~ixS');
|
||||
$patt->mixin = Regex::make('~@mixin \s+ (?<name>{{ident}}) \s* {{block}}~ixS');
|
||||
$patt->fragmentCapture = Regex::make('~@fragment \s+ (?<name>{{ident}}) \s* {{block}}~ixS');
|
||||
$patt->fragmentInvoke = Regex::make('~@fragment \s+ (?<name>{{ident}}) {{parens}}? \s* ;~ixS');
|
||||
$patt->abstract = Regex::make('~^@abstract \s+ (?<name>{{ident}})~ixS');
|
||||
|
||||
// Functions.
|
||||
$patt->functionTest = Regex::make('~{{ LB }} (?<func_name>{{ ident }}) \(~xS');
|
||||
$patt->thisFunction = Functions::makePattern(['this']);
|
||||
|
||||
// Strings and comments.
|
||||
$patt->string = '~(\'|")(?:\\\\\1|[^\1])*?\1~xS';
|
||||
$patt->commentAndString = '~
|
||||
# Quoted string (to EOF if unmatched).
|
||||
(\'|"|`)(?:\\\\\1|[^\1])*?(?:\1|$)
|
||||
|
|
||||
# Block comment (to EOF if unmatched).
|
||||
/\*(?:[^*]*\*+(?:[^/*][^*]*\*+)*/|.*)
|
||||
~xsS';
|
||||
|
||||
// Misc.
|
||||
$patt->vendorPrefix = '~^-([a-z]+)-([a-z-]+)~iS';
|
||||
$patt->ruleDirective = '~^(?:(@include)|(@extends?)|(@name))[\s]+~iS';
|
||||
$patt->argListSplit = '~\s*[,\s]\s*~S';
|
||||
$patt->cruftyHex = Regex::make('~\#({{hex}})\1({{hex}})\2({{hex}})\3~S');
|
||||
$patt->token = Regex::make('~^ \? (?<type>[a-zA-Z]) {{token_id}} \? $~xS');
|
||||
}
|
||||
|
||||
public static function make($pattern)
|
||||
{
|
||||
static $cache = [];
|
||||
|
||||
if (isset($cache[$pattern])) {
|
||||
return $cache[$pattern];
|
||||
}
|
||||
|
||||
return $cache[$pattern] = preg_replace_callback('~\{\{ *(?<name>\w+) *\}\}~S', function ($m) {
|
||||
return Regex::$classes->{ $m['name'] };
|
||||
}, $pattern);
|
||||
}
|
||||
|
||||
public static function matchAll($patt, $subject, $offset = 0)
|
||||
{
|
||||
$count = preg_match_all($patt, $subject, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER, $offset);
|
||||
|
||||
return $count ? $matches : [];
|
||||
}
|
||||
}
|
||||
|
||||
Regex::init();
|
145
include/thirdparty/css-crush/lib/CssCrush/Rule.php
vendored
Normal file
145
include/thirdparty/css-crush/lib/CssCrush/Rule.php
vendored
Normal file
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* CSS rule API.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class Rule
|
||||
{
|
||||
public $vendorContext;
|
||||
public $label;
|
||||
public $marker;
|
||||
public $name;
|
||||
public $isAbstract;
|
||||
public $resolvedExtendables;
|
||||
|
||||
public $parent;
|
||||
public $previous;
|
||||
public $next;
|
||||
|
||||
public $selectors;
|
||||
public $declarations;
|
||||
|
||||
// Arugments passed via @extend.
|
||||
public $extendArgs = [];
|
||||
public $extendSelectors = [];
|
||||
|
||||
public function __construct($selectorString, $declarationsString, $traceToken = null)
|
||||
{
|
||||
$process = Crush::$process;
|
||||
$this->label = $process->tokens->createLabel('r');
|
||||
$this->marker = $process->generateMap ? $traceToken : null;
|
||||
$this->selectors = new SelectorList($selectorString, $this);
|
||||
$this->declarations = new DeclarationList($declarationsString, $this);
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
$process = Crush::$process;
|
||||
|
||||
// Merge the extend selectors.
|
||||
$this->selectors->store += $this->extendSelectors;
|
||||
|
||||
// Dereference and return empty string if there are no selectors or declarations.
|
||||
if (empty($this->selectors->store) || empty($this->declarations->store)) {
|
||||
$process->tokens->pop($this->label);
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
$stub = $this->marker;
|
||||
|
||||
if ($process->minifyOutput) {
|
||||
return "$stub{$this->selectors->join()}{{$this->declarations->join()}}";
|
||||
}
|
||||
else {
|
||||
return $stub . call_user_func($process->ruleFormatter, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
$this->selectors = clone $this->selectors;
|
||||
$this->declarations = clone $this->declarations;
|
||||
}
|
||||
|
||||
|
||||
#############################
|
||||
# Rule inheritance.
|
||||
|
||||
public function addExtendSelectors($rawValue)
|
||||
{
|
||||
foreach (Util::splitDelimList($rawValue) as $arg) {
|
||||
$extendArg = new ExtendArg($arg);
|
||||
$this->extendArgs[$extendArg->raw] = $extendArg;
|
||||
}
|
||||
}
|
||||
|
||||
public function resolveExtendables()
|
||||
{
|
||||
if (! $this->extendArgs) {
|
||||
|
||||
return false;
|
||||
}
|
||||
elseif (! $this->resolvedExtendables) {
|
||||
|
||||
$references =& Crush::$process->references;
|
||||
|
||||
// Filter the extendArgs list to usable references.
|
||||
$filtered = [];
|
||||
foreach ($this->extendArgs as $extendArg) {
|
||||
|
||||
if (isset($references[$extendArg->name])) {
|
||||
$parentRule = $references[$extendArg->name];
|
||||
$parentRule->resolveExtendables();
|
||||
$extendArg->pointer = $parentRule;
|
||||
$filtered[$parentRule->label] = $extendArg;
|
||||
}
|
||||
}
|
||||
|
||||
$this->resolvedExtendables = true;
|
||||
$this->extendArgs = $filtered;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function applyExtendables()
|
||||
{
|
||||
if (! $this->resolveExtendables()) {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a stack of all parent rule args.
|
||||
$parentExtendArgs = [];
|
||||
foreach ($this->extendArgs as $extendArg) {
|
||||
$parentExtendArgs += $extendArg->pointer->extendArgs;
|
||||
}
|
||||
|
||||
// Merge this rule's extendArgs with parent extendArgs.
|
||||
$this->extendArgs += $parentExtendArgs;
|
||||
|
||||
// Add this rule's selectors to all extendArgs.
|
||||
foreach ($this->extendArgs as $extendArg) {
|
||||
|
||||
$ancestor = $extendArg->pointer;
|
||||
|
||||
$extendSelectors = $this->selectors->store;
|
||||
|
||||
// If there is a pseudo class extension create a new set accordingly.
|
||||
if ($extendArg->pseudo) {
|
||||
|
||||
$extendSelectors = [];
|
||||
foreach ($this->selectors->store as $selector) {
|
||||
$newSelector = clone $selector;
|
||||
$newReadable = $newSelector->appendPseudo($extendArg->pseudo);
|
||||
$extendSelectors[$newReadable] = $newSelector;
|
||||
}
|
||||
}
|
||||
$ancestor->extendSelectors += $extendSelectors;
|
||||
}
|
||||
}
|
||||
}
|
105
include/thirdparty/css-crush/lib/CssCrush/Selector.php
vendored
Normal file
105
include/thirdparty/css-crush/lib/CssCrush/Selector.php
vendored
Normal file
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Selector objects.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class Selector
|
||||
{
|
||||
public $value;
|
||||
public $readableValue;
|
||||
public $allowPrefix = true;
|
||||
|
||||
public function __construct($rawSelector)
|
||||
{
|
||||
// Look for rooting prefix.
|
||||
if (strpos($rawSelector, '^') === 0) {
|
||||
$rawSelector = ltrim($rawSelector, "^ \n\r\t");
|
||||
$this->allowPrefix = false;
|
||||
}
|
||||
|
||||
$this->readableValue = Selector::makeReadable($rawSelector);
|
||||
|
||||
$this->value = Selector::expandAliases($rawSelector);
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
if (Crush::$process->minifyOutput) {
|
||||
// Trim whitespace around selector combinators.
|
||||
$this->value = preg_replace('~ ?([>\~+]) ?~S', '$1', $this->value);
|
||||
}
|
||||
else {
|
||||
$this->value = Selector::normalizeWhiteSpace($this->value);
|
||||
}
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function appendPseudo($pseudo)
|
||||
{
|
||||
// Check to avoid doubling-up.
|
||||
if (! StringObject::endsWith($this->readableValue, $pseudo)) {
|
||||
|
||||
$this->readableValue .= $pseudo;
|
||||
$this->value .= $pseudo;
|
||||
}
|
||||
return $this->readableValue;
|
||||
}
|
||||
|
||||
public static function normalizeWhiteSpace($str)
|
||||
{
|
||||
// Create space around combinators, then normalize whitespace.
|
||||
return Util::normalizeWhiteSpace(preg_replace('~([>+]|\~(?!=))~S', ' $1 ', $str));
|
||||
}
|
||||
|
||||
public static function makeReadable($str)
|
||||
{
|
||||
$str = Selector::normalizeWhiteSpace($str);
|
||||
|
||||
// Quick test for string tokens.
|
||||
if (strpos($str, '?s') !== false) {
|
||||
$str = Crush::$process->tokens->restore($str, 's');
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
public static function expandAliases($str)
|
||||
{
|
||||
$process = Crush::$process;
|
||||
|
||||
if (! $process->selectorAliases || ! preg_match($process->selectorAliasesPatt, $str)) {
|
||||
return $str;
|
||||
}
|
||||
|
||||
while (preg_match_all($process->selectorAliasesPatt, $str, $m, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
|
||||
|
||||
$alias_call = end($m);
|
||||
$alias_name = strtolower($alias_call[1][0]);
|
||||
|
||||
$start = $alias_call[0][1];
|
||||
$length = strlen($alias_call[0][0]);
|
||||
$args = [];
|
||||
|
||||
// It's a function alias if a start paren is matched.
|
||||
if (isset($alias_call[2])) {
|
||||
|
||||
// Parse argument list.
|
||||
if (preg_match(Regex::$patt->parens, $str, $parens, PREG_OFFSET_CAPTURE, $start)) {
|
||||
$args = Functions::parseArgs($parens[2][0]);
|
||||
|
||||
// Amend offsets.
|
||||
$paren_start = $parens[0][1];
|
||||
$paren_len = strlen($parens[0][0]);
|
||||
$length = ($paren_start + $paren_len) - $start;
|
||||
}
|
||||
}
|
||||
|
||||
$str = substr_replace($str, $process->selectorAliases[$alias_name]($args), $start, $length);
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
}
|
63
include/thirdparty/css-crush/lib/CssCrush/SelectorAlias.php
vendored
Normal file
63
include/thirdparty/css-crush/lib/CssCrush/SelectorAlias.php
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Selector aliases.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class SelectorAlias
|
||||
{
|
||||
protected $type;
|
||||
protected $handler;
|
||||
|
||||
public function __construct($handler, $type = 'alias')
|
||||
{
|
||||
$this->handler = $handler;
|
||||
$this->type = $type;
|
||||
|
||||
switch ($this->type) {
|
||||
case 'alias':
|
||||
$this->handler = new Template($handler);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function __invoke($args)
|
||||
{
|
||||
$handler = $this->handler;
|
||||
$tokens = Crush::$process->tokens;
|
||||
|
||||
$splat_arg_patt = Regex::make('~#\((?<fallback>{{ ident }})?\)~');
|
||||
|
||||
switch ($this->type) {
|
||||
case 'alias':
|
||||
return $handler($args);
|
||||
case 'callback':
|
||||
$template = new Template($handler($args));
|
||||
return $template($args);
|
||||
case 'splat':
|
||||
$handler = $tokens->restore($handler, 's');
|
||||
if ($args) {
|
||||
$list = [];
|
||||
foreach ($args as $arg) {
|
||||
$list[] = SelectorAlias::wrap(
|
||||
$tokens->capture(preg_replace($splat_arg_patt, $arg, $handler), 's')
|
||||
);
|
||||
}
|
||||
$handler = implode(',', $list);
|
||||
}
|
||||
else {
|
||||
$handler = $tokens->capture(preg_replace_callback($splat_arg_patt, function ($m) {
|
||||
return $m['fallback'];
|
||||
}, $handler), 's');
|
||||
}
|
||||
return SelectorAlias::wrap($handler);
|
||||
}
|
||||
}
|
||||
|
||||
public static function wrap($str)
|
||||
{
|
||||
return strpos($str, ',') !== false ? ":any($str)" : $str;
|
||||
}
|
||||
}
|
136
include/thirdparty/css-crush/lib/CssCrush/SelectorList.php
vendored
Normal file
136
include/thirdparty/css-crush/lib/CssCrush/SelectorList.php
vendored
Normal file
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Selector lists.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class SelectorList extends Iterator
|
||||
{
|
||||
public function __construct($selectorString, Rule $rule)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$selectorString = trim(Util::stripCommentTokens($selectorString));
|
||||
|
||||
foreach (Util::splitDelimList($selectorString) as $selector) {
|
||||
|
||||
if (preg_match(Regex::$patt->abstract, $selector, $m)) {
|
||||
$rule->name = strtolower($m['name']);
|
||||
$rule->isAbstract = true;
|
||||
}
|
||||
else {
|
||||
$this->add(new Selector($selector));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function add(Selector $selector)
|
||||
{
|
||||
$this->store[$selector->readableValue] = $selector;
|
||||
}
|
||||
|
||||
public function join($glue = ',')
|
||||
{
|
||||
return implode($glue, $this->store);
|
||||
}
|
||||
|
||||
public function expand()
|
||||
{
|
||||
static $grouping_patt, $expand, $expandSelector;
|
||||
if (! $grouping_patt) {
|
||||
|
||||
$grouping_patt = Regex::make('~\:any{{ parens }}~iS');
|
||||
|
||||
$expand = function ($selector_string) use ($grouping_patt)
|
||||
{
|
||||
if (preg_match($grouping_patt, $selector_string, $m, PREG_OFFSET_CAPTURE)) {
|
||||
|
||||
list($full_match, $full_match_offset) = $m[0];
|
||||
$before = substr($selector_string, 0, $full_match_offset);
|
||||
$after = substr($selector_string, strlen($full_match) + $full_match_offset);
|
||||
$selectors = [];
|
||||
|
||||
// Allowing empty strings for more expansion possibilities.
|
||||
foreach (Util::splitDelimList($m['parens_content'][0], ['allow_empty_strings' => true]) as $segment) {
|
||||
if ($selector = trim("$before$segment$after")) {
|
||||
$selectors[$selector] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $selectors;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
$expandSelector = function ($selector_string) use ($expand)
|
||||
{
|
||||
if ($running_stack = $expand($selector_string)) {
|
||||
|
||||
$flattened_stack = [];
|
||||
do {
|
||||
$loop_stack = [];
|
||||
foreach ($running_stack as $selector => $bool) {
|
||||
$selectors = $expand($selector);
|
||||
if (! $selectors) {
|
||||
$flattened_stack += [$selector => true];
|
||||
}
|
||||
else {
|
||||
$loop_stack += $selectors;
|
||||
}
|
||||
}
|
||||
$running_stack = $loop_stack;
|
||||
|
||||
} while ($loop_stack);
|
||||
|
||||
return $flattened_stack;
|
||||
}
|
||||
|
||||
return [$selector_string => true];
|
||||
};
|
||||
}
|
||||
|
||||
$expanded_set = [];
|
||||
|
||||
foreach ($this->store as $original_selector) {
|
||||
if (stripos($original_selector->value, ':any(') !== false) {
|
||||
foreach ($expandSelector($original_selector->value) as $selector_string => $bool) {
|
||||
$new = new Selector($selector_string);
|
||||
$expanded_set[$new->readableValue] = $new;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$expanded_set[$original_selector->readableValue] = $original_selector;
|
||||
}
|
||||
}
|
||||
|
||||
$this->store = $expanded_set;
|
||||
}
|
||||
|
||||
public function merge($rawSelectors)
|
||||
{
|
||||
$stack = [];
|
||||
|
||||
foreach ($rawSelectors as $rawParentSelector) {
|
||||
foreach ($this->store as $selector) {
|
||||
|
||||
$useParentSymbol = strpos($selector->value, '&') !== false;
|
||||
|
||||
if (! $selector->allowPrefix && ! $useParentSymbol) {
|
||||
$stack[$selector->readableValue] = $selector;
|
||||
}
|
||||
elseif ($useParentSymbol) {
|
||||
$new = new Selector(str_replace('&', $rawParentSelector, $selector->value));
|
||||
$stack[$new->readableValue] = $new;
|
||||
}
|
||||
else {
|
||||
$new = new Selector("$rawParentSelector {$selector->value}");
|
||||
$stack[$new->readableValue] = $new;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->store = $stack;
|
||||
}
|
||||
}
|
156
include/thirdparty/css-crush/lib/CssCrush/StringObject.php
vendored
Normal file
156
include/thirdparty/css-crush/lib/CssCrush/StringObject.php
vendored
Normal file
|
@ -0,0 +1,156 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* String sugar.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class StringObject
|
||||
{
|
||||
public function __construct($str)
|
||||
{
|
||||
$this->raw = $str;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->raw;
|
||||
}
|
||||
|
||||
public static function endsWith($haystack, $needle)
|
||||
{
|
||||
return substr($haystack, -strlen($needle)) === $needle;
|
||||
}
|
||||
|
||||
public function update($str)
|
||||
{
|
||||
$this->raw = $str;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function substr($start, $length = null)
|
||||
{
|
||||
if (! isset($length)) {
|
||||
|
||||
return substr($this->raw, $start);
|
||||
}
|
||||
else {
|
||||
|
||||
return substr($this->raw, $start, $length);
|
||||
}
|
||||
}
|
||||
|
||||
public function matchAll($patt, $offset = 0)
|
||||
{
|
||||
return Regex::matchAll($patt, $this->raw, $offset);
|
||||
}
|
||||
|
||||
public function replaceHash($replacements)
|
||||
{
|
||||
if ($replacements) {
|
||||
$this->raw = str_replace(
|
||||
array_keys($replacements),
|
||||
array_values($replacements),
|
||||
$this->raw);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function pregReplaceHash($replacements)
|
||||
{
|
||||
if ($replacements) {
|
||||
$this->raw = preg_replace(
|
||||
array_keys($replacements),
|
||||
array_values($replacements),
|
||||
$this->raw);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function pregReplaceCallback($patt, $callback)
|
||||
{
|
||||
$this->raw = preg_replace_callback($patt, $callback, $this->raw);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function append($append)
|
||||
{
|
||||
$this->raw .= $append;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function prepend($prepend)
|
||||
{
|
||||
$this->raw = $prepend . $this->raw;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function splice($replacement, $offset, $length = null)
|
||||
{
|
||||
$this->raw = substr_replace($this->raw, $replacement, $offset, $length);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function trim()
|
||||
{
|
||||
$this->raw = trim($this->raw);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function rTrim()
|
||||
{
|
||||
$this->raw = rtrim($this->raw);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function lTrim()
|
||||
{
|
||||
$this->raw = ltrim($this->raw);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function restore($types, $release = false, $callback = null)
|
||||
{
|
||||
$this->raw = Crush::$process->tokens->restore($this->raw, $types, $release, $callback);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function captureDirectives($directive, $parse_options = [])
|
||||
{
|
||||
if (is_array($directive)) {
|
||||
$directive = '(?:' . implode('|', $directive) . ')';
|
||||
}
|
||||
|
||||
$parse_options += [
|
||||
'keyed' => true,
|
||||
'lowercase_keys' => true,
|
||||
'ignore_directives' => true,
|
||||
'singles' => false,
|
||||
'flatten' => false,
|
||||
];
|
||||
|
||||
if ($parse_options['singles']) {
|
||||
$patt = Regex::make('~@(?i)' . $directive . '(?-i)(?:\s*{{ block }}|\s+(?<name>{{ ident }})\s+(?<value>[^;]+)\s*;)~S');
|
||||
}
|
||||
else {
|
||||
$patt = Regex::make('~@(?i)' . $directive . '(?-i)\s*{{ block }}~S');
|
||||
}
|
||||
|
||||
$captured_directives = [];
|
||||
$this->pregReplaceCallback($patt, function ($m) use (&$captured_directives, $parse_options) {
|
||||
if (isset($m['name'])) {
|
||||
$name = $parse_options['lowercase_keys'] ? strtolower($m['name']) : $m['name'];
|
||||
$captured_directives[$name] = $m['value'];
|
||||
}
|
||||
else {
|
||||
$captured_directives = DeclarationList::parse($m['block_content'], $parse_options) + $captured_directives;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
return $captured_directives;
|
||||
}
|
||||
}
|
147
include/thirdparty/css-crush/lib/CssCrush/Template.php
vendored
Normal file
147
include/thirdparty/css-crush/lib/CssCrush/Template.php
vendored
Normal file
|
@ -0,0 +1,147 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Generalized 'in CSS' templating.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class Template
|
||||
{
|
||||
// Positional argument default values.
|
||||
public $defaults = [];
|
||||
|
||||
// The number of expected arguments.
|
||||
public $argCount = 0;
|
||||
|
||||
public $substitutions;
|
||||
|
||||
// The string passed in with arg calls replaced by tokens.
|
||||
public $string;
|
||||
|
||||
public function __construct($str)
|
||||
{
|
||||
static $templateFunctions;
|
||||
if (! $templateFunctions) {
|
||||
$templateFunctions = new Functions();
|
||||
}
|
||||
|
||||
$str = Template::unTokenize($str);
|
||||
|
||||
// Parse all arg function calls in the passed string,
|
||||
// callback creates default values.
|
||||
$self = $this;
|
||||
$captureCallback = function ($str) use (&$self)
|
||||
{
|
||||
$args = Functions::parseArgsSimple($str);
|
||||
|
||||
$position = array_shift($args);
|
||||
|
||||
// Match the argument index integer.
|
||||
if (! isset($position) || ! ctype_digit($position)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Store the default value.
|
||||
$defaultValue = isset($args[0]) ? $args[0] : null;
|
||||
if (isset($defaultValue)) {
|
||||
$self->defaults[$position] = $defaultValue;
|
||||
}
|
||||
|
||||
// Update argument count.
|
||||
$argNumber = ((int) $position) + 1;
|
||||
$self->argCount = max($self->argCount, $argNumber);
|
||||
|
||||
return "?a$position?";
|
||||
};
|
||||
|
||||
$templateFunctions->register['#'] = $captureCallback;
|
||||
|
||||
$this->string = $templateFunctions->apply($str);
|
||||
}
|
||||
|
||||
public function __invoke(array $args = null, $str = null)
|
||||
{
|
||||
$str = isset($str) ? $str : $this->string;
|
||||
|
||||
// Apply passed arguments as priority.
|
||||
if (isset($args)) {
|
||||
|
||||
list($find, $replace) = $this->prepare($args, false);
|
||||
}
|
||||
|
||||
// Secondly use prepared substitutions if available.
|
||||
elseif ($this->substitutions) {
|
||||
|
||||
list($find, $replace) = $this->substitutions;
|
||||
}
|
||||
|
||||
// Apply substitutions.
|
||||
$str = isset($find) ? str_replace($find, $replace, $str) : $str;
|
||||
|
||||
return Template::tokenize($str);
|
||||
}
|
||||
|
||||
public function getArgValue($index, &$args)
|
||||
{
|
||||
// First lookup a passed value.
|
||||
if (isset($args[$index]) && $args[$index] !== 'default') {
|
||||
|
||||
return $args[$index];
|
||||
}
|
||||
|
||||
// Get a default value.
|
||||
$default = isset($this->defaults[$index]) ? $this->defaults[$index] : '';
|
||||
|
||||
// Recurse for nested arg() calls.
|
||||
while (preg_match(Regex::$patt->a_token, $default, $m)) {
|
||||
$default = str_replace(
|
||||
$m[0],
|
||||
$this->getArgValue((int) $m[1], $args),
|
||||
$default);
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
public function prepare(array $args, $persist = true)
|
||||
{
|
||||
// Create table of substitutions.
|
||||
$find = [];
|
||||
$replace = [];
|
||||
|
||||
if ($this->argCount) {
|
||||
|
||||
$argIndexes = range(0, $this->argCount-1);
|
||||
|
||||
foreach ($argIndexes as $index) {
|
||||
$find[] = "?a$index?";
|
||||
$replace[] = $this->getArgValue($index, $args);
|
||||
}
|
||||
}
|
||||
|
||||
$substitutions = [$find, $replace];
|
||||
|
||||
// Persist substitutions by default.
|
||||
if ($persist) {
|
||||
$this->substitutions = $substitutions;
|
||||
}
|
||||
|
||||
return $substitutions;
|
||||
}
|
||||
|
||||
public static function tokenize($str)
|
||||
{
|
||||
$str = Crush::$process->tokens->capture($str, 's');
|
||||
$str = Crush::$process->tokens->capture($str, 'u');
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
public static function unTokenize($str)
|
||||
{
|
||||
$str = Crush::$process->tokens->restore($str, ['u', 's']);
|
||||
|
||||
return $str;
|
||||
}
|
||||
}
|
161
include/thirdparty/css-crush/lib/CssCrush/Tokens.php
vendored
Normal file
161
include/thirdparty/css-crush/lib/CssCrush/Tokens.php
vendored
Normal file
|
@ -0,0 +1,161 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Token API.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class Tokens
|
||||
{
|
||||
public $store;
|
||||
protected $ids;
|
||||
|
||||
public function __construct(array $types = null)
|
||||
{
|
||||
$types = $types ?: [
|
||||
's', // strings.
|
||||
'c', // comments.
|
||||
'r', // rules.
|
||||
'u', // URLs.
|
||||
't', // traces.
|
||||
];
|
||||
|
||||
$this->store = new \stdClass;
|
||||
$this->ids = new \stdClass;
|
||||
|
||||
foreach ($types as $type) {
|
||||
$this->store->$type = [];
|
||||
$this->ids->$type = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public function get($label)
|
||||
{
|
||||
$path =& $this->store->{$label[1]};
|
||||
|
||||
return isset($path[$label]) ? $path[$label] : null;
|
||||
}
|
||||
|
||||
public function pop($label)
|
||||
{
|
||||
$value = $this->get($label);
|
||||
if (isset($value)) {
|
||||
unset($this->store->{$label[1]}[$label]);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function add($value, $type = null, $existing_label = null)
|
||||
{
|
||||
if ($value instanceof Url) {
|
||||
$type = 'u';
|
||||
}
|
||||
elseif ($value instanceof Rule) {
|
||||
$type = 'r';
|
||||
}
|
||||
$label = $existing_label ? $existing_label : $this->createLabel($type);
|
||||
$this->store->{$type}[$label] = $value;
|
||||
|
||||
return $label;
|
||||
}
|
||||
|
||||
public function createLabel($type)
|
||||
{
|
||||
$counter = base_convert(++$this->ids->$type, 10, 36);
|
||||
|
||||
return "?$type$counter?";
|
||||
}
|
||||
|
||||
public function restore($str, $types, $release = false, $callback = null)
|
||||
{
|
||||
$types = implode('', (array) $types);
|
||||
$patt = Regex::make("~\?[$types]{{ token_id }}\?~S");
|
||||
$tokens = $this;
|
||||
$callback = $callback ?: function ($m) use ($tokens, $release) {
|
||||
return $release ? $tokens->pop($m[0]) : $tokens->get($m[0]);
|
||||
};
|
||||
|
||||
return preg_replace_callback($patt, $callback, $str);
|
||||
}
|
||||
|
||||
public function capture($str, $type)
|
||||
{
|
||||
switch ($type) {
|
||||
case 'u':
|
||||
return $this->captureUrls($str);
|
||||
break;
|
||||
case 's':
|
||||
return preg_replace_callback(Regex::$patt->string, function ($m) {
|
||||
return Crush::$process->tokens->add($m[0], 's');
|
||||
}, $str);
|
||||
}
|
||||
}
|
||||
|
||||
public function captureUrls($str, $add_padding = false)
|
||||
{
|
||||
$count = preg_match_all(
|
||||
Regex::make('~@import \s+ (?<import>{{s_token}}) | {{LB}} (?<func>url|data-uri) {{parens}}~ixS'),
|
||||
$str,
|
||||
$m,
|
||||
PREG_OFFSET_CAPTURE);
|
||||
|
||||
while ($count--) {
|
||||
|
||||
list($full_text, $full_offset) = $m[0][$count];
|
||||
list($import_text, $import_offset) = $m['import'][$count];
|
||||
|
||||
// @import directive.
|
||||
if ($import_offset !== -1) {
|
||||
|
||||
$label = $this->add(new Url(trim($import_text)));
|
||||
$str = str_replace($import_text, $add_padding ? str_pad($label, strlen($import_text)) : $label, $str);
|
||||
}
|
||||
|
||||
// A URL function.
|
||||
else {
|
||||
$func_name = strtolower($m['func'][$count][0]);
|
||||
|
||||
$url = new Url(trim($m['parens_content'][$count][0]));
|
||||
$url->convertToData = 'data-uri' === $func_name;
|
||||
$label = $this->add($url);
|
||||
$str = substr_replace(
|
||||
$str,
|
||||
$add_padding ? Tokens::pad($label, $full_text) : $label,
|
||||
$full_offset,
|
||||
strlen($full_text));
|
||||
}
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
public static function pad($label, $replaced_text)
|
||||
{
|
||||
// Padding token labels to maintain whitespace and newlines.
|
||||
if (($last_newline_pos = strrpos($replaced_text, "\n")) !== false) {
|
||||
$label .= str_repeat("\n", substr_count($replaced_text, "\n")) . str_repeat(' ', strlen(substr($replaced_text, $last_newline_pos))-1);
|
||||
}
|
||||
else {
|
||||
$label = str_pad($label, strlen($replaced_text));
|
||||
}
|
||||
|
||||
return $label;
|
||||
}
|
||||
|
||||
public static function is($label, $of_type)
|
||||
{
|
||||
if (preg_match(Regex::$patt->token, $label, $m)) {
|
||||
|
||||
return $of_type ? ($of_type === $m['type']) : true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function test($value)
|
||||
{
|
||||
return preg_match(Regex::$patt->token, $value, $m) ? $m['type'] : false;
|
||||
}
|
||||
}
|
214
include/thirdparty/css-crush/lib/CssCrush/Url.php
vendored
Normal file
214
include/thirdparty/css-crush/lib/CssCrush/Url.php
vendored
Normal file
|
@ -0,0 +1,214 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* URL tokens.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class Url
|
||||
{
|
||||
public $protocol;
|
||||
|
||||
public $isAbsolute;
|
||||
public $isRelative;
|
||||
public $isRooted;
|
||||
public $isData;
|
||||
|
||||
public $noRewrite;
|
||||
public $convertToData;
|
||||
public $value;
|
||||
public $originalValue;
|
||||
|
||||
public function __construct($raw_value)
|
||||
{
|
||||
if (preg_match(Regex::$patt->s_token, $raw_value)) {
|
||||
$this->value = trim(Crush::$process->tokens->pop($raw_value), '\'"');
|
||||
}
|
||||
else {
|
||||
$this->value = $raw_value;
|
||||
}
|
||||
|
||||
$this->originalValue = $this->value;
|
||||
$this->evaluate();
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
if ($this->convertToData) {
|
||||
$this->toData();
|
||||
}
|
||||
|
||||
if ($this->isRelative || $this->isRooted) {
|
||||
$this->simplify();
|
||||
}
|
||||
|
||||
if ($this->isData) {
|
||||
return 'url("' . preg_replace('~(?<!\x5c)"~', '\\"', $this->value) . '")';
|
||||
}
|
||||
|
||||
// Only wrap url with quotes if it contains tricky characters.
|
||||
$quote = '';
|
||||
if (preg_match('~[()*\s]~S', $this->value)) {
|
||||
$quote = '"';
|
||||
}
|
||||
|
||||
return "url($quote$this->value$quote)";
|
||||
}
|
||||
|
||||
public function update($new_value)
|
||||
{
|
||||
$this->value = $new_value;
|
||||
|
||||
return $this->evaluate();
|
||||
}
|
||||
|
||||
public function evaluate()
|
||||
{
|
||||
// Protocol, protocol-relative (//) or fragment URL.
|
||||
if (preg_match('~^(?: (?<protocol>[a-z]+)\: | \/{2} | \# )~ix', $this->value, $m)) {
|
||||
|
||||
$this->protocol = ! empty($m['protocol']) ? strtolower($m['protocol']) : 'relative';
|
||||
|
||||
switch ($this->protocol) {
|
||||
case 'data':
|
||||
$type = 'data';
|
||||
break;
|
||||
default:
|
||||
$type = 'absolute';
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Relative and rooted URLs.
|
||||
else {
|
||||
$type = 'relative';
|
||||
$leading_variable = strpos($this->value, '$(') === 0;
|
||||
|
||||
// Normalize './' led paths.
|
||||
$this->value = preg_replace('~^\.\/+~i', '', $this->value);
|
||||
|
||||
if ($leading_variable || ($this->value !== '' && $this->value[0] === '/')) {
|
||||
$type = 'rooted';
|
||||
}
|
||||
|
||||
// Normalize slashes.
|
||||
$this->value = rtrim(preg_replace('~[\\\\/]+~', '/', $this->value), '/');
|
||||
}
|
||||
|
||||
$this->setType($type);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isRelativeImplicit()
|
||||
{
|
||||
return $this->isRelative && preg_match('~^([\w$-]|\.[^\/.])~', $this->originalValue);
|
||||
}
|
||||
|
||||
public function getAbsolutePath()
|
||||
{
|
||||
$path = false;
|
||||
if ($this->protocol) {
|
||||
$path = $this->value;
|
||||
}
|
||||
elseif ($this->isRelative || $this->isRooted) {
|
||||
$path = Crush::$process->docRoot .
|
||||
($this->isRelative ? $this->toRoot()->simplify()->value : $this->value);
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function prepend($path_fragment)
|
||||
{
|
||||
if ($this->isRelative) {
|
||||
$this->value = $path_fragment . $this->value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toRoot()
|
||||
{
|
||||
if ($this->isRelative) {
|
||||
$this->prepend(Crush::$process->input->dirUrl . '/');
|
||||
$this->setType('rooted');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toData()
|
||||
{
|
||||
// Only make one conversion attempt.
|
||||
$this->convertToData = false;
|
||||
|
||||
$file = Crush::$process->docRoot . $this->toRoot()->value;
|
||||
|
||||
// File not found.
|
||||
if (! file_exists($file)) {
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$file_ext = pathinfo($file, PATHINFO_EXTENSION);
|
||||
|
||||
// Only allow certain extensions
|
||||
static $allowed_file_extensions = [
|
||||
'woff' => 'application/x-font-woff;charset=utf-8',
|
||||
'ttf' => 'font/truetype;charset=utf-8',
|
||||
'svg' => 'image/svg+xml',
|
||||
'svgz' => 'image/svg+xml',
|
||||
'gif' => 'image/gif',
|
||||
'jpeg' => 'image/jpg',
|
||||
'jpg' => 'image/jpg',
|
||||
'png' => 'image/png',
|
||||
];
|
||||
|
||||
if (! isset($allowed_file_extensions[$file_ext])) {
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$mime_type = $allowed_file_extensions[$file_ext];
|
||||
$base64 = base64_encode(file_get_contents($file));
|
||||
$this->value = "data:$mime_type;base64,$base64";
|
||||
|
||||
$this->setType('data')->protocol = 'data';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setType($type = 'absolute')
|
||||
{
|
||||
$this->isAbsolute = false;
|
||||
$this->isRooted = false;
|
||||
$this->isRelative = false;
|
||||
$this->isData = false;
|
||||
|
||||
switch ($type) {
|
||||
case 'absolute':
|
||||
$this->isAbsolute = true;
|
||||
break;
|
||||
case 'relative':
|
||||
$this->isRelative = true;
|
||||
break;
|
||||
case 'rooted':
|
||||
$this->isRooted = true;
|
||||
break;
|
||||
case 'data':
|
||||
$this->isData = true;
|
||||
$this->convertToData = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function simplify()
|
||||
{
|
||||
if ($this->isRelative || $this->isRooted) {
|
||||
$this->value = Util::simplifyPath($this->value);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
}
|
277
include/thirdparty/css-crush/lib/CssCrush/Util.php
vendored
Normal file
277
include/thirdparty/css-crush/lib/CssCrush/Util.php
vendored
Normal file
|
@ -0,0 +1,277 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* General utilities.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class Util
|
||||
{
|
||||
public static function htmlAttributes(array $attributes, array $sort_order = null)
|
||||
{
|
||||
// Optionally sort attributes (for better readability).
|
||||
if ($sort_order) {
|
||||
uksort($attributes, function ($a, $b) use ($sort_order) {
|
||||
$a_index = array_search($a, $sort_order);
|
||||
$b_index = array_search($b, $sort_order);
|
||||
$a_found = is_int($a_index);
|
||||
$b_found = is_int($b_index);
|
||||
|
||||
if ($a_found && $b_found) {
|
||||
if ($a_index == $b_index) {
|
||||
return 0;
|
||||
}
|
||||
return $a_index > $b_index ? 1 : -1;
|
||||
}
|
||||
elseif ($a_found && ! $b_found) {
|
||||
return -1;
|
||||
}
|
||||
elseif ($b_found && ! $a_found) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return strcmp($a, $b);
|
||||
});
|
||||
}
|
||||
|
||||
$str = '';
|
||||
foreach ($attributes as $name => $value) {
|
||||
$value = htmlspecialchars($value, ENT_COMPAT, 'UTF-8', false);
|
||||
$str .= " $name=\"$value\"";
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
public static function normalizePath($path, $strip_drive_letter = false)
|
||||
{
|
||||
if (! $path) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($strip_drive_letter) {
|
||||
$path = preg_replace('~^[a-z]\:~i', '', $path);
|
||||
}
|
||||
|
||||
// Backslashes and repeat slashes to a single forward slash.
|
||||
$path = rtrim(preg_replace('~[\\\\/]+~', '/', $path), '/');
|
||||
|
||||
// Removing redundant './'.
|
||||
$path = str_replace('/./', '/', $path);
|
||||
if (strpos($path, './') === 0) {
|
||||
$path = substr($path, 2);
|
||||
}
|
||||
|
||||
return Util::simplifyPath($path);
|
||||
}
|
||||
|
||||
public static function simplifyPath($path)
|
||||
{
|
||||
// Reduce redundant path segments. e.g 'foo/../bar' => 'bar'
|
||||
$patt = '~[^/.]+/\.\./~S';
|
||||
while (preg_match($patt, $path)) {
|
||||
$path = preg_replace($patt, '', $path);
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
public static function resolveUserPath($path, callable $recovery = null, $docRoot = null)
|
||||
{
|
||||
// System path.
|
||||
if ($realpath = realpath($path)) {
|
||||
$path = $realpath;
|
||||
}
|
||||
else {
|
||||
if (! $docRoot) {
|
||||
$docRoot = isset(Crush::$process->docRoot) ? Crush::$process->docRoot : Crush::$config->docRoot;
|
||||
}
|
||||
|
||||
// Absolute path.
|
||||
if (strpos($path, '/') === 0) {
|
||||
// If $path is not doc_root based assume it's doc_root relative and prepend doc_root.
|
||||
if (strpos($path, $docRoot) !== 0) {
|
||||
$path = $docRoot . $path;
|
||||
}
|
||||
}
|
||||
// Relative path. Try resolving based on the directory of the executing script.
|
||||
else {
|
||||
$path = Crush::$config->scriptDir . '/' . $path;
|
||||
}
|
||||
|
||||
if (! file_exists($path) && $recovery) {
|
||||
$path = $recovery($path);
|
||||
}
|
||||
$path = realpath($path);
|
||||
}
|
||||
|
||||
return $path ? Util::normalizePath($path) : false;
|
||||
}
|
||||
|
||||
public static function stripCommentTokens($str)
|
||||
{
|
||||
return preg_replace(Regex::$patt->c_token, '', $str);
|
||||
}
|
||||
|
||||
public static function normalizeWhiteSpace($str)
|
||||
{
|
||||
static $find, $replace;
|
||||
if (! $find) {
|
||||
$replacements = [
|
||||
// Convert all whitespace sequences to a single space.
|
||||
'~\s+~S' => ' ',
|
||||
// Trim bracket whitespace where it's safe to do it.
|
||||
'~([\[(]) | ([\])])| ?([{}]) ?~S' => '${1}${2}${3}',
|
||||
// Trim whitespace around delimiters and special characters.
|
||||
'~ ?([;,]) ?~S' => '$1',
|
||||
];
|
||||
$find = array_keys($replacements);
|
||||
$replace = array_values($replacements);
|
||||
}
|
||||
|
||||
return preg_replace($find, $replace, $str);
|
||||
}
|
||||
|
||||
public static function splitDelimList($str, $options = [])
|
||||
{
|
||||
extract($options + [
|
||||
'delim' => ',',
|
||||
'regex' => false,
|
||||
'allow_empty_strings' => false,
|
||||
]);
|
||||
|
||||
$str = trim($str);
|
||||
|
||||
if (! $regex && strpos($str, $delim) === false) {
|
||||
return ! $allow_empty_strings && ! strlen($str) ? [] : [$str];
|
||||
}
|
||||
|
||||
if ($match_count = preg_match_all(Regex::$patt->parens, $str, $matches)) {
|
||||
$keys = [];
|
||||
foreach ($matches[0] as $index => &$value) {
|
||||
$keys[] = "?$index?";
|
||||
}
|
||||
$str = str_replace($matches[0], $keys, $str);
|
||||
}
|
||||
|
||||
$list = $regex ? preg_split($regex, $str) : explode($delim, $str);
|
||||
|
||||
if ($match_count) {
|
||||
foreach ($list as &$value) {
|
||||
$value = str_replace($keys, $matches[0], $value);
|
||||
}
|
||||
}
|
||||
|
||||
$list = array_map('trim', $list);
|
||||
|
||||
return ! $allow_empty_strings ? array_filter($list, 'strlen') : $list;
|
||||
}
|
||||
|
||||
public static function getLinkBetweenPaths($path1, $path2, $directories = true)
|
||||
{
|
||||
$path1 = trim(Util::normalizePath($path1, true), '/');
|
||||
$path2 = trim(Util::normalizePath($path2, true), '/');
|
||||
|
||||
$link = '';
|
||||
|
||||
if ($path1 != $path2) {
|
||||
|
||||
// Split the directory paths into arrays so we can compare segment by segment.
|
||||
$path1_segs = explode('/', $path1);
|
||||
$path2_segs = explode('/', $path2);
|
||||
|
||||
// Shift the segments until they are on different branches.
|
||||
while (isset($path1_segs[0]) && isset($path2_segs[0]) && ($path1_segs[0] === $path2_segs[0])) {
|
||||
array_shift($path1_segs);
|
||||
array_shift($path2_segs);
|
||||
}
|
||||
|
||||
$link = str_repeat('../', count($path1_segs)) . implode('/', $path2_segs);
|
||||
}
|
||||
|
||||
$link = $link !== '' ? rtrim($link, '/') : '';
|
||||
|
||||
// Append end slash if getting a link between directories.
|
||||
if ($link && $directories) {
|
||||
$link .= '/';
|
||||
}
|
||||
|
||||
return $link;
|
||||
}
|
||||
|
||||
public static function filePutContents($file, $str)
|
||||
{
|
||||
if ($stream = fopen($file, 'w')) {
|
||||
fwrite($stream, $str);
|
||||
fclose($stream);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
warning("Could not write file '$file'.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function parseIni($path, $sections = false)
|
||||
{
|
||||
if (! ($result = @parse_ini_file($path, $sections))) {
|
||||
notice("Ini file '$path' could not be parsed.");
|
||||
|
||||
return false;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function readConfigFile($path)
|
||||
{
|
||||
require_once $path;
|
||||
return Options::filter(get_defined_vars());
|
||||
}
|
||||
|
||||
/*
|
||||
* Get raw value (useful if testing values that may or may not be a token).
|
||||
*/
|
||||
public static function rawValue($value)
|
||||
{
|
||||
if ($tokenType = Tokens::test($value)) {
|
||||
if ($tokenType == 'u') {
|
||||
$value = Crush::$process->tokens->get($value)->value;
|
||||
}
|
||||
elseif ($tokenType == 's') {
|
||||
$value = Crush::$process->tokens->get($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/*
|
||||
* Encode integer to Base64 VLQ.
|
||||
*/
|
||||
public static function vlqEncode($value)
|
||||
{
|
||||
static $VLQ_BASE_SHIFT, $VLQ_BASE, $VLQ_BASE_MASK, $VLQ_CONTINUATION_BIT, $BASE64_MAP;
|
||||
if (! $VLQ_BASE_SHIFT) {
|
||||
$VLQ_BASE_SHIFT = 5;
|
||||
$VLQ_BASE = 1 << $VLQ_BASE_SHIFT;
|
||||
$VLQ_BASE_MASK = $VLQ_BASE - 1;
|
||||
$VLQ_CONTINUATION_BIT = $VLQ_BASE;
|
||||
$BASE64_MAP = str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/');
|
||||
}
|
||||
|
||||
$vlq = $value < 0 ? ((-$value) << 1) + 1 : ($value << 1) + 0;
|
||||
|
||||
$encoded = "";
|
||||
do {
|
||||
$digit = $vlq & $VLQ_BASE_MASK;
|
||||
$vlq >>= $VLQ_BASE_SHIFT;
|
||||
if ($vlq > 0) {
|
||||
$digit |= $VLQ_CONTINUATION_BIT;
|
||||
}
|
||||
$encoded .= $BASE64_MAP[$digit];
|
||||
|
||||
} while ($vlq > 0);
|
||||
|
||||
return $encoded;
|
||||
}
|
||||
}
|
116
include/thirdparty/css-crush/lib/CssCrush/Version.php
vendored
Normal file
116
include/thirdparty/css-crush/lib/CssCrush/Version.php
vendored
Normal file
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Version string.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
class Version
|
||||
{
|
||||
public $major;
|
||||
public $minor;
|
||||
public $patch;
|
||||
public $extra;
|
||||
|
||||
public function __construct($version_string)
|
||||
{
|
||||
// Ideally expecting `git describe --long` (e.g. v2.0.0-5-gb28cdb5)
|
||||
// but also accepting simpler formats.
|
||||
preg_match('~^
|
||||
v?
|
||||
(?<major>\d+)
|
||||
(?:\.(?<minor>\d+))?
|
||||
(?:\.(?<patch>\d+))?
|
||||
(?:-(?<extra>.+))?
|
||||
$~ix',
|
||||
$version_string,
|
||||
$version);
|
||||
|
||||
if ($version) {
|
||||
$this->major = (int) $version['major'];
|
||||
$this->minor = isset($version['minor']) ? (int) $version['minor'] : 0;
|
||||
$this->patch = isset($version['patch']) ? (int) $version['patch'] : 0;
|
||||
$this->extra = isset($version['extra']) ? $version['extra'] : null;
|
||||
}
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
$out = (string) $this->major;
|
||||
|
||||
if (isset($this->minor)) {
|
||||
$out .= ".$this->minor";
|
||||
}
|
||||
if (isset($this->patch)) {
|
||||
$out .= ".$this->patch";
|
||||
}
|
||||
if (isset($this->extra)) {
|
||||
$out .= "-$this->extra";
|
||||
}
|
||||
|
||||
return "v$out";
|
||||
}
|
||||
|
||||
public function compare($version_string)
|
||||
{
|
||||
$LESS = -1;
|
||||
$MORE = 1;
|
||||
$EQUAL = 0;
|
||||
|
||||
$test = new Version($version_string);
|
||||
|
||||
foreach (['major', 'minor', 'patch'] as $level) {
|
||||
|
||||
if ($this->{$level} < $test->{$level}) {
|
||||
|
||||
return $LESS;
|
||||
}
|
||||
elseif ($this->{$level} > $test->{$level}) {
|
||||
|
||||
return $MORE;
|
||||
}
|
||||
}
|
||||
|
||||
return $EQUAL;
|
||||
}
|
||||
|
||||
public static function detect() {
|
||||
return self::gitDescribe() ?: self::packageDescribe();
|
||||
}
|
||||
|
||||
public static function gitDescribe()
|
||||
{
|
||||
static $attempted, $version;
|
||||
if (! $attempted && file_exists(Crush::$dir . '/.git')) {
|
||||
$attempted = true;
|
||||
$command = 'cd ' . escapeshellarg(Crush::$dir) . ' && git describe --tag --long';
|
||||
@exec($command, $lines);
|
||||
if ($lines) {
|
||||
$version = new Version(trim($lines[0]));
|
||||
if (is_null($version->major)) {
|
||||
$version = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $version;
|
||||
}
|
||||
|
||||
public static function packageDescribe()
|
||||
{
|
||||
static $attempted, $version;
|
||||
if (! $attempted && file_exists(Crush::$dir . '/package.json')) {
|
||||
$attempted = true;
|
||||
$package = json_decode(file_get_contents(Crush::$dir . '/package.json'));
|
||||
if ($package->version) {
|
||||
$version = new Version($package->version);
|
||||
if (is_null($version->major)) {
|
||||
$version = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $version;
|
||||
}
|
||||
}
|
167
include/thirdparty/css-crush/lib/functions.php
vendored
Normal file
167
include/thirdparty/css-crush/lib/functions.php
vendored
Normal file
|
@ -0,0 +1,167 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Public API.
|
||||
*
|
||||
*/
|
||||
use CssCrush\Crush;
|
||||
|
||||
/**
|
||||
* Process CSS file and return a new compiled file.
|
||||
*
|
||||
* @see docs/api/functions.md
|
||||
*/
|
||||
function csscrush_file($file, $options = []) {
|
||||
|
||||
try {
|
||||
Crush::$process = new CssCrush\Process($options, ['type' => 'file', 'data' => $file]);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
CssCrush\warning($e->getMessage());
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
return new CssCrush\File(Crush::$process);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process CSS file and return an HTML link tag with populated href.
|
||||
*
|
||||
* @see docs/api/functions.md
|
||||
*/
|
||||
function csscrush_tag($file, $options = [], $tag_attributes = []) {
|
||||
|
||||
$file = csscrush_file($file, $options);
|
||||
if ($file && $file->url) {
|
||||
$tag_attributes['href'] = $file->url;
|
||||
$tag_attributes += [
|
||||
'rel' => 'stylesheet',
|
||||
'media' => 'all',
|
||||
];
|
||||
$attrs = CssCrush\Util::htmlAttributes($tag_attributes, ['rel', 'href', 'media']);
|
||||
|
||||
return "<link$attrs />\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process CSS file and return CSS as text wrapped in html style tags.
|
||||
*
|
||||
* @see docs/api/functions.md
|
||||
*/
|
||||
function csscrush_inline($file, $options = [], $tag_attributes = []) {
|
||||
|
||||
if (! is_array($options)) {
|
||||
$options = [];
|
||||
}
|
||||
if (! isset($options['boilerplate'])) {
|
||||
$options['boilerplate'] = false;
|
||||
}
|
||||
|
||||
$file = csscrush_file($file, $options);
|
||||
if ($file && $file->path) {
|
||||
$tagOpen = '';
|
||||
$tagClose = '';
|
||||
if (is_array($tag_attributes)) {
|
||||
$attrs = CssCrush\Util::htmlAttributes($tag_attributes);
|
||||
$tagOpen = "<style$attrs>";
|
||||
$tagClose = '</style>';
|
||||
}
|
||||
return $tagOpen . file_get_contents($file->path) . $tagClose . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compile a raw string of CSS string and return it.
|
||||
*
|
||||
* @see docs/api/functions.md
|
||||
*/
|
||||
function csscrush_string($string, $options = []) {
|
||||
|
||||
if (! isset($options['boilerplate'])) {
|
||||
$options['boilerplate'] = false;
|
||||
}
|
||||
|
||||
Crush::$process = new CssCrush\Process($options, ['type' => 'filter', 'data' => $string]);
|
||||
|
||||
return Crush::$process->compile()->__toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set default options and config settings.
|
||||
*
|
||||
* @see docs/api/functions.md
|
||||
*/
|
||||
function csscrush_set($object_name, $modifier) {
|
||||
|
||||
if (in_array($object_name, ['options', 'config'])) {
|
||||
|
||||
$pointer = $object_name === 'options' ? Crush::$config->options : Crush::$config;
|
||||
|
||||
if (is_callable($modifier)) {
|
||||
$modifier($pointer);
|
||||
}
|
||||
elseif (is_array($modifier)) {
|
||||
foreach ($modifier as $key => $value) {
|
||||
$pointer->{$key} = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get default options and config settings.
|
||||
*
|
||||
* @see docs/api/functions.md
|
||||
*/
|
||||
function csscrush_get($object_name, $property = null) {
|
||||
|
||||
if (in_array($object_name, ['options', 'config'])) {
|
||||
|
||||
$pointer = $object_name === 'options' ? Crush::$config->options : Crush::$config;
|
||||
|
||||
if (! isset($property)) {
|
||||
return $pointer;
|
||||
}
|
||||
else {
|
||||
return isset($pointer->{$property}) ? $pointer->{$property} : null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add plugin.
|
||||
*
|
||||
* @see docs/api/functions.md
|
||||
*/
|
||||
function csscrush_plugin($name, callable $callback) {
|
||||
|
||||
Crush::plugin($name, $callback);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get stats from most recent compile.
|
||||
*
|
||||
* @see docs/api/functions.md
|
||||
*/
|
||||
function csscrush_stat() {
|
||||
|
||||
$process = Crush::$process;
|
||||
$stats = $process->stat;
|
||||
|
||||
// Get logged errors as late as possible.
|
||||
$stats['errors'] = $process->errors;
|
||||
$stats['warnings'] = $process->warnings;
|
||||
$stats += ['compile_time' => 0];
|
||||
|
||||
return $stats;
|
||||
}
|
151
include/thirdparty/css-crush/misc/color-keywords.ini
vendored
Normal file
151
include/thirdparty/css-crush/misc/color-keywords.ini
vendored
Normal file
|
@ -0,0 +1,151 @@
|
|||
; Sources:
|
||||
; http://www.w3.org/TR/css3-color
|
||||
|
||||
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"
|
||||
rebeccapurple = "102,51,153"
|
||||
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"
|
||||
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"
|
48
include/thirdparty/css-crush/misc/formatters.php
vendored
Normal file
48
include/thirdparty/css-crush/misc/formatters.php
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* Formatter callbacks.
|
||||
*
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
Crush::$config->formatters = [
|
||||
'single-line' => 'CssCrush\fmtr_single',
|
||||
'padded' => 'CssCrush\fmtr_padded',
|
||||
'block' => 'CssCrush\fmtr_block',
|
||||
];
|
||||
|
||||
function fmtr_single($rule) {
|
||||
|
||||
$EOL = Crush::$process->newline;
|
||||
|
||||
$selectors = $rule->selectors->join(', ');
|
||||
$block = $rule->declarations->join('; ');
|
||||
return "$selectors { $block; }$EOL";
|
||||
}
|
||||
|
||||
function fmtr_padded($rule, $padding = 40) {
|
||||
|
||||
$EOL = Crush::$process->newline;
|
||||
|
||||
$selectors = $rule->selectors->join(', ');
|
||||
$block = $rule->declarations->join('; ');
|
||||
|
||||
if (strlen($selectors) > $padding) {
|
||||
$padding = str_repeat(' ', $padding);
|
||||
return "$selectors$EOL$padding { $block; }$EOL";
|
||||
}
|
||||
else {
|
||||
$selectors = str_pad($selectors, $padding);
|
||||
return "$selectors { $block; }$EOL";
|
||||
}
|
||||
}
|
||||
|
||||
function fmtr_block($rule, $indent = ' ') {
|
||||
|
||||
$EOL = Crush::$process->newline;
|
||||
|
||||
$selectors = $rule->selectors->join(",$EOL");
|
||||
$block = $rule->declarations->join(";$EOL$indent");
|
||||
return "$selectors {{$EOL}$indent$block;$EOL$indent}$EOL";
|
||||
}
|
176
include/thirdparty/css-crush/misc/property-sorting.ini
vendored
Normal file
176
include/thirdparty/css-crush/misc/property-sorting.ini
vendored
Normal file
|
@ -0,0 +1,176 @@
|
|||
; Table for property sorting.
|
||||
; Vendor prefixes are added at runtime.
|
||||
|
||||
; Generated content
|
||||
content
|
||||
quotes
|
||||
|
||||
; Positioning
|
||||
position
|
||||
z-index
|
||||
top
|
||||
right
|
||||
bottom
|
||||
left
|
||||
|
||||
; Display
|
||||
visibility
|
||||
opacity
|
||||
display
|
||||
overflow
|
||||
overflow-x
|
||||
overflow-y
|
||||
vertical-align
|
||||
|
||||
; Floats
|
||||
float
|
||||
clear
|
||||
|
||||
; Transforms
|
||||
transform
|
||||
transform-style
|
||||
perspective
|
||||
perspective-origin
|
||||
backface-visibility
|
||||
|
||||
; Box-model: dimensions
|
||||
box-sizing
|
||||
width
|
||||
height
|
||||
min-width
|
||||
max-width
|
||||
min-height
|
||||
max-height
|
||||
|
||||
; Box-model: padding
|
||||
padding
|
||||
padding-top
|
||||
padding-right
|
||||
padding-bottom
|
||||
padding-left
|
||||
|
||||
; Box-model: margins
|
||||
margin
|
||||
margin-top
|
||||
margin-right
|
||||
margin-bottom
|
||||
margin-left
|
||||
|
||||
; Box-model: borders
|
||||
border
|
||||
border-color
|
||||
border-image
|
||||
border-radius
|
||||
border-style
|
||||
border-width
|
||||
border-top
|
||||
border-top-color
|
||||
border-top-left-radius
|
||||
border-top-right-radius
|
||||
border-top-style
|
||||
border-top-width
|
||||
border-right
|
||||
border-right-color
|
||||
border-right-style
|
||||
border-right-width
|
||||
border-bottom
|
||||
border-bottom-color
|
||||
border-bottom-style
|
||||
border-bottom-left-radius
|
||||
border-bottom-right-radius
|
||||
border-bottom-width
|
||||
border-left
|
||||
border-left-color
|
||||
border-left-style
|
||||
border-left-width
|
||||
|
||||
; Box-model: effects
|
||||
box-shadow
|
||||
|
||||
; Counters
|
||||
counter-increment
|
||||
counter-reset
|
||||
|
||||
; Foreground color
|
||||
color
|
||||
|
||||
; Background
|
||||
background
|
||||
background-attachment
|
||||
background-clip
|
||||
background-color
|
||||
background-image
|
||||
background-origin
|
||||
background-position
|
||||
background-position-x
|
||||
background-position-y
|
||||
background-repeat
|
||||
background-size
|
||||
|
||||
; Text
|
||||
direction
|
||||
text-align
|
||||
text-align-last
|
||||
text-decoration
|
||||
text-decoration-color
|
||||
text-decoration-line
|
||||
text-decoration-style
|
||||
text-indent
|
||||
text-overflow
|
||||
text-shadow
|
||||
text-transform
|
||||
|
||||
; Fonts: general
|
||||
font
|
||||
font-family
|
||||
font-size
|
||||
font-style
|
||||
font-weight
|
||||
font-variant
|
||||
line-height
|
||||
|
||||
; Fonts: spacing and behaviour
|
||||
letter-spacing
|
||||
white-space
|
||||
word-break
|
||||
word-spacing
|
||||
word-wrap
|
||||
hyphens
|
||||
orphans
|
||||
|
||||
; Outlines
|
||||
outline
|
||||
outline-color
|
||||
outline-offset
|
||||
outline-style
|
||||
outline-width
|
||||
|
||||
; Animations
|
||||
animation
|
||||
animation-delay
|
||||
animation-direction
|
||||
animation-duration
|
||||
animation-fill-mode
|
||||
animation-iteration-count
|
||||
animation-name
|
||||
animation-play-state
|
||||
animation-timing-function
|
||||
|
||||
; Transitions
|
||||
transition
|
||||
transition-delay
|
||||
transition-duration
|
||||
transition-property
|
||||
transition-timing-function
|
||||
|
||||
; Tables specific
|
||||
table-layout
|
||||
border-collapse
|
||||
caption-side
|
||||
empty-cells
|
||||
|
||||
; Lists specific
|
||||
list-style
|
||||
list-style-image
|
||||
list-style-position
|
||||
list-style-type
|
70
include/thirdparty/css-crush/plugins/aria.php
vendored
Normal file
70
include/thirdparty/css-crush/plugins/aria.php
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
/**
|
||||
* Pseudo classes for working with ARIA roles, states and properties
|
||||
*
|
||||
* @see docs/plugins/aria.md
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
\csscrush_plugin('aria', function ($process) {
|
||||
foreach (aria() as $name => $handler) {
|
||||
$type = is_callable($handler) ? 'callback' : 'alias';
|
||||
$process->addSelectorAlias($name, $handler, $type);
|
||||
}
|
||||
});
|
||||
|
||||
function aria() {
|
||||
|
||||
static $aria, $optional_value;
|
||||
if (! $aria) {
|
||||
$optional_value = function ($property) {
|
||||
return function ($args) use ($property) {
|
||||
return $args ? "[$property=\"#(0)\"]" : "[$property]";
|
||||
};
|
||||
};
|
||||
$aria = [
|
||||
|
||||
// Roles.
|
||||
'role' => $optional_value('role'),
|
||||
|
||||
// States and properties.
|
||||
'aria-activedescendant' => $optional_value('aria-activedescendant'),
|
||||
'aria-atomic' => '[aria-atomic="#(0 true)"]',
|
||||
'aria-autocomplete' => $optional_value('aria-autocomplete'),
|
||||
'aria-busy' => '[aria-busy="#(0 true)"]',
|
||||
'aria-checked' => '[aria-checked="#(0 true)"]',
|
||||
'aria-controls' => $optional_value('aria-controls'),
|
||||
'aria-describedby' => $optional_value('aria-describedby'),
|
||||
'aria-disabled' => '[aria-disabled="#(0 true)"]',
|
||||
'aria-dropeffect' => $optional_value('aria-dropeffect'),
|
||||
'aria-expanded' => '[aria-expanded="#(0 true)"]',
|
||||
'aria-flowto' => $optional_value('aria-flowto'),
|
||||
'aria-grabbed' => '[aria-grabbed="#(0 true)"]',
|
||||
'aria-haspopup' => '[aria-haspopup="#(0 true)"]',
|
||||
'aria-hidden' => '[aria-hidden="#(0 true)"]',
|
||||
'aria-invalid' => '[aria-invalid="#(0 true)"]',
|
||||
'aria-label' => $optional_value('aria-label'),
|
||||
'aria-labelledby' => $optional_value('aria-labelledby'),
|
||||
'aria-level' => $optional_value('aria-level'),
|
||||
'aria-live' => $optional_value('aria-live'),
|
||||
'aria-multiline' => '[aria-multiline="#(0 true)"]',
|
||||
'aria-multiselectable' => '[aria-multiselectable="#(0 true)"]',
|
||||
'aria-orientation' => $optional_value('aria-orientation'),
|
||||
'aria-owns' => $optional_value('aria-owns'),
|
||||
'aria-posinset' => $optional_value('aria-posinset'),
|
||||
'aria-pressed' => '[aria-pressed="#(0 true)"]',
|
||||
'aria-readonly' => '[aria-readonly="#(0 true)"]',
|
||||
'aria-relevant' => $optional_value('aria-relevant'),
|
||||
'aria-required' => '[aria-required="#(0 true)"]',
|
||||
'aria-selected' => '[aria-selected="#(0 true)"]',
|
||||
'aria-setsize' => $optional_value('aria-setsize'),
|
||||
'aria-sort' => $optional_value('aria-sort'),
|
||||
'aria-valuemax' => $optional_value('aria-valuemax'),
|
||||
'aria-valuemin' => $optional_value('aria-valuemin'),
|
||||
'aria-valuenow' => $optional_value('aria-valuenow'),
|
||||
'aria-valuetext' => $optional_value('aria-valuetext'),
|
||||
];
|
||||
}
|
||||
|
||||
return $aria;
|
||||
}
|
652
include/thirdparty/css-crush/plugins/canvas.php
vendored
Normal file
652
include/thirdparty/css-crush/plugins/canvas.php
vendored
Normal file
|
@ -0,0 +1,652 @@
|
|||
<?php
|
||||
/**
|
||||
* Bitmap image generator
|
||||
*
|
||||
* @see docs/plugins/canvas.md
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
use stdClass;
|
||||
|
||||
\csscrush_plugin('canvas', function ($process) {
|
||||
$process->on('capture_phase2', 'CssCrush\canvas_capture');
|
||||
$process->functions->add('canvas', 'CssCrush\canvas_generator');
|
||||
$process->functions->add('canvas-data', 'CssCrush\canvas_generator');
|
||||
});
|
||||
|
||||
function canvas_capture($process) {
|
||||
|
||||
$process->string->pregReplaceCallback(
|
||||
Regex::make('~@canvas\s+(?<name>{{ ident }})\s*{{ block }}~iS'),
|
||||
function ($m) {
|
||||
Crush::$process->misc->canvas_defs[strtolower($m['name'])] = new Template($m['block_content']);
|
||||
return '';
|
||||
});
|
||||
}
|
||||
|
||||
function canvas_generator($input, $context) {
|
||||
|
||||
$process = Crush::$process;
|
||||
|
||||
// Check GD requirements are met.
|
||||
static $requirements;
|
||||
if (! isset($requirements)) {
|
||||
$requirements = canvas_requirements();
|
||||
}
|
||||
if ($requirements === false) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Check process cache.
|
||||
$cache_key = $context->function . $input;
|
||||
if (isset($process->misc->canvas_cache[$cache_key])) {
|
||||
return $process->misc->canvas_cache[$cache_key];
|
||||
}
|
||||
|
||||
// Parse args, bail if none.
|
||||
$args = Functions::parseArgs($input);
|
||||
if (! isset($args[0])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$name = strtolower(array_shift($args));
|
||||
|
||||
// Bail if name not registered.
|
||||
$canvas_defs =& $process->misc->canvas_defs;
|
||||
if (! isset($canvas_defs[$name])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Apply args to template.
|
||||
$block = $canvas_defs[$name]($args);
|
||||
|
||||
$raw = DeclarationList::parse($block, [
|
||||
'keyed' => true,
|
||||
'lowercase_keys' => true,
|
||||
'flatten' => true,
|
||||
'apply_hooks' => true,
|
||||
]);
|
||||
|
||||
// Create canvas object.
|
||||
$canvas = new Canvas();
|
||||
|
||||
// Parseable canvas attributes with default values.
|
||||
static $schema = [
|
||||
'fill' => null,
|
||||
'background-fill' => null,
|
||||
'src' => null,
|
||||
'canvas-filter' => null,
|
||||
'width' => null,
|
||||
'height' => null,
|
||||
'margin' => 0,
|
||||
];
|
||||
|
||||
// Resolve properties, set defaults if not present.
|
||||
$canvas->raw = array_intersect_key($raw, $schema) + $schema;
|
||||
|
||||
// Pre-populate.
|
||||
canvas_preprocess($canvas);
|
||||
|
||||
// Apply functions.
|
||||
canvas_apply_css_funcs($canvas);
|
||||
// debug($canvas);
|
||||
|
||||
// Create fingerprint for this canvas based on canvas object.
|
||||
$fingerprint = substr(md5(serialize($canvas)), 0, 7);
|
||||
$generated_filename = "cnv-$name-$fingerprint.png";
|
||||
|
||||
if (! empty($process->options->asset_dir)) {
|
||||
$generated_filepath = $process->options->asset_dir . '/' . $generated_filename;
|
||||
$generated_url = Util::getLinkBetweenPaths(
|
||||
$process->output->dir, $process->options->asset_dir) . $generated_filename;
|
||||
}
|
||||
else {
|
||||
$generated_filepath = $process->output->dir . '/' . $generated_filename;
|
||||
$generated_url = $generated_filename;
|
||||
}
|
||||
$cached_file = file_exists($generated_filepath);
|
||||
|
||||
// $cached_file = false;
|
||||
if (! $cached_file) {
|
||||
|
||||
// Source arguments take priority.
|
||||
if ($src = canvas_fetch_src($canvas->raw['src'])) {
|
||||
|
||||
// Resolve the src image dimensions and positioning.
|
||||
$dst_w = $src->width;
|
||||
$dst_h = $src->height;
|
||||
if (isset($canvas->width) && isset($canvas->height)) {
|
||||
$dst_w = $canvas->width;
|
||||
$dst_h = $canvas->height;
|
||||
}
|
||||
elseif (isset($canvas->width)) {
|
||||
$dst_w = $canvas->width;
|
||||
$dst_h = ($src->height/$src->width) * $canvas->width;
|
||||
}
|
||||
elseif (isset($canvas->height)) {
|
||||
$dst_w = ($src->width/$src->height) * $canvas->height;
|
||||
$dst_h = $canvas->height;
|
||||
}
|
||||
|
||||
// Update the canvas height and width based on the src.
|
||||
$canvas->width = $dst_w;
|
||||
$canvas->height = $dst_h;
|
||||
|
||||
// Create base.
|
||||
canvas_create($canvas);
|
||||
|
||||
// Apply background layer.
|
||||
canvas_fill($canvas, 'background-fill');
|
||||
|
||||
// Filters.
|
||||
canvas_apply_filters($canvas, $src);
|
||||
|
||||
// Place the src image on the base canvas image.
|
||||
imagecopyresized(
|
||||
$canvas->image, // dest_img
|
||||
$src->image, // src_img
|
||||
$canvas->margin->left, // dst_x
|
||||
$canvas->margin->top, // dst_y
|
||||
0, // src_x
|
||||
0, // src_y
|
||||
$dst_w, // dst_w
|
||||
$dst_h, // dst_h
|
||||
$src->width, // src_w
|
||||
$src->height // src_h
|
||||
);
|
||||
imagedestroy($src->image);
|
||||
}
|
||||
else {
|
||||
|
||||
// Set defaults.
|
||||
$canvas->width = isset($canvas->width) ? intval($canvas->width) : 100;
|
||||
$canvas->height = isset($canvas->height) ? intval($canvas->height) : 100;
|
||||
$canvas->fills += ['fill' => 'black'];
|
||||
|
||||
// Create base.
|
||||
canvas_create($canvas);
|
||||
|
||||
// Apply background layer.
|
||||
canvas_fill($canvas, 'background-fill');
|
||||
canvas_fill($canvas, 'fill');
|
||||
}
|
||||
}
|
||||
else {
|
||||
// debug('file cached');
|
||||
}
|
||||
|
||||
|
||||
// Either write to a file.
|
||||
if ($context->function === 'canvas' && $process->ioContext === 'file') {
|
||||
|
||||
if (! $cached_file) {
|
||||
imagepng($canvas->image, $generated_filepath);
|
||||
}
|
||||
|
||||
$url = new Url($generated_url);
|
||||
$url->noRewrite = true;
|
||||
}
|
||||
// Or create data uri.
|
||||
else {
|
||||
if (! $cached_file) {
|
||||
ob_start();
|
||||
imagepng($canvas->image);
|
||||
$data = ob_get_clean();
|
||||
}
|
||||
else {
|
||||
$data = file_get_contents($generated_filepath);
|
||||
}
|
||||
|
||||
$url = new Url('data:image/png;base64,' . base64_encode($data));
|
||||
}
|
||||
|
||||
$label = $process->tokens->add($url);
|
||||
|
||||
// Cache the output URL.
|
||||
$process->misc->canvas_cache[$cache_key] = $label;
|
||||
|
||||
return $label;
|
||||
}
|
||||
|
||||
|
||||
function canvas_fn_linear_gradient($input, $context) {
|
||||
|
||||
$args = Functions::parseArgs($input) + ['white', 'black'];
|
||||
|
||||
$first_arg = strtolower($args[0]);
|
||||
|
||||
static $directions = [
|
||||
'to top' => ['vertical', true],
|
||||
'to right' => ['horizontal', false],
|
||||
'to bottom' => ['vertical', false],
|
||||
'to left' => ['horizontal', true],
|
||||
];
|
||||
|
||||
if (isset($directions[$first_arg])) {
|
||||
list($direction, $flip) = $directions[$first_arg];
|
||||
array_shift($args);
|
||||
}
|
||||
else {
|
||||
list($direction, $flip) = $directions['to bottom'];
|
||||
}
|
||||
|
||||
// Create fill object.
|
||||
$fill = new stdClass();
|
||||
$fill->stops = [];
|
||||
$fill->direction = $direction;
|
||||
|
||||
canvas_set_fill_dims($fill, $context->canvas);
|
||||
|
||||
// Start color.
|
||||
$color = Color::parse($args[0]);
|
||||
$fill->stops[] = $color ? $color : [0, 0, 0, 1];
|
||||
|
||||
// End color.
|
||||
$color = Color::parse($args[1]);
|
||||
$fill->stops[] = $color ? $color : [255, 255, 255, 1];
|
||||
|
||||
if ($flip) {
|
||||
$fill->stops = array_reverse($fill->stops);
|
||||
}
|
||||
|
||||
$context->canvas->fills[$context->currentProperty] = $fill;
|
||||
}
|
||||
|
||||
function canvas_fn_filter($input, $context) {
|
||||
|
||||
$args = Functions::parseArgs($input);
|
||||
|
||||
array_unshift($context->canvas->filters, [$context->function, $args]);
|
||||
}
|
||||
|
||||
|
||||
function canvas_apply_filters($canvas, $src) {
|
||||
|
||||
foreach ($canvas->filters as $filter) {
|
||||
list($name, $args) = $filter;
|
||||
|
||||
switch ($name) {
|
||||
case 'greyscale':
|
||||
case 'grayscale':
|
||||
imagefilter($src->image, IMG_FILTER_GRAYSCALE);
|
||||
break;
|
||||
|
||||
case 'invert':
|
||||
imagefilter($src->image, IMG_FILTER_NEGATE);
|
||||
break;
|
||||
|
||||
case 'opacity':
|
||||
canvas_fade($src, floatval($args[0]));
|
||||
break;
|
||||
|
||||
case 'colorize':
|
||||
$rgb = $args + ['black'];
|
||||
if (count($rgb) === 1) {
|
||||
// If only one argument parse it as a CSS color value.
|
||||
$rgb = Color::parse($rgb[0]);
|
||||
if (! $rgb) {
|
||||
$rgb = [0, 0, 0];
|
||||
}
|
||||
}
|
||||
imagefilter($src->image, IMG_FILTER_COLORIZE, $rgb[0], $rgb[1], $rgb[2]);
|
||||
break;
|
||||
|
||||
case 'blur':
|
||||
$level = 1;
|
||||
if (isset($args[0])) {
|
||||
// Allow multiple blurs for a stronger effect.
|
||||
// Set hard limit.
|
||||
$level = min(max(intval($args[0]), 1), 20);
|
||||
}
|
||||
while ($level--) {
|
||||
imagefilter($src->image, IMG_FILTER_GAUSSIAN_BLUR);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'contrast':
|
||||
if (isset($args[0])) {
|
||||
// By default it works like this:
|
||||
// (max) -100 <- 0 -> +100 (min)
|
||||
// But we're flipping the polarity to be more predictable:
|
||||
// (min) -100 <- 0 -> +100 (max)
|
||||
$level = intval($args[0]) * -1;
|
||||
}
|
||||
imagefilter($src->image, IMG_FILTER_CONTRAST, $level);
|
||||
break;
|
||||
|
||||
case 'brightness':
|
||||
if (isset($args[0])) {
|
||||
// -255 <- 0 -> +255
|
||||
$level = intval($args[0]);
|
||||
}
|
||||
imagefilter($src->image, IMG_FILTER_BRIGHTNESS, $level);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function canvas_apply_css_funcs($canvas) {
|
||||
|
||||
static $functions;
|
||||
if (! $functions) {
|
||||
$functions = new stdClass();
|
||||
|
||||
$functions->fill = new Functions(['canvas-linear-gradient' => 'CssCrush\canvas_fn_linear_gradient']);
|
||||
|
||||
$functions->generic = new Functions(array_diff_key(Crush::$process->functions->register, $functions->fill->register));
|
||||
|
||||
$functions->filter = new Functions([
|
||||
'contrast' => 'CssCrush\canvas_fn_filter',
|
||||
'opacity' => 'CssCrush\canvas_fn_filter',
|
||||
'colorize' => 'CssCrush\canvas_fn_filter',
|
||||
'grayscale' => 'CssCrush\canvas_fn_filter',
|
||||
'greyscale' => 'CssCrush\canvas_fn_filter',
|
||||
'brightness' => 'CssCrush\canvas_fn_filter',
|
||||
'invert' => 'CssCrush\canvas_fn_filter',
|
||||
'blur' => 'CssCrush\canvas_fn_filter',
|
||||
]);
|
||||
}
|
||||
|
||||
$context = new stdClass();
|
||||
|
||||
foreach ($canvas->raw as $property => &$value) {
|
||||
|
||||
if (! is_string($value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $functions->generic->apply($value);
|
||||
$context->canvas = $canvas;
|
||||
|
||||
if (in_array($property, ['fill', 'background-fill'])) {
|
||||
$context->currentProperty = $property;
|
||||
$value = $functions->fill->apply($value, $context);
|
||||
}
|
||||
elseif ($property === 'canvas-filter') {
|
||||
$value = $functions->filter->apply($value, $context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function canvas_preprocess($canvas) {
|
||||
|
||||
if (isset($canvas->raw['margin'])) {
|
||||
|
||||
$parts = canvas_parselist($canvas->raw['margin']);
|
||||
$count = count($parts);
|
||||
if ($count === 1) {
|
||||
$margin = [$parts[0], $parts[0], $parts[0], $parts[0]];
|
||||
}
|
||||
elseif ($count === 2) {
|
||||
$margin = [$parts[0], $parts[1], $parts[0], $parts[1]];
|
||||
}
|
||||
elseif ($count === 3) {
|
||||
$margin = [$parts[0], $parts[1], $parts[2], $parts[1]];
|
||||
}
|
||||
else {
|
||||
$margin = $parts;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$margin = [0, 0, 0, 0];
|
||||
}
|
||||
|
||||
foreach (['fill', 'background-fill'] as $fill_name) {
|
||||
if (isset($canvas->raw[$fill_name])) {
|
||||
$canvas->fills[$fill_name] = $canvas->raw[$fill_name];
|
||||
}
|
||||
}
|
||||
|
||||
$canvas->margin = (object) [
|
||||
'top' => $margin[0],
|
||||
'right' => $margin[1],
|
||||
'bottom' => $margin[2],
|
||||
'left' => $margin[3],
|
||||
];
|
||||
$canvas->width = $canvas->raw['width'];
|
||||
$canvas->height = $canvas->raw['height'];
|
||||
}
|
||||
|
||||
function canvas_fetch_src($url_token) {
|
||||
|
||||
if ($url_token && $url = Crush::$process->tokens->get($url_token)) {
|
||||
|
||||
$file = $url->getAbsolutePath();
|
||||
|
||||
// Testing the image availability and getting info.
|
||||
if ($info = @getimagesize($file)) {
|
||||
|
||||
$image = null;
|
||||
|
||||
// If image is available copy it.
|
||||
switch ($info['mime']) {
|
||||
case 'image/png':
|
||||
$image = imagecreatefrompng($file);
|
||||
break;
|
||||
case 'image/jpg':
|
||||
case 'image/jpeg':
|
||||
$image = imagecreatefromjpeg($file);
|
||||
break;
|
||||
case 'image/gif':
|
||||
$image = imagecreatefromgif($file);
|
||||
break;
|
||||
case 'image/webp':
|
||||
$image = imagecreatefromwebp($file);
|
||||
break;
|
||||
}
|
||||
if ($image) {
|
||||
return (object) [
|
||||
'file' => $file,
|
||||
'info' => $info,
|
||||
'width' => $info[0],
|
||||
'height' => $info[1],
|
||||
'image' => $image,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Adapted from GD Gradient Fill by Ozh (http://planetozh.com):
|
||||
http://planetozh.com/blog/my-projects/images-php-gd-gradient-fill
|
||||
*/
|
||||
function canvas_gradient($canvas, $fill) {
|
||||
|
||||
$image = $canvas->image;
|
||||
|
||||
// Resolve drawing direction.
|
||||
if ($fill->direction === 'horizontal') {
|
||||
$line_numbers = $fill->x2 - $fill->x1;
|
||||
}
|
||||
else {
|
||||
$line_numbers = $fill->y2 - $fill->y1;
|
||||
}
|
||||
|
||||
list($r1, $g1, $b1, $a1) = $fill->stops[0];
|
||||
list($r2, $g2, $b2, $a2) = $fill->stops[1];
|
||||
|
||||
$r = $g = $b = $a = -1;
|
||||
|
||||
for ($line = 0; $line < $line_numbers; $line++) {
|
||||
|
||||
$last = "$r,$g,$b,$a";
|
||||
|
||||
$r = $r2 - $r1 ? intval($r1 + ($r2 - $r1) * ($line / $line_numbers)): $r1;
|
||||
$g = $g2 - $g1 ? intval($g1 + ($g2 - $g1) * ($line / $line_numbers)): $g1;
|
||||
$b = $b2 - $b1 ? intval($b1 + ($b2 - $b1) * ($line / $line_numbers)): $b1;
|
||||
$a = $a2 - $a1 ? ($a1 + ($a2 - $a1) * ($line / $line_numbers)) : $a1;
|
||||
$a = canvas_opacity($a);
|
||||
|
||||
if ($last != "$r,$g,$b,$a") {
|
||||
$color = imagecolorallocatealpha($image, $r, $g, $b, $a);
|
||||
}
|
||||
|
||||
switch($fill->direction) {
|
||||
case 'horizontal':
|
||||
imagefilledrectangle($image,
|
||||
$fill->x1 + $line,
|
||||
$fill->y1,
|
||||
$fill->x1 + $line,
|
||||
$fill->y2,
|
||||
$color);
|
||||
|
||||
break;
|
||||
case 'vertical':
|
||||
default:
|
||||
imagefilledrectangle($image,
|
||||
$fill->x1,
|
||||
$fill->y1 + $line,
|
||||
$fill->x2,
|
||||
$fill->y1 + $line,
|
||||
$color);
|
||||
break;
|
||||
}
|
||||
imagealphablending($image, true);
|
||||
}
|
||||
}
|
||||
|
||||
function canvas_create($canvas) {
|
||||
|
||||
$margin = $canvas->margin;
|
||||
$width = $canvas->width + $margin->right + $margin->left;
|
||||
$height = $canvas->height + $margin->top + $margin->bottom;
|
||||
|
||||
// Create image object.
|
||||
$canvas->image = canvas_create_transparent($width, $height);
|
||||
}
|
||||
|
||||
function canvas_create_transparent($width, $height) {
|
||||
|
||||
$image = imagecreatetruecolor($width, $height);
|
||||
|
||||
// Set transparent canvas background.
|
||||
imagealphablending($image, false);
|
||||
imagesavealpha($image, true);
|
||||
imagefill($image, 0, 0, imagecolorallocatealpha($image, 0, 0, 0, 127));
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
function canvas_fade($src, $opacity) {
|
||||
|
||||
$width = imagesx($src->image);
|
||||
$height = imagesy($src->image);
|
||||
$new_image = canvas_create_transparent($width, $height);
|
||||
$opacity = canvas_opacity($opacity);
|
||||
|
||||
// Perform pixel-based alpha map application
|
||||
for ($x = 0; $x < $width; $x++) {
|
||||
for ($y = 0; $y < $height; $y++) {
|
||||
$colors = imagecolorsforindex($src->image, imagecolorat($src->image, $x, $y));
|
||||
imagesetpixel($new_image, $x, $y, imagecolorallocatealpha(
|
||||
$new_image, $colors['red'], $colors['green'], $colors['blue'], $opacity));
|
||||
}
|
||||
}
|
||||
|
||||
imagedestroy($src->image);
|
||||
$src->image = $new_image;
|
||||
}
|
||||
|
||||
|
||||
function canvas_fill($canvas, $property) {
|
||||
|
||||
if (! isset($canvas->fills[$property])) {
|
||||
return false;
|
||||
}
|
||||
$fill = $canvas->fills[$property];
|
||||
|
||||
// Gradient fill.
|
||||
if (is_object($fill)) {
|
||||
canvas_gradient($canvas, $fill);
|
||||
}
|
||||
|
||||
// Solid color fill.
|
||||
elseif ($solid = Color::parse($fill)) {
|
||||
|
||||
list($r, $g, $b, $a) = $solid;
|
||||
$color = imagecolorallocatealpha($canvas->image, $r, $g, $b, canvas_opacity($a));
|
||||
|
||||
$fill = new stdClass();
|
||||
$canvas->currentProperty = $property;
|
||||
canvas_set_fill_dims($fill, $canvas);
|
||||
|
||||
imagefilledrectangle($canvas->image, $fill->x1, $fill->y1, $fill->x2, $fill->y2, $color);
|
||||
imagealphablending($canvas->image, true);
|
||||
}
|
||||
|
||||
// Can't parse.
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function canvas_set_fill_dims($fill, $canvas) {
|
||||
|
||||
// Resolve fill dimensions and coordinates.
|
||||
$margin = $canvas->margin;
|
||||
|
||||
$fill->x1 = 0;
|
||||
$fill->y1 = 0;
|
||||
$fill->x2 = $canvas->width + $margin->right + $margin->left;
|
||||
$fill->y2 = $canvas->height + $margin->top + $margin->bottom;
|
||||
|
||||
if (isset($canvas->currentProperty) && $canvas->currentProperty === 'fill') {
|
||||
$fill->x1 = $margin->left;
|
||||
$fill->y1 = $margin->top;
|
||||
$fill->x2 = $canvas->width + $fill->x1 - 1;
|
||||
$fill->y2 = $canvas->height + $fill->y1 - 1;
|
||||
}
|
||||
}
|
||||
|
||||
function canvas_requirements() {
|
||||
|
||||
$requirements_met = true;
|
||||
|
||||
if (! extension_loaded('gd')) {
|
||||
$requirements_met = false;
|
||||
warning('GD extension not available.');
|
||||
}
|
||||
else {
|
||||
$gd_info = implode('|', array_keys(array_filter(gd_info())));
|
||||
|
||||
foreach (['jpe?g' => 'JPG', 'png' => 'PNG'] as $file_ext_patt => $file_ext) {
|
||||
if (! preg_match("~\b(?<ext>$file_ext_patt) support\b~i", $gd_info)) {
|
||||
$requirements_met = false;
|
||||
warning("GD extension has no $file_ext support.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $requirements_met;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Canvas object.
|
||||
*/
|
||||
class Canvas
|
||||
{
|
||||
public $image, $fills = [], $filters = [];
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if (isset($this->image)) {
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Helpers.
|
||||
*/
|
||||
function canvas_opacity($float) {
|
||||
return 127 - max(min(round($float * 127), 127), 0);
|
||||
}
|
||||
|
||||
function canvas_parselist($str, $numbers = true) {
|
||||
$list = preg_split('~ +~', trim($str));
|
||||
return $numbers ? array_map('floatval', $list) : $list;
|
||||
}
|
65
include/thirdparty/css-crush/plugins/ease.php
vendored
Normal file
65
include/thirdparty/css-crush/plugins/ease.php
vendored
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
/**
|
||||
* Expanded easing keywords for transitions
|
||||
*
|
||||
* @see docs/plugins/ease.md
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
\csscrush_plugin('ease', function ($process) {
|
||||
$process->on('rule_prealias', 'CssCrush\ease');
|
||||
});
|
||||
|
||||
function ease(Rule $rule) {
|
||||
|
||||
static $find, $replace, $easing_properties;
|
||||
if (! $find) {
|
||||
$easings = [
|
||||
'ease-in-out-back' => 'cubic-bezier(.680,-0.550,.265,1.550)',
|
||||
'ease-in-out-circ' => 'cubic-bezier(.785,.135,.150,.860)',
|
||||
'ease-in-out-expo' => 'cubic-bezier(1,0,0,1)',
|
||||
'ease-in-out-sine' => 'cubic-bezier(.445,.050,.550,.950)',
|
||||
'ease-in-out-quint' => 'cubic-bezier(.860,0,.070,1)',
|
||||
'ease-in-out-quart' => 'cubic-bezier(.770,0,.175,1)',
|
||||
'ease-in-out-cubic' => 'cubic-bezier(.645,.045,.355,1)',
|
||||
'ease-in-out-quad' => 'cubic-bezier(.455,.030,.515,.955)',
|
||||
'ease-out-back' => 'cubic-bezier(.175,.885,.320,1.275)',
|
||||
'ease-out-circ' => 'cubic-bezier(.075,.820,.165,1)',
|
||||
'ease-out-expo' => 'cubic-bezier(.190,1,.220,1)',
|
||||
'ease-out-sine' => 'cubic-bezier(.390,.575,.565,1)',
|
||||
'ease-out-quint' => 'cubic-bezier(.230,1,.320,1)',
|
||||
'ease-out-quart' => 'cubic-bezier(.165,.840,.440,1)',
|
||||
'ease-out-cubic' => 'cubic-bezier(.215,.610,.355,1)',
|
||||
'ease-out-quad' => 'cubic-bezier(.250,.460,.450,.940)',
|
||||
'ease-in-back' => 'cubic-bezier(.600,-0.280,.735,.045)',
|
||||
'ease-in-circ' => 'cubic-bezier(.600,.040,.980,.335)',
|
||||
'ease-in-expo' => 'cubic-bezier(.950,.050,.795,.035)',
|
||||
'ease-in-sine' => 'cubic-bezier(.470,0,.745,.715)',
|
||||
'ease-in-quint' => 'cubic-bezier(.755,.050,.855,.060)',
|
||||
'ease-in-quart' => 'cubic-bezier(.895,.030,.685,.220)',
|
||||
'ease-in-cubic' => 'cubic-bezier(.550,.055,.675,.190)',
|
||||
'ease-in-quad' => 'cubic-bezier(.550,.085,.680,.530)',
|
||||
];
|
||||
|
||||
$easing_properties = [
|
||||
'transition' => true,
|
||||
'transition-timing-function' => true,
|
||||
];
|
||||
|
||||
foreach ($easings as $property => $value) {
|
||||
$patt = Regex::make("~{{ LB }}$property{{ RB }}~i");
|
||||
$find[] = $patt;
|
||||
$replace[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (! array_intersect_key($rule->declarations->canonicalProperties, $easing_properties)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($rule->declarations->filter(['skip' => false]) as $declaration) {
|
||||
if (isset($easing_properties[$declaration->canonicalProperty])) {
|
||||
$declaration->value = preg_replace($find, $replace, $declaration->value);
|
||||
}
|
||||
}
|
||||
}
|
33
include/thirdparty/css-crush/plugins/forms.php
vendored
Normal file
33
include/thirdparty/css-crush/plugins/forms.php
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
/**
|
||||
* Pseudo classes for working with forms
|
||||
*
|
||||
* @see docs/plugins/forms.md
|
||||
*/
|
||||
namespace CssCrush;
|
||||
|
||||
\csscrush_plugin('forms', function ($process) {
|
||||
foreach (forms() as $name => $handler) {
|
||||
if (is_array($handler)) {
|
||||
$type = $handler['type'];
|
||||
$handler = $handler['handler'];
|
||||
}
|
||||
$process->addSelectorAlias($name, $handler, $type);
|
||||
}
|
||||
});
|
||||
|
||||
function forms() {
|
||||
return [
|
||||
'input' => [
|
||||
'type' => 'splat',
|
||||
'handler' => 'input[type=#(text)]',
|
||||
],
|
||||
'checkbox' => 'input[type="checkbox"]',
|
||||
'radio' => 'input[type="radio"]',
|
||||
'file' => 'input[type="file"]',
|
||||
'image' => 'input[type="image"]',
|
||||
'password' => 'input[type="password"]',
|
||||
'submit' => 'input[type="submit"]',
|
||||
'text' => 'input[type="text"]',
|
||||
];
|
||||
}
|
10
include/thirdparty/css-crush/plugins/hocus-pocus.php
vendored
Normal file
10
include/thirdparty/css-crush/plugins/hocus-pocus.php
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
/**
|
||||
* :hover/:focus and :hover/:focus/:active composite pseudo classes
|
||||
*
|
||||
* @see docs/plugins/hocus-pocus.md
|
||||
*/
|
||||
csscrush_plugin('hocus-pocus', function ($process) {
|
||||
$process->addSelectorAlias('hocus', ':any(:hover,:focus)');
|
||||
$process->addSelectorAlias('pocus', ':any(:hover,:focus,:active)');
|
||||
});
|
160
include/thirdparty/css-crush/plugins/property-sorter.php
vendored
Normal file
160
include/thirdparty/css-crush/plugins/property-sorter.php
vendored
Normal file
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
/**
|
||||
* Customizable property sorting
|
||||
*
|
||||
* @see docs/plugins/property-sorter.md
|
||||
*/
|
||||
namespace CssCrush {
|
||||
|
||||
\csscrush_plugin('property-sorter', function ($process) {
|
||||
$process->on('rule_prealias', 'CssCrush\property_sorter');
|
||||
});
|
||||
|
||||
function property_sorter(Rule $rule) {
|
||||
|
||||
usort($rule->declarations->store, 'CssCrush\property_sorter_callback');
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Callback for sorting.
|
||||
*/
|
||||
function property_sorter_callback($a, $b) {
|
||||
|
||||
$map =& property_sorter_get_table();
|
||||
$a_prop =& $a->canonicalProperty;
|
||||
$b_prop =& $b->canonicalProperty;
|
||||
$a_listed = isset($map[$a_prop]);
|
||||
$b_listed = isset($map[$b_prop]);
|
||||
|
||||
// If the properties are identical we need to flag for an index comparison.
|
||||
$compare_indexes = false;
|
||||
|
||||
// If the 'canonical' properties are identical we need to flag for a vendor comparison.
|
||||
$compare_vendor = false;
|
||||
|
||||
// If both properties are listed.
|
||||
if ($a_listed && $b_listed) {
|
||||
|
||||
if ($a_prop === $b_prop) {
|
||||
if ($a->vendor || $b->vendor) {
|
||||
$compare_vendor = true;
|
||||
}
|
||||
else {
|
||||
$compare_indexes = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Table comparison.
|
||||
return $map[$a_prop] > $map[$b_prop] ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
// If one property is listed it always takes higher priority.
|
||||
elseif ($a_listed && ! $b_listed) {
|
||||
return -1;
|
||||
}
|
||||
elseif ($b_listed && ! $a_listed) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// If neither property is listed.
|
||||
else {
|
||||
|
||||
if ($a_prop === $b_prop) {
|
||||
if ($a->vendor || $b->vendor) {
|
||||
$compare_vendor = true;
|
||||
}
|
||||
else {
|
||||
$compare_indexes = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Regular sort.
|
||||
return $a_prop > $b_prop ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Comparing by index.
|
||||
if ($compare_indexes ) {
|
||||
return $a->index > $b->index ? 1 : -1;
|
||||
}
|
||||
|
||||
// Comparing by vendor mark.
|
||||
if ($compare_vendor) {
|
||||
if (! $a->vendor && $b->vendor) {
|
||||
return 1;
|
||||
}
|
||||
elseif ($a->vendor && ! $b->vendor) {
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
// If both have a vendor mark compare vendor name length.
|
||||
return strlen($b->vendor) > strlen($a->vendor) ? 1 : -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Cache for the table of values to compare against.
|
||||
*/
|
||||
function &property_sorter_get_table () {
|
||||
|
||||
// Check for cached table.
|
||||
if (isset($GLOBALS['CSSCRUSH_PROPERTY_SORT_ORDER_CACHE'])) {
|
||||
return $GLOBALS['CSSCRUSH_PROPERTY_SORT_ORDER_CACHE'];
|
||||
}
|
||||
|
||||
$table = [];
|
||||
|
||||
// Nothing cached, check for a user-defined table.
|
||||
if (isset($GLOBALS['CSSCRUSH_PROPERTY_SORT_ORDER'])) {
|
||||
$table = (array) $GLOBALS['CSSCRUSH_PROPERTY_SORT_ORDER'];
|
||||
}
|
||||
|
||||
// No user-defined table, use pre-defined.
|
||||
else {
|
||||
|
||||
// Load from property-sorting.ini.
|
||||
$sorting_file_contents = file_get_contents(Crush::$dir . '/misc/property-sorting.ini');
|
||||
if ($sorting_file_contents !== false) {
|
||||
|
||||
$sorting_file_contents = preg_replace('~;[^\r\n]*~', '', $sorting_file_contents);
|
||||
$table = preg_split('~\s+~', trim($sorting_file_contents));
|
||||
}
|
||||
else {
|
||||
notice("Property sorting file not found.");
|
||||
}
|
||||
|
||||
// Store to the global variable.
|
||||
$GLOBALS['CSSCRUSH_PROPERTY_SORT_ORDER'] = $table;
|
||||
}
|
||||
|
||||
// Cache the table (and flip it).
|
||||
$GLOBALS['CSSCRUSH_PROPERTY_SORT_ORDER_CACHE'] = array_flip($table);
|
||||
|
||||
return $GLOBALS['CSSCRUSH_PROPERTY_SORT_ORDER_CACHE'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
/*
|
||||
Get the current sorting table.
|
||||
*/
|
||||
function csscrush_get_property_sort_order() {
|
||||
CssCrush\property_sorter_get_table();
|
||||
return $GLOBALS['CSSCRUSH_PROPERTY_SORT_ORDER'];
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Set a custom sorting table.
|
||||
*/
|
||||
function csscrush_set_property_sort_order(array $new_order) {
|
||||
unset($GLOBALS['CSSCRUSH_PROPERTY_SORT_ORDER_CACHE']);
|
||||
$GLOBALS['CSSCRUSH_PROPERTY_SORT_ORDER'] = $new_order;
|
||||
}
|
||||
}
|
1105
include/thirdparty/css-crush/plugins/svg.php
vendored
Normal file
1105
include/thirdparty/css-crush/plugins/svg.php
vendored
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue