Version Description
Download this release
Release Info
Developer | connectthink |
Plugin | WP-SCSS |
Version | 1.1.1 |
Comparing to | |
See all releases |
Version 1.1.1
- class/class-wp-scss.php +194 -0
- options.php +240 -0
- readme.txt +67 -0
- scssphp/scss.inc.php +4371 -0
- wp-scss.php +241 -0
class/class-wp-scss.php
ADDED
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class Wp_Scss {
|
4 |
+
/**
|
5 |
+
* Compiling preferences properites
|
6 |
+
*
|
7 |
+
* @var string
|
8 |
+
* @access public
|
9 |
+
*/
|
10 |
+
public $scss_dir, $css_dir, $compile_method, $scssc, $compile_errors;
|
11 |
+
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Set values for Wp_Scss::properties
|
15 |
+
*
|
16 |
+
* @param string scss_dir - path to source directory for scss files
|
17 |
+
* @param string css_dir - path to output directory for css files
|
18 |
+
* @param string method - type of compile (compressed, expanded, etc)
|
19 |
+
*
|
20 |
+
* @var object scssc - instantiate the compiling object.
|
21 |
+
*
|
22 |
+
* @var array compile_errors - catches errors from compile
|
23 |
+
*/
|
24 |
+
public function __construct ($scss_dir, $css_dir, $compile_method) {
|
25 |
+
$this->scss_dir = $scss_dir;
|
26 |
+
$this->css_dir = $css_dir;
|
27 |
+
$this->compile_method = $compile_method;
|
28 |
+
|
29 |
+
global $scssc;
|
30 |
+
$scssc = new scssc();
|
31 |
+
$scssc->setFormatter($compile_method);
|
32 |
+
$scssc->setImportPaths($scss_dir);
|
33 |
+
|
34 |
+
$this->compile_errors = array();
|
35 |
+
}
|
36 |
+
|
37 |
+
/**
|
38 |
+
* METHOD COMPILE
|
39 |
+
* Loops through scss directory and compilers files that end
|
40 |
+
* with .scss and do not have '_' in front.
|
41 |
+
*
|
42 |
+
* @function compiler - passes input content through scssphp,
|
43 |
+
* puts compiled css into cache file
|
44 |
+
*
|
45 |
+
* @var array input_files - array of .scss files with no '_' in front
|
46 |
+
* @var array sdir_arr - an array of all the files in the scss directory
|
47 |
+
*
|
48 |
+
* @return nothing - Puts successfully compiled css into apporpriate location
|
49 |
+
* Puts error in 'compile_errors' property
|
50 |
+
* @access public
|
51 |
+
*/
|
52 |
+
public function compile() {
|
53 |
+
global $scssc, $cache;
|
54 |
+
$cache = WPSCSS_PLUGIN_DIR . '/cache/';
|
55 |
+
|
56 |
+
//Compiler - Takes scss $in and writes compiled css to $out file
|
57 |
+
// catches errors and puts them the object's compiled_errors property
|
58 |
+
function compiler($in, $out, $instance) {
|
59 |
+
global $scssc, $cache;
|
60 |
+
|
61 |
+
if (is_writable($cache)) {
|
62 |
+
try {
|
63 |
+
$css = $scssc->compile(file_get_contents($in));
|
64 |
+
file_put_contents($cache.basename($out), $css);
|
65 |
+
} catch (Exception $e) {
|
66 |
+
$errors = array (
|
67 |
+
'file' => basename($in),
|
68 |
+
'message' => $e->getMessage(),
|
69 |
+
);
|
70 |
+
array_push($instance->compile_errors, $errors);
|
71 |
+
}
|
72 |
+
} else {
|
73 |
+
$errors = array (
|
74 |
+
'file' => "/wp-plugins/wp-scss/cache/",
|
75 |
+
'message' => "File Permission Error, permission denied. Please make the cache directory writable."
|
76 |
+
);
|
77 |
+
array_push($instance->compile_errors, $errors);
|
78 |
+
}
|
79 |
+
}
|
80 |
+
|
81 |
+
$input_files = array();
|
82 |
+
// Loop through directory and get .scss file that do not start with '_'
|
83 |
+
foreach(new DirectoryIterator($this->scss_dir) as $file) {
|
84 |
+
if (substr($file, 0, 1) != "_" && pathinfo($file->getFilename(), PATHINFO_EXTENSION) == 'scss') {
|
85 |
+
array_push($input_files, $file->getFilename());
|
86 |
+
}
|
87 |
+
}
|
88 |
+
|
89 |
+
// For each input file, find matching css file and compile
|
90 |
+
foreach ($input_files as $scss_file) {
|
91 |
+
$input = $this->scss_dir.$scss_file;
|
92 |
+
$outputName = preg_replace("/\.[^$]*/",".css", $scss_file);
|
93 |
+
$output = $this->css_dir.$outputName;
|
94 |
+
|
95 |
+
compiler($input, $output, $this);
|
96 |
+
}
|
97 |
+
|
98 |
+
if (count($this->compile_errors) < 1) {
|
99 |
+
if ( is_writable($this->css_dir) ) {
|
100 |
+
foreach (new DirectoryIterator($cache) as $cache_file) {
|
101 |
+
if ( pathinfo($cache_file->getFilename(), PATHINFO_EXTENSION) == 'css') {
|
102 |
+
file_put_contents($this->css_dir.$cache_file, file_get_contents($cache.$cache_file));
|
103 |
+
unlink($cache.$cache_file->getFilename()); // Delete file on successful write
|
104 |
+
}
|
105 |
+
}
|
106 |
+
} else {
|
107 |
+
$errors = array(
|
108 |
+
'file' => 'CSS Directory',
|
109 |
+
'message' => "File Permissions Error, permission denied. Please make your CSS directory writable."
|
110 |
+
);
|
111 |
+
array_push($this->compile_errors, $errors);
|
112 |
+
}
|
113 |
+
}
|
114 |
+
}
|
115 |
+
|
116 |
+
|
117 |
+
/**
|
118 |
+
* METHOD NEEDS_COMPILING
|
119 |
+
* Gets the most recently modified file in the scss directory
|
120 |
+
* and compares that do the most recently modified css file.
|
121 |
+
* If scss is greater, we assume that changes have been made
|
122 |
+
* and compiling needs to occur to update css.
|
123 |
+
*
|
124 |
+
* @param string scss_dir - path to scss folder
|
125 |
+
* @param string css_dir - path to css folder
|
126 |
+
*
|
127 |
+
* @var array sdir_arr - scss directory files
|
128 |
+
* @var array cdir_arr - css directory files
|
129 |
+
*
|
130 |
+
* @var string latest_scss - file mod time of the most recent file change
|
131 |
+
* @var string latest_css - file mod time of the most recent file change
|
132 |
+
*
|
133 |
+
* @return bool - true if compiling is needed
|
134 |
+
*/
|
135 |
+
public function needs_compiling() {
|
136 |
+
$latest_scss = 0;
|
137 |
+
$latest_css = 0;
|
138 |
+
|
139 |
+
foreach ( new DirectoryIterator($this->scss_dir) as $sfile ) {
|
140 |
+
if (pathinfo($sfile->getFilename(), PATHINFO_EXTENSION) == 'scss') {
|
141 |
+
$file_time = $sfile->getMTime();
|
142 |
+
|
143 |
+
if ( (int) $file_time > $latest_scss) {
|
144 |
+
$latest_scss = $file_time;
|
145 |
+
}
|
146 |
+
}
|
147 |
+
}
|
148 |
+
|
149 |
+
foreach ( new DirectoryIterator($this->css_dir) as $cfile ) {
|
150 |
+
if (pathinfo($cfile->getFilename(), PATHINFO_EXTENSION) == 'css') {
|
151 |
+
$file_time = $cfile->getMTime();
|
152 |
+
|
153 |
+
if ( (int) $file_time > $latest_css) {
|
154 |
+
$latest_css = $file_time;
|
155 |
+
}
|
156 |
+
}
|
157 |
+
}
|
158 |
+
|
159 |
+
if ($latest_scss > $latest_css) {
|
160 |
+
return true;
|
161 |
+
} else {
|
162 |
+
return false;
|
163 |
+
}
|
164 |
+
}
|
165 |
+
|
166 |
+
/**
|
167 |
+
* METHOD ENQUEUE STYLES
|
168 |
+
* Enqueues all styles in the css directory.
|
169 |
+
*
|
170 |
+
* @param $css_folder - directory from theme root. We need this passed in separately
|
171 |
+
* so it can be used in a url, not path
|
172 |
+
*/
|
173 |
+
public function enqueue_files($css_folder) {
|
174 |
+
|
175 |
+
foreach( new DirectoryIterator($this->css_dir) as $stylesheet ) {
|
176 |
+
if ( pathinfo($stylesheet->getFilename(), PATHINFO_EXTENSION) == 'css' ) {
|
177 |
+
$name = $stylesheet->getBasename('.css') . '-style';
|
178 |
+
$uri = get_stylesheet_directory_uri().$css_folder.$stylesheet->getFilename();
|
179 |
+
$ver = $stylesheet->getMTime();
|
180 |
+
|
181 |
+
|
182 |
+
wp_register_style(
|
183 |
+
$name,
|
184 |
+
$uri,
|
185 |
+
array(),
|
186 |
+
$ver,
|
187 |
+
$media = 'all' );
|
188 |
+
|
189 |
+
wp_enqueue_style( $name );
|
190 |
+
}
|
191 |
+
}
|
192 |
+
}
|
193 |
+
|
194 |
+
} // End Wp_Scss Class
|
options.php
ADDED
@@ -0,0 +1,240 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
class Wp_Scss_Settings
|
3 |
+
{
|
4 |
+
/**
|
5 |
+
* Holds the values to be used in the fields callbacks
|
6 |
+
*/
|
7 |
+
private $options;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Start up
|
11 |
+
*/
|
12 |
+
public function __construct()
|
13 |
+
{
|
14 |
+
add_action( 'admin_menu', array( $this, 'add_plugin_page' ) );
|
15 |
+
add_action( 'admin_init', array( $this, 'page_init' ) );
|
16 |
+
}
|
17 |
+
|
18 |
+
/**
|
19 |
+
* Add options page
|
20 |
+
*/
|
21 |
+
public function add_plugin_page()
|
22 |
+
{
|
23 |
+
// This page will be under "Settings"
|
24 |
+
add_options_page(
|
25 |
+
'Settings Admin',
|
26 |
+
'WP-SCSS',
|
27 |
+
'manage_options',
|
28 |
+
'wpscss_options',
|
29 |
+
array( $this, 'create_admin_page' )
|
30 |
+
);
|
31 |
+
}
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Options page callback
|
35 |
+
*/
|
36 |
+
public function create_admin_page()
|
37 |
+
{
|
38 |
+
// Set class property
|
39 |
+
$this->options = get_option( 'wpscss_options' );
|
40 |
+
?>
|
41 |
+
<div class="wrap">
|
42 |
+
<?php screen_icon(); ?>
|
43 |
+
<h2>WP-SCSS Settings</h2>
|
44 |
+
<p>
|
45 |
+
<span class="version">Version <em><?php echo get_option('wpscss_version'); ?></em>
|
46 |
+
<br/>
|
47 |
+
<span class="author">By: <a href="http://connectthink.com" target="_blank">Connect Think</a></span>
|
48 |
+
<br/>
|
49 |
+
<span class="repo">Help & Issues: <a href="https://github.com/ConnectThink/WP-SCSS" target="_blank">Github</a></span>
|
50 |
+
</p>
|
51 |
+
<form method="post" action="options.php">
|
52 |
+
<?php
|
53 |
+
// This prints out all hidden setting fields
|
54 |
+
settings_fields( 'wpscss_options_group' );
|
55 |
+
do_settings_sections( 'wpscss_options' );
|
56 |
+
submit_button();
|
57 |
+
?>
|
58 |
+
</form>
|
59 |
+
</div>
|
60 |
+
<?php
|
61 |
+
}
|
62 |
+
|
63 |
+
/**
|
64 |
+
* Register and add settings
|
65 |
+
*/
|
66 |
+
public function page_init()
|
67 |
+
{
|
68 |
+
register_setting(
|
69 |
+
'wpscss_options_group', // Option group
|
70 |
+
'wpscss_options', // Option name
|
71 |
+
array( $this, 'sanitize' ) // Sanitize
|
72 |
+
);
|
73 |
+
|
74 |
+
// Paths to Directories
|
75 |
+
add_settings_section(
|
76 |
+
'wpscss_paths_section', // ID
|
77 |
+
'Configure Paths', // Title
|
78 |
+
array( $this, 'print_paths_info' ), // Callback
|
79 |
+
'wpscss_options' // Page
|
80 |
+
);
|
81 |
+
add_settings_field(
|
82 |
+
'wpscss_scss_dir', // ID
|
83 |
+
'Scss Location', // Title
|
84 |
+
array( $this, 'scss_dir_callback' ), // Callback
|
85 |
+
'wpscss_options', // Page
|
86 |
+
'wpscss_paths_section' // Section
|
87 |
+
);
|
88 |
+
add_settings_field(
|
89 |
+
'wpscss_css_dir',
|
90 |
+
'CSS Location',
|
91 |
+
array( $this, 'css_dir_callback' ),
|
92 |
+
'wpscss_options',
|
93 |
+
'wpscss_paths_section'
|
94 |
+
);
|
95 |
+
|
96 |
+
// Compiling Options
|
97 |
+
add_settings_section(
|
98 |
+
'wpscss_compile_section', // ID
|
99 |
+
'Compiling Options', // Title
|
100 |
+
array( $this, 'print_compile_info' ), // Callback
|
101 |
+
'wpscss_options' // Page
|
102 |
+
);
|
103 |
+
add_settings_field(
|
104 |
+
'Compiling Mode',
|
105 |
+
'Compiling Mode',
|
106 |
+
array( $this, 'compiling_mode_callback' ), // Callback
|
107 |
+
'wpscss_options', // Page
|
108 |
+
'wpscss_compile_section' // Section
|
109 |
+
);
|
110 |
+
add_settings_field(
|
111 |
+
'Error Display',
|
112 |
+
'Error Display',
|
113 |
+
array( $this, 'errors_callback' ), // Callback
|
114 |
+
'wpscss_options', // Page
|
115 |
+
'wpscss_compile_section' // Section
|
116 |
+
);
|
117 |
+
|
118 |
+
// Compiling Options
|
119 |
+
add_settings_section(
|
120 |
+
'wpscss_compile_section', // ID
|
121 |
+
'Compiling Options', // Title
|
122 |
+
array( $this, 'print_compile_info' ), // Callback
|
123 |
+
'wpscss_options' // Page
|
124 |
+
);
|
125 |
+
add_settings_field(
|
126 |
+
'Compiling Mode',
|
127 |
+
'Compiling Mode',
|
128 |
+
array( $this, 'compiling_mode_callback' ), // Callback
|
129 |
+
'wpscss_options', // Page
|
130 |
+
'wpscss_compile_section' // Section
|
131 |
+
);
|
132 |
+
add_settings_field(
|
133 |
+
'Error Display',
|
134 |
+
'Error Display',
|
135 |
+
array( $this, 'errors_callback' ), // Callback
|
136 |
+
'wpscss_options', // Page
|
137 |
+
'wpscss_compile_section' // Section
|
138 |
+
);
|
139 |
+
|
140 |
+
// Enqueuing Options
|
141 |
+
add_settings_section(
|
142 |
+
'wpscss_enqueue_section', // ID
|
143 |
+
'Enqueuing Options', // Title
|
144 |
+
array( $this, 'print_enqueue_info' ), // Callback
|
145 |
+
'wpscss_options' // Page
|
146 |
+
);
|
147 |
+
add_settings_field(
|
148 |
+
'Enqueue Stylesheets',
|
149 |
+
'Enqueue Stylesheets',
|
150 |
+
array( $this, 'enqueue_callback' ), // Callback
|
151 |
+
'wpscss_options', // Page
|
152 |
+
'wpscss_enqueue_section' // Section
|
153 |
+
);
|
154 |
+
|
155 |
+
}
|
156 |
+
|
157 |
+
/**
|
158 |
+
* Sanitize each setting field as needed
|
159 |
+
*
|
160 |
+
* @param array $input Contains all settings fields as array keys
|
161 |
+
*/
|
162 |
+
public function sanitize( $input ) {
|
163 |
+
|
164 |
+
if( !empty( $input['wpscss_scss_dir'] ) )
|
165 |
+
$input['wpscss_scss_dir'] = sanitize_text_field( $input['wpscss_scss_dir'] );
|
166 |
+
|
167 |
+
if( !empty( $input['wpscss_css_dir'] ) )
|
168 |
+
$input['wpscss_css_dir'] = sanitize_text_field( $input['wpscss_css_dir'] );
|
169 |
+
|
170 |
+
return $input;
|
171 |
+
}
|
172 |
+
|
173 |
+
/**
|
174 |
+
* Print the Section text
|
175 |
+
*/
|
176 |
+
public function print_paths_info() {
|
177 |
+
print 'Add the paths to your directories below. Paths should start with the root of your theme. example: "/library/scss/"';
|
178 |
+
}
|
179 |
+
public function print_compile_info() {
|
180 |
+
print 'Choose how you would like SCSS to be compiled and how you would like the plugin to handle errors';
|
181 |
+
}
|
182 |
+
public function print_enqueue_info() {
|
183 |
+
print 'WP-SCSS can enqueue your css stylesheets in the header automatically.';
|
184 |
+
}
|
185 |
+
|
186 |
+
/**
|
187 |
+
* Text Fields' Callbacks
|
188 |
+
*/
|
189 |
+
public function scss_dir_callback() {
|
190 |
+
printf(
|
191 |
+
'<input type="text" id="scss_dir" name="wpscss_options[scss_dir]" value="%s" />',
|
192 |
+
esc_attr( $this->options['scss_dir'])
|
193 |
+
);
|
194 |
+
}
|
195 |
+
public function css_dir_callback() {
|
196 |
+
printf(
|
197 |
+
'<input type="text" id="css_dir" name="wpscss_options[css_dir]" value="%s" />',
|
198 |
+
esc_attr( $this->options['css_dir'])
|
199 |
+
);
|
200 |
+
}
|
201 |
+
|
202 |
+
/**
|
203 |
+
* Select Boxes' Callbacks
|
204 |
+
*/
|
205 |
+
public function compiling_mode_callback() {
|
206 |
+
$this->options = get_option( 'wpscss_options' );
|
207 |
+
|
208 |
+
$html = '<select id="compiling_options" name="wpscss_options[compiling_options]">';
|
209 |
+
$html .= '<option value="scss_formatter"' . selected( $this->options['compiling_options'], 'scss_formatter', false) . '>Expanded</option>';
|
210 |
+
$html .= '<option value="scss_formatter_nested"' . selected( $this->options['compiling_options'], 'scss_formatter_nested', false) . '>Nested</option>';
|
211 |
+
$html .= '<option value="scss_formatter_compressed"' . selected( $this->options['compiling_options'], 'scss_formatter_compressed', false) . '>Compressed</option>';
|
212 |
+
$html .= '</select>';
|
213 |
+
|
214 |
+
echo $html;
|
215 |
+
}
|
216 |
+
public function errors_callback() {
|
217 |
+
$this->options = get_option( 'wpscss_options' );
|
218 |
+
|
219 |
+
$html = '<select id="errors" name="wpscss_options[errors]">';
|
220 |
+
$html .= '<option value="show"' . selected( $this->options['errors'], 'show', false) . '>Show in Header</option>';
|
221 |
+
$html .= '<option value="log"' . selected( $this->options['errors'], 'hide', false) . '>Print to Log</option>';
|
222 |
+
$html .= '</select>';
|
223 |
+
|
224 |
+
echo $html;
|
225 |
+
}
|
226 |
+
|
227 |
+
/**
|
228 |
+
* Checkboxes' Callbacks
|
229 |
+
*/
|
230 |
+
function enqueue_callback() {
|
231 |
+
$this->options = get_option( 'wpscss_options' );
|
232 |
+
|
233 |
+
$html = '<input type="checkbox" id="enqueue" name="wpscss_options[enqueue]" value="1"' . checked( 1, $this->options['enqueue'], false ) . '/>';
|
234 |
+
$html .= '<label for="enqueue"></label>';
|
235 |
+
|
236 |
+
echo $html;
|
237 |
+
}
|
238 |
+
|
239 |
+
}
|
240 |
+
|
readme.txt
ADDED
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
=== WP-SCSS ===
|
2 |
+
Contributors: connectthink
|
3 |
+
Tags: sass, scss, css
|
4 |
+
Plugin URI: https://github.com/ConnectThink/WP-SCSS
|
5 |
+
Requires at least: 3.0.1
|
6 |
+
Tested up to: 3.8
|
7 |
+
Stable tag: 1.1.1
|
8 |
+
License: GPLv3 or later
|
9 |
+
License URI: http://www.gnu.org/copyleft/gpl.html
|
10 |
+
|
11 |
+
Compiles .scss files to .css and enqueues them.
|
12 |
+
|
13 |
+
== Description ==
|
14 |
+
|
15 |
+
Compiles .scss files on your wordpress install using [lefo's scssphp](https://github.com/leafo/scssphp). Includes settings page for configuring directories, error reporting, compiling options, and auto enqueuing.
|
16 |
+
|
17 |
+
The plugin only compiles when changes have been made to the scss files. Compiles are made to the matching css file, so disabling this plugin will not take down your stylesheets. In the instance where a matching css file does not exist yet, the plugin will create the appropriate css file in the css directory.
|
18 |
+
|
19 |
+
[Get detailed instructions on github](https://github.com/ConnectThink/WP-SCSS)
|
20 |
+
|
21 |
+
== Installation ==
|
22 |
+
|
23 |
+
1. Upload plugin to plugins directory
|
24 |
+
2. Active plugin through the 'Plugins' menu in Wordpress
|
25 |
+
3. Configure plugin options through settings page `settings -> WP-SCSS`.
|
26 |
+
|
27 |
+
== Frequently Asked Questions ==
|
28 |
+
|
29 |
+
= How do I @import subfiles =
|
30 |
+
|
31 |
+
You can import other scss files into parent files and compile them into a single css file. To do this, use @import as normal in your scss file. All imported file names *must* start with an underscore. Otherwise they will be compiled into their own css file.
|
32 |
+
|
33 |
+
When importing in your scss file, you can leave off the underscore.
|
34 |
+
|
35 |
+
> `@import 'subfile';`
|
36 |
+
|
37 |
+
|
38 |
+
= Does this plugin support Compass? =
|
39 |
+
Currently there isn't a way to fully support [compass](https://github.com/chriseppstein/compass) with a php compiler. If you want limited support, you can manually import the compass framework. You'll need both the _compass.scss and compass directory.
|
40 |
+
|
41 |
+
|
42 |
+
> `compass / frameworks / compass / stylesheets /
|
43 |
+
|
44 |
+
> `@import 'compass';`
|
45 |
+
|
46 |
+
|
47 |
+
Alternatively, you can include [Bourbon](https://github.com/thoughtbot/bourbon) in a similar fashion.
|
48 |
+
|
49 |
+
= Can I use .sass syntax with this Plugin? =
|
50 |
+
This plugin will only work with .scss format.
|
51 |
+
|
52 |
+
= It's not updating my css, what's happening? =
|
53 |
+
Do you have errors printing to the front end? If not, check your log file in your scss directory. The css will not be updated if there are errors in your sass file(s).
|
54 |
+
|
55 |
+
Make sure your directories are properly defined in the settings. Paths are defined from the root of the theme.
|
56 |
+
|
57 |
+
= I'm having other issues and need help =
|
58 |
+
If you are having issues with the plugin, create an issue on [github](https://github.com/ConnectThink/WP-SCSS), and we'll do our best to help.
|
59 |
+
|
60 |
+
== Changelog ==
|
61 |
+
|
62 |
+
= 1.1 =
|
63 |
+
* Added error handling for file permissions issues
|
64 |
+
* Changed error log to .log for auto updating errors
|
65 |
+
|
66 |
+
= 1.0 =
|
67 |
+
* Initial Build
|
scssphp/scss.inc.php
ADDED
@@ -0,0 +1,4371 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* SCSS compiler written in PHP
|
4 |
+
*
|
5 |
+
* @copyright 2012-2013 Leaf Corcoran
|
6 |
+
*
|
7 |
+
* @license http://opensource.org/licenses/gpl-license GPL-3.0
|
8 |
+
* @license http://opensource.org/licenses/MIT MIT
|
9 |
+
*
|
10 |
+
* @link http://leafo.net/scssphp
|
11 |
+
*/
|
12 |
+
|
13 |
+
/**
|
14 |
+
* The scss compiler and parser.
|
15 |
+
*
|
16 |
+
* Converting SCSS to CSS is a three stage process. The incoming file is parsed
|
17 |
+
* by `scssc_parser` into a syntax tree, then it is compiled into another tree
|
18 |
+
* representing the CSS structure by `scssc`. The CSS tree is fed into a
|
19 |
+
* formatter, like `scssc_formatter` which then outputs CSS as a string.
|
20 |
+
*
|
21 |
+
* During the first compile, all values are *reduced*, which means that their
|
22 |
+
* types are brought to the lowest form before being dump as strings. This
|
23 |
+
* handles math equations, variable dereferences, and the like.
|
24 |
+
*
|
25 |
+
* The `parse` function of `scssc` is the entry point.
|
26 |
+
*
|
27 |
+
* In summary:
|
28 |
+
*
|
29 |
+
* The `scssc` class creates an instance of the parser, feeds it SCSS code,
|
30 |
+
* then transforms the resulting tree to a CSS tree. This class also holds the
|
31 |
+
* evaluation context, such as all available mixins and variables at any given
|
32 |
+
* time.
|
33 |
+
*
|
34 |
+
* The `scssc_parser` class is only concerned with parsing its input.
|
35 |
+
*
|
36 |
+
* The `scssc_formatter` takes a CSS tree, and dumps it to a formatted string,
|
37 |
+
* handling things like indentation.
|
38 |
+
*/
|
39 |
+
|
40 |
+
/**
|
41 |
+
* SCSS compiler
|
42 |
+
*
|
43 |
+
* @author Leaf Corcoran <leafot@gmail.com>
|
44 |
+
*/
|
45 |
+
class scssc {
|
46 |
+
static public $VERSION = "v0.0.8";
|
47 |
+
|
48 |
+
static protected $operatorNames = array(
|
49 |
+
'+' => "add",
|
50 |
+
'-' => "sub",
|
51 |
+
'*' => "mul",
|
52 |
+
'/' => "div",
|
53 |
+
'%' => "mod",
|
54 |
+
|
55 |
+
'==' => "eq",
|
56 |
+
'!=' => "neq",
|
57 |
+
'<' => "lt",
|
58 |
+
'>' => "gt",
|
59 |
+
|
60 |
+
'<=' => "lte",
|
61 |
+
'>=' => "gte",
|
62 |
+
);
|
63 |
+
|
64 |
+
static protected $namespaces = array(
|
65 |
+
"special" => "%",
|
66 |
+
"mixin" => "@",
|
67 |
+
"function" => "^",
|
68 |
+
);
|
69 |
+
|
70 |
+
static protected $numberPrecision = 5;
|
71 |
+
static protected $unitTable = array(
|
72 |
+
"in" => array(
|
73 |
+
"in" => 1,
|
74 |
+
"pt" => 72,
|
75 |
+
"pc" => 6,
|
76 |
+
"cm" => 2.54,
|
77 |
+
"mm" => 25.4,
|
78 |
+
"px" => 96,
|
79 |
+
)
|
80 |
+
);
|
81 |
+
|
82 |
+
static public $true = array("keyword", "true");
|
83 |
+
static public $false = array("keyword", "false");
|
84 |
+
static public $null = array("null");
|
85 |
+
|
86 |
+
static public $defaultValue = array("keyword", "");
|
87 |
+
static public $selfSelector = array("self");
|
88 |
+
|
89 |
+
protected $importPaths = array("");
|
90 |
+
protected $importCache = array();
|
91 |
+
|
92 |
+
protected $userFunctions = array();
|
93 |
+
|
94 |
+
protected $formatter = "scss_formatter_nested";
|
95 |
+
|
96 |
+
function compile($code, $name=null) {
|
97 |
+
$this->indentLevel = -1;
|
98 |
+
$this->commentsSeen = array();
|
99 |
+
$this->extends = array();
|
100 |
+
$this->extendsMap = array();
|
101 |
+
|
102 |
+
$locale = setlocale(LC_NUMERIC, 0);
|
103 |
+
setlocale(LC_NUMERIC, "C");
|
104 |
+
|
105 |
+
$this->parsedFiles = array();
|
106 |
+
$this->parser = new scss_parser($name);
|
107 |
+
$tree = $this->parser->parse($code);
|
108 |
+
|
109 |
+
$this->formatter = new $this->formatter();
|
110 |
+
|
111 |
+
$this->env = null;
|
112 |
+
$this->scope = null;
|
113 |
+
|
114 |
+
$this->compileRoot($tree);
|
115 |
+
|
116 |
+
$out = $this->formatter->format($this->scope);
|
117 |
+
|
118 |
+
setlocale(LC_NUMERIC, $locale);
|
119 |
+
return $out;
|
120 |
+
}
|
121 |
+
|
122 |
+
protected function isSelfExtend($target, $origin) {
|
123 |
+
foreach ($origin as $sel) {
|
124 |
+
if (in_array($target, $sel)) {
|
125 |
+
return true;
|
126 |
+
}
|
127 |
+
}
|
128 |
+
|
129 |
+
return false;
|
130 |
+
}
|
131 |
+
|
132 |
+
protected function pushExtends($target, $origin) {
|
133 |
+
if ($this->isSelfExtend($target, $origin)) {
|
134 |
+
return;
|
135 |
+
}
|
136 |
+
|
137 |
+
$i = count($this->extends);
|
138 |
+
$this->extends[] = array($target, $origin);
|
139 |
+
|
140 |
+
foreach ($target as $part) {
|
141 |
+
if (isset($this->extendsMap[$part])) {
|
142 |
+
$this->extendsMap[$part][] = $i;
|
143 |
+
} else {
|
144 |
+
$this->extendsMap[$part] = array($i);
|
145 |
+
}
|
146 |
+
}
|
147 |
+
}
|
148 |
+
|
149 |
+
protected function makeOutputBlock($type, $selectors = null) {
|
150 |
+
$out = new stdClass;
|
151 |
+
$out->type = $type;
|
152 |
+
$out->lines = array();
|
153 |
+
$out->children = array();
|
154 |
+
$out->parent = $this->scope;
|
155 |
+
$out->selectors = $selectors;
|
156 |
+
$out->depth = $this->env->depth;
|
157 |
+
|
158 |
+
return $out;
|
159 |
+
}
|
160 |
+
|
161 |
+
protected function matchExtendsSingle($single, &$outOrigin) {
|
162 |
+
$counts = array();
|
163 |
+
foreach ($single as $part) {
|
164 |
+
if (!is_string($part)) return false; // hmm
|
165 |
+
|
166 |
+
if (isset($this->extendsMap[$part])) {
|
167 |
+
foreach ($this->extendsMap[$part] as $idx) {
|
168 |
+
$counts[$idx] =
|
169 |
+
isset($counts[$idx]) ? $counts[$idx] + 1 : 1;
|
170 |
+
}
|
171 |
+
}
|
172 |
+
}
|
173 |
+
|
174 |
+
$outOrigin = array();
|
175 |
+
$found = false;
|
176 |
+
|
177 |
+
foreach ($counts as $idx => $count) {
|
178 |
+
list($target, $origin) = $this->extends[$idx];
|
179 |
+
|
180 |
+
// check count
|
181 |
+
if ($count != count($target)) continue;
|
182 |
+
|
183 |
+
// check if target is subset of single
|
184 |
+
if (array_diff(array_intersect($single, $target), $target)) continue;
|
185 |
+
|
186 |
+
$rem = array_diff($single, $target);
|
187 |
+
|
188 |
+
foreach ($origin as $j => $new) {
|
189 |
+
$origin[$j][count($origin[$j]) - 1] = $this->combineSelectorSingle(end($new), $rem);
|
190 |
+
}
|
191 |
+
|
192 |
+
$outOrigin = array_merge($outOrigin, $origin);
|
193 |
+
|
194 |
+
$found = true;
|
195 |
+
}
|
196 |
+
|
197 |
+
return $found;
|
198 |
+
}
|
199 |
+
|
200 |
+
protected function combineSelectorSingle($base, $other) {
|
201 |
+
$tag = null;
|
202 |
+
$out = array();
|
203 |
+
|
204 |
+
foreach (array($base, $other) as $single) {
|
205 |
+
foreach ($single as $part) {
|
206 |
+
if (preg_match('/^[^\[.#:]/', $part)) {
|
207 |
+
$tag = $part;
|
208 |
+
} else {
|
209 |
+
$out[] = $part;
|
210 |
+
}
|
211 |
+
}
|
212 |
+
}
|
213 |
+
|
214 |
+
if ($tag) {
|
215 |
+
array_unshift($out, $tag);
|
216 |
+
}
|
217 |
+
|
218 |
+
return $out;
|
219 |
+
}
|
220 |
+
|
221 |
+
protected function matchExtends($selector, &$out, $from = 0, $initial=true) {
|
222 |
+
foreach ($selector as $i => $part) {
|
223 |
+
if ($i < $from) continue;
|
224 |
+
|
225 |
+
if ($this->matchExtendsSingle($part, $origin)) {
|
226 |
+
$before = array_slice($selector, 0, $i);
|
227 |
+
$after = array_slice($selector, $i + 1);
|
228 |
+
|
229 |
+
foreach ($origin as $new) {
|
230 |
+
$k = 0;
|
231 |
+
|
232 |
+
// remove shared parts
|
233 |
+
if ($initial) {
|
234 |
+
foreach ($before as $k => $val) {
|
235 |
+
if (!isset($new[$k]) || $val != $new[$k]) {
|
236 |
+
break;
|
237 |
+
}
|
238 |
+
}
|
239 |
+
}
|
240 |
+
|
241 |
+
$result = array_merge(
|
242 |
+
$before,
|
243 |
+
$k > 0 ? array_slice($new, $k) : $new,
|
244 |
+
$after);
|
245 |
+
|
246 |
+
|
247 |
+
if ($result == $selector) continue;
|
248 |
+
$out[] = $result;
|
249 |
+
|
250 |
+
// recursively check for more matches
|
251 |
+
$this->matchExtends($result, $out, $i, false);
|
252 |
+
|
253 |
+
// selector sequence merging
|
254 |
+
if (!empty($before) && count($new) > 1) {
|
255 |
+
$result2 = array_merge(
|
256 |
+
array_slice($new, 0, -1),
|
257 |
+
$k > 0 ? array_slice($before, $k) : $before,
|
258 |
+
array_slice($new, -1),
|
259 |
+
$after);
|
260 |
+
|
261 |
+
$out[] = $result2;
|
262 |
+
}
|
263 |
+
}
|
264 |
+
}
|
265 |
+
}
|
266 |
+
}
|
267 |
+
|
268 |
+
protected function flattenSelectors($block, $parentKey = null) {
|
269 |
+
if ($block->selectors) {
|
270 |
+
$selectors = array();
|
271 |
+
foreach ($block->selectors as $s) {
|
272 |
+
$selectors[] = $s;
|
273 |
+
if (!is_array($s)) continue;
|
274 |
+
// check extends
|
275 |
+
if (!empty($this->extendsMap)) {
|
276 |
+
$this->matchExtends($s, $selectors);
|
277 |
+
}
|
278 |
+
}
|
279 |
+
|
280 |
+
$block->selectors = array();
|
281 |
+
$placeholderSelector = false;
|
282 |
+
foreach ($selectors as $selector) {
|
283 |
+
if ($this->hasSelectorPlaceholder($selector)) {
|
284 |
+
$placeholderSelector = true;
|
285 |
+
continue;
|
286 |
+
}
|
287 |
+
$block->selectors[] = $this->compileSelector($selector);
|
288 |
+
}
|
289 |
+
|
290 |
+
if ($placeholderSelector && 0 == count($block->selectors) && null !== $parentKey) {
|
291 |
+
unset($block->parent->children[$parentKey]);
|
292 |
+
return;
|
293 |
+
}
|
294 |
+
}
|
295 |
+
|
296 |
+
foreach ($block->children as $key => $child) {
|
297 |
+
$this->flattenSelectors($child, $key);
|
298 |
+
}
|
299 |
+
}
|
300 |
+
|
301 |
+
protected function compileRoot($rootBlock) {
|
302 |
+
$this->pushEnv($rootBlock);
|
303 |
+
$this->scope = $this->makeOutputBlock("root");
|
304 |
+
|
305 |
+
$this->compileChildren($rootBlock->children, $this->scope);
|
306 |
+
$this->flattenSelectors($this->scope);
|
307 |
+
|
308 |
+
$this->popEnv();
|
309 |
+
}
|
310 |
+
|
311 |
+
protected function compileMedia($media) {
|
312 |
+
$this->pushEnv($media);
|
313 |
+
$parentScope = $this->mediaParent($this->scope);
|
314 |
+
|
315 |
+
$this->scope = $this->makeOutputBlock("media", array(
|
316 |
+
$this->compileMediaQuery($this->multiplyMedia($this->env)))
|
317 |
+
);
|
318 |
+
|
319 |
+
$parentScope->children[] = $this->scope;
|
320 |
+
|
321 |
+
// top level properties in a media cause it to be wrapped
|
322 |
+
$needsWrap = false;
|
323 |
+
foreach ($media->children as $child) {
|
324 |
+
$type = $child[0];
|
325 |
+
if ($type !== 'block' && $type !== 'media' && $type !== 'directive') {
|
326 |
+
$needsWrap = true;
|
327 |
+
break;
|
328 |
+
}
|
329 |
+
}
|
330 |
+
|
331 |
+
if ($needsWrap) {
|
332 |
+
$wrapped = (object)array(
|
333 |
+
"selectors" => array(),
|
334 |
+
"children" => $media->children
|
335 |
+
);
|
336 |
+
$media->children = array(array("block", $wrapped));
|
337 |
+
}
|
338 |
+
|
339 |
+
$this->compileChildren($media->children, $this->scope);
|
340 |
+
|
341 |
+
$this->scope = $this->scope->parent;
|
342 |
+
$this->popEnv();
|
343 |
+
}
|
344 |
+
|
345 |
+
protected function mediaParent($scope) {
|
346 |
+
while (!empty($scope->parent)) {
|
347 |
+
if (!empty($scope->type) && $scope->type != "media") {
|
348 |
+
break;
|
349 |
+
}
|
350 |
+
$scope = $scope->parent;
|
351 |
+
}
|
352 |
+
|
353 |
+
return $scope;
|
354 |
+
}
|
355 |
+
|
356 |
+
// TODO refactor compileNestedBlock and compileMedia into same thing
|
357 |
+
protected function compileNestedBlock($block, $selectors) {
|
358 |
+
$this->pushEnv($block);
|
359 |
+
|
360 |
+
$this->scope = $this->makeOutputBlock($block->type, $selectors);
|
361 |
+
$this->scope->parent->children[] = $this->scope;
|
362 |
+
$this->compileChildren($block->children, $this->scope);
|
363 |
+
|
364 |
+
$this->scope = $this->scope->parent;
|
365 |
+
$this->popEnv();
|
366 |
+
}
|
367 |
+
|
368 |
+
/**
|
369 |
+
* Recursively compiles a block.
|
370 |
+
*
|
371 |
+
* A block is analogous to a CSS block in most cases. A single SCSS document
|
372 |
+
* is encapsulated in a block when parsed, but it does not have parent tags
|
373 |
+
* so all of its children appear on the root level when compiled.
|
374 |
+
*
|
375 |
+
* Blocks are made up of selectors and children.
|
376 |
+
*
|
377 |
+
* The children of a block are just all the blocks that are defined within.
|
378 |
+
*
|
379 |
+
* Compiling the block involves pushing a fresh environment on the stack,
|
380 |
+
* and iterating through the props, compiling each one.
|
381 |
+
*
|
382 |
+
* @see scss::compileChild()
|
383 |
+
*
|
384 |
+
* @param \StdClass $block
|
385 |
+
*/
|
386 |
+
protected function compileBlock($block) {
|
387 |
+
$env = $this->pushEnv($block);
|
388 |
+
|
389 |
+
$env->selectors =
|
390 |
+
array_map(array($this, "evalSelector"), $block->selectors);
|
391 |
+
|
392 |
+
$out = $this->makeOutputBlock(null, $this->multiplySelectors($env));
|
393 |
+
$this->scope->children[] = $out;
|
394 |
+
$this->compileChildren($block->children, $out);
|
395 |
+
|
396 |
+
$this->popEnv();
|
397 |
+
}
|
398 |
+
|
399 |
+
// joins together .classes and #ids
|
400 |
+
protected function flattenSelectorSingle($single) {
|
401 |
+
$joined = array();
|
402 |
+
foreach ($single as $part) {
|
403 |
+
if (empty($joined) ||
|
404 |
+
!is_string($part) ||
|
405 |
+
preg_match('/[\[.:#%]/', $part))
|
406 |
+
{
|
407 |
+
$joined[] = $part;
|
408 |
+
continue;
|
409 |
+
}
|
410 |
+
|
411 |
+
if (is_array(end($joined))) {
|
412 |
+
$joined[] = $part;
|
413 |
+
} else {
|
414 |
+
$joined[count($joined) - 1] .= $part;
|
415 |
+
}
|
416 |
+
}
|
417 |
+
|
418 |
+
return $joined;
|
419 |
+
}
|
420 |
+
|
421 |
+
// replaces all the interpolates
|
422 |
+
protected function evalSelector($selector) {
|
423 |
+
return array_map(array($this, "evalSelectorPart"), $selector);
|
424 |
+
}
|
425 |
+
|
426 |
+
protected function evalSelectorPart($piece) {
|
427 |
+
foreach ($piece as &$p) {
|
428 |
+
if (!is_array($p)) continue;
|
429 |
+
|
430 |
+
switch ($p[0]) {
|
431 |
+
case "interpolate":
|
432 |
+
$p = $this->compileValue($p);
|
433 |
+
break;
|
434 |
+
case "string":
|
435 |
+
$p = $this->compileValue($p);
|
436 |
+
break;
|
437 |
+
}
|
438 |
+
}
|
439 |
+
|
440 |
+
return $this->flattenSelectorSingle($piece);
|
441 |
+
}
|
442 |
+
|
443 |
+
// compiles to string
|
444 |
+
// self(&) should have been replaced by now
|
445 |
+
protected function compileSelector($selector) {
|
446 |
+
if (!is_array($selector)) return $selector; // media and the like
|
447 |
+
|
448 |
+
return implode(" ", array_map(
|
449 |
+
array($this, "compileSelectorPart"), $selector));
|
450 |
+
}
|
451 |
+
|
452 |
+
protected function compileSelectorPart($piece) {
|
453 |
+
foreach ($piece as &$p) {
|
454 |
+
if (!is_array($p)) continue;
|
455 |
+
|
456 |
+
switch ($p[0]) {
|
457 |
+
case "self":
|
458 |
+
$p = "&";
|
459 |
+
break;
|
460 |
+
default:
|
461 |
+
$p = $this->compileValue($p);
|
462 |
+
break;
|
463 |
+
}
|
464 |
+
}
|
465 |
+
|
466 |
+
return implode($piece);
|
467 |
+
}
|
468 |
+
|
469 |
+
protected function hasSelectorPlaceholder($selector)
|
470 |
+
{
|
471 |
+
if (!is_array($selector)) return false;
|
472 |
+
|
473 |
+
foreach ($selector as $parts) {
|
474 |
+
foreach ($parts as $part) {
|
475 |
+
if ('%' == $part[0]) {
|
476 |
+
return true;
|
477 |
+
}
|
478 |
+
}
|
479 |
+
}
|
480 |
+
|
481 |
+
return false;
|
482 |
+
}
|
483 |
+
|
484 |
+
protected function compileChildren($stms, $out) {
|
485 |
+
foreach ($stms as $stm) {
|
486 |
+
$ret = $this->compileChild($stm, $out);
|
487 |
+
if (!is_null($ret)) return $ret;
|
488 |
+
}
|
489 |
+
}
|
490 |
+
|
491 |
+
protected function compileMediaQuery($queryList) {
|
492 |
+
$out = "@media";
|
493 |
+
$first = true;
|
494 |
+
foreach ($queryList as $query){
|
495 |
+
$parts = array();
|
496 |
+
foreach ($query as $q) {
|
497 |
+
switch ($q[0]) {
|
498 |
+
case "mediaType":
|
499 |
+
$parts[] = implode(" ", array_map(array($this, "compileValue"), array_slice($q, 1)));
|
500 |
+
break;
|
501 |
+
case "mediaExp":
|
502 |
+
if (isset($q[2])) {
|
503 |
+
$parts[] = "(". $this->compileValue($q[1]) . $this->formatter->assignSeparator . $this->compileValue($q[2]) . ")";
|
504 |
+
} else {
|
505 |
+
$parts[] = "(" . $this->compileValue($q[1]) . ")";
|
506 |
+
}
|
507 |
+
break;
|
508 |
+
}
|
509 |
+
}
|
510 |
+
if (!empty($parts)) {
|
511 |
+
if ($first) {
|
512 |
+
$first = false;
|
513 |
+
$out .= " ";
|
514 |
+
} else {
|
515 |
+
$out .= $this->formatter->tagSeparator;
|
516 |
+
}
|
517 |
+
$out .= implode(" and ", $parts);
|
518 |
+
}
|
519 |
+
}
|
520 |
+
return $out;
|
521 |
+
}
|
522 |
+
|
523 |
+
// returns true if the value was something that could be imported
|
524 |
+
protected function compileImport($rawPath, $out) {
|
525 |
+
if ($rawPath[0] == "string") {
|
526 |
+
$path = $this->compileStringContent($rawPath);
|
527 |
+
if ($path = $this->findImport($path)) {
|
528 |
+
$this->importFile($path, $out);
|
529 |
+
return true;
|
530 |
+
}
|
531 |
+
return false;
|
532 |
+
}
|
533 |
+
if ($rawPath[0] == "list") {
|
534 |
+
// handle a list of strings
|
535 |
+
if (count($rawPath[2]) == 0) return false;
|
536 |
+
foreach ($rawPath[2] as $path) {
|
537 |
+
if ($path[0] != "string") return false;
|
538 |
+
}
|
539 |
+
|
540 |
+
foreach ($rawPath[2] as $path) {
|
541 |
+
$this->compileImport($path, $out);
|
542 |
+
}
|
543 |
+
|
544 |
+
return true;
|
545 |
+
}
|
546 |
+
|
547 |
+
return false;
|
548 |
+
}
|
549 |
+
|
550 |
+
// return a value to halt execution
|
551 |
+
protected function compileChild($child, $out) {
|
552 |
+
$this->sourcePos = isset($child[-1]) ? $child[-1] : -1;
|
553 |
+
$this->sourceParser = isset($child[-2]) ? $child[-2] : $this->parser;
|
554 |
+
|
555 |
+
switch ($child[0]) {
|
556 |
+
case "import":
|
557 |
+
list(,$rawPath) = $child;
|
558 |
+
$rawPath = $this->reduce($rawPath);
|
559 |
+
if (!$this->compileImport($rawPath, $out)) {
|
560 |
+
$out->lines[] = "@import " . $this->compileValue($rawPath) . ";";
|
561 |
+
}
|
562 |
+
break;
|
563 |
+
case "directive":
|
564 |
+
list(, $directive) = $child;
|
565 |
+
$s = "@" . $directive->name;
|
566 |
+
if (!empty($directive->value)) {
|
567 |
+
$s .= " " . $this->compileValue($directive->value);
|
568 |
+
}
|
569 |
+
$this->compileNestedBlock($directive, array($s));
|
570 |
+
break;
|
571 |
+
case "media":
|
572 |
+
$this->compileMedia($child[1]);
|
573 |
+
break;
|
574 |
+
case "block":
|
575 |
+
$this->compileBlock($child[1]);
|
576 |
+
break;
|
577 |
+
case "charset":
|
578 |
+
$out->lines[] = "@charset ".$this->compileValue($child[1]).";";
|
579 |
+
break;
|
580 |
+
case "assign":
|
581 |
+
list(,$name, $value) = $child;
|
582 |
+
if ($name[0] == "var") {
|
583 |
+
$isDefault = !empty($child[3]);
|
584 |
+
|
585 |
+
if ($isDefault) {
|
586 |
+
$existingValue = $this->get($name[1], true);
|
587 |
+
$shouldSet = $existingValue === true || $existingValue == self::$null;
|
588 |
+
}
|
589 |
+
|
590 |
+
if (!$isDefault || $shouldSet) {
|
591 |
+
$this->set($name[1], $this->reduce($value));
|
592 |
+
}
|
593 |
+
break;
|
594 |
+
}
|
595 |
+
|
596 |
+
// if the value reduces to null from something else then
|
597 |
+
// the property should be discarded
|
598 |
+
if ($value[0] != "null") {
|
599 |
+
$value = $this->reduce($value);
|
600 |
+
if ($value[0] == "null") {
|
601 |
+
break;
|
602 |
+
}
|
603 |
+
}
|
604 |
+
|
605 |
+
$compiledValue = $this->compileValue($value);
|
606 |
+
$out->lines[] = $this->formatter->property(
|
607 |
+
$this->compileValue($name),
|
608 |
+
$compiledValue);
|
609 |
+
break;
|
610 |
+
case "comment":
|
611 |
+
$out->lines[] = $child[1];
|
612 |
+
break;
|
613 |
+
case "mixin":
|
614 |
+
case "function":
|
615 |
+
list(,$block) = $child;
|
616 |
+
$this->set(self::$namespaces[$block->type] . $block->name, $block);
|
617 |
+
break;
|
618 |
+
case "extend":
|
619 |
+
list(, $selectors) = $child;
|
620 |
+
foreach ($selectors as $sel) {
|
621 |
+
// only use the first one
|
622 |
+
$sel = current($this->evalSelector($sel));
|
623 |
+
$this->pushExtends($sel, $out->selectors);
|
624 |
+
}
|
625 |
+
break;
|
626 |
+
case "if":
|
627 |
+
list(, $if) = $child;
|
628 |
+
if ($this->isTruthy($this->reduce($if->cond, true))) {
|
629 |
+
return $this->compileChildren($if->children, $out);
|
630 |
+
} else {
|
631 |
+
foreach ($if->cases as $case) {
|
632 |
+
if ($case->type == "else" ||
|
633 |
+
$case->type == "elseif" && $this->isTruthy($this->reduce($case->cond)))
|
634 |
+
{
|
635 |
+
return $this->compileChildren($case->children, $out);
|
636 |
+
}
|
637 |
+
}
|
638 |
+
}
|
639 |
+
break;
|
640 |
+
case "return":
|
641 |
+
return $this->reduce($child[1], true);
|
642 |
+
case "each":
|
643 |
+
list(,$each) = $child;
|
644 |
+
$list = $this->coerceList($this->reduce($each->list));
|
645 |
+
foreach ($list[2] as $item) {
|
646 |
+
$this->pushEnv();
|
647 |
+
$this->set($each->var, $item);
|
648 |
+
// TODO: allow return from here
|
649 |
+
$this->compileChildren($each->children, $out);
|
650 |
+
$this->popEnv();
|
651 |
+
}
|
652 |
+
break;
|
653 |
+
case "while":
|
654 |
+
list(,$while) = $child;
|
655 |
+
while ($this->isTruthy($this->reduce($while->cond, true))) {
|
656 |
+
$ret = $this->compileChildren($while->children, $out);
|
657 |
+
if ($ret) return $ret;
|
658 |
+
}
|
659 |
+
break;
|
660 |
+
case "for":
|
661 |
+
list(,$for) = $child;
|
662 |
+
$start = $this->reduce($for->start, true);
|
663 |
+
$start = $start[1];
|
664 |
+
$end = $this->reduce($for->end, true);
|
665 |
+
$end = $end[1];
|
666 |
+
$d = $start < $end ? 1 : -1;
|
667 |
+
|
668 |
+
while (true) {
|
669 |
+
if ((!$for->until && $start - $d == $end) ||
|
670 |
+
($for->until && $start == $end))
|
671 |
+
{
|
672 |
+
break;
|
673 |
+
}
|
674 |
+
|
675 |
+
$this->set($for->var, array("number", $start, ""));
|
676 |
+
$start += $d;
|
677 |
+
|
678 |
+
$ret = $this->compileChildren($for->children, $out);
|
679 |
+
if ($ret) return $ret;
|
680 |
+
}
|
681 |
+
|
682 |
+
break;
|
683 |
+
case "nestedprop":
|
684 |
+
list(,$prop) = $child;
|
685 |
+
$prefixed = array();
|
686 |
+
$prefix = $this->compileValue($prop->prefix) . "-";
|
687 |
+
foreach ($prop->children as $child) {
|
688 |
+
if ($child[0] == "assign") {
|
689 |
+
array_unshift($child[1][2], $prefix);
|
690 |
+
}
|
691 |
+
if ($child[0] == "nestedprop") {
|
692 |
+
array_unshift($child[1]->prefix[2], $prefix);
|
693 |
+
}
|
694 |
+
$prefixed[] = $child;
|
695 |
+
}
|
696 |
+
$this->compileChildren($prefixed, $out);
|
697 |
+
break;
|
698 |
+
case "include": // including a mixin
|
699 |
+
list(,$name, $argValues, $content) = $child;
|
700 |
+
$mixin = $this->get(self::$namespaces["mixin"] . $name, false);
|
701 |
+
if (!$mixin) {
|
702 |
+
$this->throwError("Undefined mixin $name");
|
703 |
+
}
|
704 |
+
|
705 |
+
$callingScope = $this->env;
|
706 |
+
|
707 |
+
// push scope, apply args
|
708 |
+
$this->pushEnv();
|
709 |
+
if ($this->env->depth > 0) {
|
710 |
+
$this->env->depth--;
|
711 |
+
}
|
712 |
+
|
713 |
+
if (!is_null($content)) {
|
714 |
+
$content->scope = $callingScope;
|
715 |
+
$this->setRaw(self::$namespaces["special"] . "content", $content);
|
716 |
+
}
|
717 |
+
|
718 |
+
if (!is_null($mixin->args)) {
|
719 |
+
$this->applyArguments($mixin->args, $argValues);
|
720 |
+
}
|
721 |
+
|
722 |
+
foreach ($mixin->children as $child) {
|
723 |
+
$this->compileChild($child, $out);
|
724 |
+
}
|
725 |
+
|
726 |
+
$this->popEnv();
|
727 |
+
|
728 |
+
break;
|
729 |
+
case "mixin_content":
|
730 |
+
$content = $this->get(self::$namespaces["special"] . "content");
|
731 |
+
if (is_null($content)) {
|
732 |
+
$this->throwError("Expected @content inside of mixin");
|
733 |
+
}
|
734 |
+
|
735 |
+
$strongTypes = array('include', 'block', 'for', 'while');
|
736 |
+
foreach ($content->children as $child) {
|
737 |
+
$this->storeEnv = (in_array($child[0], $strongTypes))
|
738 |
+
? null
|
739 |
+
: $content->scope;
|
740 |
+
|
741 |
+
$this->compileChild($child, $out);
|
742 |
+
}
|
743 |
+
|
744 |
+
unset($this->storeEnv);
|
745 |
+
break;
|
746 |
+
case "debug":
|
747 |
+
list(,$value, $pos) = $child;
|
748 |
+
$line = $this->parser->getLineNo($pos);
|
749 |
+
$value = $this->compileValue($this->reduce($value, true));
|
750 |
+
fwrite(STDERR, "Line $line DEBUG: $value\n");
|
751 |
+
break;
|
752 |
+
default:
|
753 |
+
$this->throwError("unknown child type: $child[0]");
|
754 |
+
}
|
755 |
+
}
|
756 |
+
|
757 |
+
protected function expToString($exp) {
|
758 |
+
list(, $op, $left, $right, $inParens, $whiteLeft, $whiteRight) = $exp;
|
759 |
+
$content = array($this->reduce($left));
|
760 |
+
if ($whiteLeft) $content[] = " ";
|
761 |
+
$content[] = $op;
|
762 |
+
if ($whiteRight) $content[] = " ";
|
763 |
+
$content[] = $this->reduce($right);
|
764 |
+
return array("string", "", $content);
|
765 |
+
}
|
766 |
+
|
767 |
+
protected function isTruthy($value) {
|
768 |
+
return $value != self::$false && $value != self::$null;
|
769 |
+
}
|
770 |
+
|
771 |
+
// should $value cause its operand to eval
|
772 |
+
protected function shouldEval($value) {
|
773 |
+
switch ($value[0]) {
|
774 |
+
case "exp":
|
775 |
+
if ($value[1] == "/") {
|
776 |
+
return $this->shouldEval($value[2], $value[3]);
|
777 |
+
}
|
778 |
+
case "var":
|
779 |
+
case "fncall":
|
780 |
+
return true;
|
781 |
+
}
|
782 |
+
return false;
|
783 |
+
}
|
784 |
+
|
785 |
+
protected function reduce($value, $inExp = false) {
|
786 |
+
list($type) = $value;
|
787 |
+
switch ($type) {
|
788 |
+
case "exp":
|
789 |
+
list(, $op, $left, $right, $inParens) = $value;
|
790 |
+
$opName = isset(self::$operatorNames[$op]) ? self::$operatorNames[$op] : $op;
|
791 |
+
|
792 |
+
$inExp = $inExp || $this->shouldEval($left) || $this->shouldEval($right);
|
793 |
+
|
794 |
+
$left = $this->reduce($left, true);
|
795 |
+
$right = $this->reduce($right, true);
|
796 |
+
|
797 |
+
// only do division in special cases
|
798 |
+
if ($opName == "div" && !$inParens && !$inExp) {
|
799 |
+
if ($left[0] != "color" && $right[0] != "color") {
|
800 |
+
return $this->expToString($value);
|
801 |
+
}
|
802 |
+
}
|
803 |
+
|
804 |
+
$left = $this->coerceForExpression($left);
|
805 |
+
$right = $this->coerceForExpression($right);
|
806 |
+
|
807 |
+
$ltype = $left[0];
|
808 |
+
$rtype = $right[0];
|
809 |
+
|
810 |
+
// this tries:
|
811 |
+
// 1. op_[op name]_[left type]_[right type]
|
812 |
+
// 2. op_[left type]_[right type] (passing the op as first arg
|
813 |
+
// 3. op_[op name]
|
814 |
+
$fn = "op_${opName}_${ltype}_${rtype}";
|
815 |
+
if (is_callable(array($this, $fn)) ||
|
816 |
+
(($fn = "op_${ltype}_${rtype}") &&
|
817 |
+
is_callable(array($this, $fn)) &&
|
818 |
+
$passOp = true) ||
|
819 |
+
(($fn = "op_${opName}") &&
|
820 |
+
is_callable(array($this, $fn)) &&
|
821 |
+
$genOp = true))
|
822 |
+
{
|
823 |
+
$unitChange = false;
|
824 |
+
if (!isset($genOp) &&
|
825 |
+
$left[0] == "number" && $right[0] == "number")
|
826 |
+
{
|
827 |
+
if ($opName == "mod" && $right[2] != "") {
|
828 |
+
$this->throwError("Cannot modulo by a number with units: $right[1]$right[2].");
|
829 |
+
}
|
830 |
+
|
831 |
+
$unitChange = true;
|
832 |
+
$emptyUnit = $left[2] == "" || $right[2] == "";
|
833 |
+
$targetUnit = "" != $left[2] ? $left[2] : $right[2];
|
834 |
+
|
835 |
+
if ($opName != "mul") {
|
836 |
+
$left[2] = "" != $left[2] ? $left[2] : $targetUnit;
|
837 |
+
$right[2] = "" != $right[2] ? $right[2] : $targetUnit;
|
838 |
+
}
|
839 |
+
|
840 |
+
if ($opName != "mod") {
|
841 |
+
$left = $this->normalizeNumber($left);
|
842 |
+
$right = $this->normalizeNumber($right);
|
843 |
+
}
|
844 |
+
|
845 |
+
if ($opName == "div" && !$emptyUnit && $left[2] == $right[2]) {
|
846 |
+
$targetUnit = "";
|
847 |
+
}
|
848 |
+
|
849 |
+
if ($opName == "mul") {
|
850 |
+
$left[2] = "" != $left[2] ? $left[2] : $right[2];
|
851 |
+
$right[2] = "" != $right[2] ? $right[2] : $left[2];
|
852 |
+
} elseif ($opName == "div" && $left[2] == $right[2]) {
|
853 |
+
$left[2] = "";
|
854 |
+
$right[2] = "";
|
855 |
+
}
|
856 |
+
}
|
857 |
+
|
858 |
+
$shouldEval = $inParens || $inExp;
|
859 |
+
if (isset($passOp)) {
|
860 |
+
$out = $this->$fn($op, $left, $right, $shouldEval);
|
861 |
+
} else {
|
862 |
+
$out = $this->$fn($left, $right, $shouldEval);
|
863 |
+
}
|
864 |
+
|
865 |
+
if (!is_null($out)) {
|
866 |
+
if ($unitChange && $out[0] == "number") {
|
867 |
+
$out = $this->coerceUnit($out, $targetUnit);
|
868 |
+
}
|
869 |
+
return $out;
|
870 |
+
}
|
871 |
+
}
|
872 |
+
|
873 |
+
return $this->expToString($value);
|
874 |
+
case "unary":
|
875 |
+
list(, $op, $exp, $inParens) = $value;
|
876 |
+
$inExp = $inExp || $this->shouldEval($exp);
|
877 |
+
|
878 |
+
$exp = $this->reduce($exp);
|
879 |
+
if ($exp[0] == "number") {
|
880 |
+
switch ($op) {
|
881 |
+
case "+":
|
882 |
+
return $exp;
|
883 |
+
case "-":
|
884 |
+
$exp[1] *= -1;
|
885 |
+
return $exp;
|
886 |
+
}
|
887 |
+
}
|
888 |
+
|
889 |
+
if ($op == "not") {
|
890 |
+
if ($inExp || $inParens) {
|
891 |
+
if ($exp == self::$false) {
|
892 |
+
return self::$true;
|
893 |
+
} else {
|
894 |
+
return self::$false;
|
895 |
+
}
|
896 |
+
} else {
|
897 |
+
$op = $op . " ";
|
898 |
+
}
|
899 |
+
}
|
900 |
+
|
901 |
+
return array("string", "", array($op, $exp));
|
902 |
+
case "var":
|
903 |
+
list(, $name) = $value;
|
904 |
+
return $this->reduce($this->get($name));
|
905 |
+
case "list":
|
906 |
+
foreach ($value[2] as &$item) {
|
907 |
+
$item = $this->reduce($item);
|
908 |
+
}
|
909 |
+
return $value;
|
910 |
+
case "string":
|
911 |
+
foreach ($value[2] as &$item) {
|
912 |
+
if (is_array($item)) {
|
913 |
+
$item = $this->reduce($item);
|
914 |
+
}
|
915 |
+
}
|
916 |
+
return $value;
|
917 |
+
case "interpolate":
|
918 |
+
$value[1] = $this->reduce($value[1]);
|
919 |
+
return $value;
|
920 |
+
case "fncall":
|
921 |
+
list(,$name, $argValues) = $value;
|
922 |
+
|
923 |
+
// user defined function?
|
924 |
+
$func = $this->get(self::$namespaces["function"] . $name, false);
|
925 |
+
if ($func) {
|
926 |
+
$this->pushEnv();
|
927 |
+
|
928 |
+
// set the args
|
929 |
+
if (isset($func->args)) {
|
930 |
+
$this->applyArguments($func->args, $argValues);
|
931 |
+
}
|
932 |
+
|
933 |
+
// throw away lines and children
|
934 |
+
$tmp = (object)array(
|
935 |
+
"lines" => array(),
|
936 |
+
"children" => array()
|
937 |
+
);
|
938 |
+
$ret = $this->compileChildren($func->children, $tmp);
|
939 |
+
$this->popEnv();
|
940 |
+
|
941 |
+
return is_null($ret) ? self::$defaultValue : $ret;
|
942 |
+
}
|
943 |
+
|
944 |
+
// built in function
|
945 |
+
if ($this->callBuiltin($name, $argValues, $returnValue)) {
|
946 |
+
return $returnValue;
|
947 |
+
}
|
948 |
+
|
949 |
+
// need to flatten the arguments into a list
|
950 |
+
$listArgs = array();
|
951 |
+
foreach ((array)$argValues as $arg) {
|
952 |
+
if (empty($arg[0])) {
|
953 |
+
$listArgs[] = $this->reduce($arg[1]);
|
954 |
+
}
|
955 |
+
}
|
956 |
+
return array("function", $name, array("list", ",", $listArgs));
|
957 |
+
default:
|
958 |
+
return $value;
|
959 |
+
}
|
960 |
+
}
|
961 |
+
|
962 |
+
public function normalizeValue($value) {
|
963 |
+
$value = $this->coerceForExpression($this->reduce($value));
|
964 |
+
list($type) = $value;
|
965 |
+
|
966 |
+
switch ($type) {
|
967 |
+
case "list":
|
968 |
+
$value = $this->extractInterpolation($value);
|
969 |
+
if ($value[0] != "list") {
|
970 |
+
return array("keyword", $this->compileValue($value));
|
971 |
+
}
|
972 |
+
foreach ($value[2] as $key => $item) {
|
973 |
+
$value[2][$key] = $this->normalizeValue($item);
|
974 |
+
}
|
975 |
+
return $value;
|
976 |
+
case "number":
|
977 |
+
return $this->normalizeNumber($value);
|
978 |
+
default:
|
979 |
+
return $value;
|
980 |
+
}
|
981 |
+
}
|
982 |
+
|
983 |
+
// just does physical lengths for now
|
984 |
+
protected function normalizeNumber($number) {
|
985 |
+
list(, $value, $unit) = $number;
|
986 |
+
if (isset(self::$unitTable["in"][$unit])) {
|
987 |
+
$conv = self::$unitTable["in"][$unit];
|
988 |
+
return array("number", $value / $conv, "in");
|
989 |
+
}
|
990 |
+
return $number;
|
991 |
+
}
|
992 |
+
|
993 |
+
// $number should be normalized
|
994 |
+
protected function coerceUnit($number, $unit) {
|
995 |
+
list(, $value, $baseUnit) = $number;
|
996 |
+
if (isset(self::$unitTable[$baseUnit][$unit])) {
|
997 |
+
$value = $value * self::$unitTable[$baseUnit][$unit];
|
998 |
+
}
|
999 |
+
|
1000 |
+
return array("number", $value, $unit);
|
1001 |
+
}
|
1002 |
+
|
1003 |
+
protected function op_add_number_number($left, $right) {
|
1004 |
+
return array("number", $left[1] + $right[1], $left[2]);
|
1005 |
+
}
|
1006 |
+
|
1007 |
+
protected function op_mul_number_number($left, $right) {
|
1008 |
+
return array("number", $left[1] * $right[1], $left[2]);
|
1009 |
+
}
|
1010 |
+
|
1011 |
+
protected function op_sub_number_number($left, $right) {
|
1012 |
+
return array("number", $left[1] - $right[1], $left[2]);
|
1013 |
+
}
|
1014 |
+
|
1015 |
+
protected function op_div_number_number($left, $right) {
|
1016 |
+
return array("number", $left[1] / $right[1], $left[2]);
|
1017 |
+
}
|
1018 |
+
|
1019 |
+
protected function op_mod_number_number($left, $right) {
|
1020 |
+
return array("number", $left[1] % $right[1], $left[2]);
|
1021 |
+
}
|
1022 |
+
|
1023 |
+
// adding strings
|
1024 |
+
protected function op_add($left, $right) {
|
1025 |
+
if ($strLeft = $this->coerceString($left)) {
|
1026 |
+
if ($right[0] == "string") {
|
1027 |
+
$right[1] = "";
|
1028 |
+
}
|
1029 |
+
$strLeft[2][] = $right;
|
1030 |
+
return $strLeft;
|
1031 |
+
}
|
1032 |
+
|
1033 |
+
if ($strRight = $this->coerceString($right)) {
|
1034 |
+
if ($left[0] == "string") {
|
1035 |
+
$left[1] = "";
|
1036 |
+
}
|
1037 |
+
array_unshift($strRight[2], $left);
|
1038 |
+
return $strRight;
|
1039 |
+
}
|
1040 |
+
}
|
1041 |
+
|
1042 |
+
protected function op_and($left, $right, $shouldEval) {
|
1043 |
+
if (!$shouldEval) return;
|
1044 |
+
if ($left != self::$false) return $right;
|
1045 |
+
return $left;
|
1046 |
+
}
|
1047 |
+
|
1048 |
+
protected function op_or($left, $right, $shouldEval) {
|
1049 |
+
if (!$shouldEval) return;
|
1050 |
+
if ($left != self::$false) return $left;
|
1051 |
+
return $right;
|
1052 |
+
}
|
1053 |
+
|
1054 |
+
protected function op_color_color($op, $left, $right) {
|
1055 |
+
$out = array('color');
|
1056 |
+
foreach (range(1, 3) as $i) {
|
1057 |
+
$lval = isset($left[$i]) ? $left[$i] : 0;
|
1058 |
+
$rval = isset($right[$i]) ? $right[$i] : 0;
|
1059 |
+
switch ($op) {
|
1060 |
+
case '+':
|
1061 |
+
$out[] = $lval + $rval;
|
1062 |
+
break;
|
1063 |
+
case '-':
|
1064 |
+
$out[] = $lval - $rval;
|
1065 |
+
break;
|
1066 |
+
case '*':
|
1067 |
+
$out[] = $lval * $rval;
|
1068 |
+
break;
|
1069 |
+
case '%':
|
1070 |
+
$out[] = $lval % $rval;
|
1071 |
+
break;
|
1072 |
+
case '/':
|
1073 |
+
if ($rval == 0) {
|
1074 |
+
$this->throwError("color: Can't divide by zero");
|
1075 |
+
}
|
1076 |
+
$out[] = $lval / $rval;
|
1077 |
+
break;
|
1078 |
+
case "==":
|
1079 |
+
return $this->op_eq($left, $right);
|
1080 |
+
case "!=":
|
1081 |
+
return $this->op_neq($left, $right);
|
1082 |
+
default:
|
1083 |
+
$this->throwError("color: unknown op $op");
|
1084 |
+
}
|
1085 |
+
}
|
1086 |
+
|
1087 |
+
if (isset($left[4])) $out[4] = $left[4];
|
1088 |
+
elseif (isset($right[4])) $out[4] = $right[4];
|
1089 |
+
|
1090 |
+
return $this->fixColor($out);
|
1091 |
+
}
|
1092 |
+
|
1093 |
+
protected function op_color_number($op, $left, $right) {
|
1094 |
+
$value = $right[1];
|
1095 |
+
return $this->op_color_color($op, $left,
|
1096 |
+
array("color", $value, $value, $value));
|
1097 |
+
}
|
1098 |
+
|
1099 |
+
protected function op_number_color($op, $left, $right) {
|
1100 |
+
$value = $left[1];
|
1101 |
+
return $this->op_color_color($op,
|
1102 |
+
array("color", $value, $value, $value), $right);
|
1103 |
+
}
|
1104 |
+
|
1105 |
+
protected function op_eq($left, $right) {
|
1106 |
+
if (($lStr = $this->coerceString($left)) && ($rStr = $this->coerceString($right))) {
|
1107 |
+
$lStr[1] = "";
|
1108 |
+
$rStr[1] = "";
|
1109 |
+
return $this->toBool($this->compileValue($lStr) == $this->compileValue($rStr));
|
1110 |
+
}
|
1111 |
+
|
1112 |
+
return $this->toBool($left == $right);
|
1113 |
+
}
|
1114 |
+
|
1115 |
+
protected function op_neq($left, $right) {
|
1116 |
+
return $this->toBool($left != $right);
|
1117 |
+
}
|
1118 |
+
|
1119 |
+
protected function op_gte_number_number($left, $right) {
|
1120 |
+
return $this->toBool($left[1] >= $right[1]);
|
1121 |
+
}
|
1122 |
+
|
1123 |
+
protected function op_gt_number_number($left, $right) {
|
1124 |
+
return $this->toBool($left[1] > $right[1]);
|
1125 |
+
}
|
1126 |
+
|
1127 |
+
protected function op_lte_number_number($left, $right) {
|
1128 |
+
return $this->toBool($left[1] <= $right[1]);
|
1129 |
+
}
|
1130 |
+
|
1131 |
+
protected function op_lt_number_number($left, $right) {
|
1132 |
+
return $this->toBool($left[1] < $right[1]);
|
1133 |
+
}
|
1134 |
+
|
1135 |
+
protected function toBool($thing) {
|
1136 |
+
return $thing ? self::$true : self::$false;
|
1137 |
+
}
|
1138 |
+
|
1139 |
+
/**
|
1140 |
+
* Compiles a primitive value into a CSS property value.
|
1141 |
+
*
|
1142 |
+
* Values in scssphp are typed by being wrapped in arrays, their format is
|
1143 |
+
* typically:
|
1144 |
+
*
|
1145 |
+
* array(type, contents [, additional_contents]*)
|
1146 |
+
*
|
1147 |
+
* The input is expected to be reduced. This function will not work on
|
1148 |
+
* things like expressions and variables.
|
1149 |
+
*
|
1150 |
+
* @param array $value
|
1151 |
+
*/
|
1152 |
+
protected function compileValue($value) {
|
1153 |
+
$value = $this->reduce($value);
|
1154 |
+
|
1155 |
+
list($type) = $value;
|
1156 |
+
switch ($type) {
|
1157 |
+
case "keyword":
|
1158 |
+
return $value[1];
|
1159 |
+
case "color":
|
1160 |
+
// [1] - red component (either number for a %)
|
1161 |
+
// [2] - green component
|
1162 |
+
// [3] - blue component
|
1163 |
+
// [4] - optional alpha component
|
1164 |
+
list(, $r, $g, $b) = $value;
|
1165 |
+
|
1166 |
+
$r = round($r);
|
1167 |
+
$g = round($g);
|
1168 |
+
$b = round($b);
|
1169 |
+
|
1170 |
+
if (count($value) == 5 && $value[4] != 1) { // rgba
|
1171 |
+
return 'rgba('.$r.', '.$g.', '.$b.', '.$value[4].')';
|
1172 |
+
}
|
1173 |
+
|
1174 |
+
$h = sprintf("#%02x%02x%02x", $r, $g, $b);
|
1175 |
+
|
1176 |
+
// Converting hex color to short notation (e.g. #003399 to #039)
|
1177 |
+
if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) {
|
1178 |
+
$h = '#' . $h[1] . $h[3] . $h[5];
|
1179 |
+
}
|
1180 |
+
|
1181 |
+
return $h;
|
1182 |
+
case "number":
|
1183 |
+
return round($value[1], self::$numberPrecision) . $value[2];
|
1184 |
+
case "string":
|
1185 |
+
return $value[1] . $this->compileStringContent($value) . $value[1];
|
1186 |
+
case "function":
|
1187 |
+
$args = !empty($value[2]) ? $this->compileValue($value[2]) : "";
|
1188 |
+
return "$value[1]($args)";
|
1189 |
+
case "list":
|
1190 |
+
$value = $this->extractInterpolation($value);
|
1191 |
+
if ($value[0] != "list") return $this->compileValue($value);
|
1192 |
+
|
1193 |
+
list(, $delim, $items) = $value;
|
1194 |
+
|
1195 |
+
$filtered = array();
|
1196 |
+
foreach ($items as $item) {
|
1197 |
+
if ($item[0] == "null") continue;
|
1198 |
+
$filtered[] = $this->compileValue($item);
|
1199 |
+
}
|
1200 |
+
|
1201 |
+
return implode("$delim ", $filtered);
|
1202 |
+
case "interpolated": # node created by extractInterpolation
|
1203 |
+
list(, $interpolate, $left, $right) = $value;
|
1204 |
+
list(,, $whiteLeft, $whiteRight) = $interpolate;
|
1205 |
+
|
1206 |
+
$left = count($left[2]) > 0 ?
|
1207 |
+
$this->compileValue($left).$whiteLeft : "";
|
1208 |
+
|
1209 |
+
$right = count($right[2]) > 0 ?
|
1210 |
+
$whiteRight.$this->compileValue($right) : "";
|
1211 |
+
|
1212 |
+
return $left.$this->compileValue($interpolate).$right;
|
1213 |
+
|
1214 |
+
case "interpolate": # raw parse node
|
1215 |
+
list(, $exp) = $value;
|
1216 |
+
|
1217 |
+
// strip quotes if it's a string
|
1218 |
+
$reduced = $this->reduce($exp);
|
1219 |
+
switch ($reduced[0]) {
|
1220 |
+
case "string":
|
1221 |
+
$reduced = array("keyword",
|
1222 |
+
$this->compileStringContent($reduced));
|
1223 |
+
break;
|
1224 |
+
case "null":
|
1225 |
+
$reduced = array("keyword", "");
|
1226 |
+
}
|
1227 |
+
|
1228 |
+
return $this->compileValue($reduced);
|
1229 |
+
case "null":
|
1230 |
+
return "null";
|
1231 |
+
default:
|
1232 |
+
$this->throwError("unknown value type: $type");
|
1233 |
+
}
|
1234 |
+
}
|
1235 |
+
|
1236 |
+
protected function compileStringContent($string) {
|
1237 |
+
$parts = array();
|
1238 |
+
foreach ($string[2] as $part) {
|
1239 |
+
if (is_array($part)) {
|
1240 |
+
$parts[] = $this->compileValue($part);
|
1241 |
+
} else {
|
1242 |
+
$parts[] = $part;
|
1243 |
+
}
|
1244 |
+
}
|
1245 |
+
|
1246 |
+
return implode($parts);
|
1247 |
+
}
|
1248 |
+
|
1249 |
+
// doesn't need to be recursive, compileValue will handle that
|
1250 |
+
protected function extractInterpolation($list) {
|
1251 |
+
$items = $list[2];
|
1252 |
+
foreach ($items as $i => $item) {
|
1253 |
+
if ($item[0] == "interpolate") {
|
1254 |
+
$before = array("list", $list[1], array_slice($items, 0, $i));
|
1255 |
+
$after = array("list", $list[1], array_slice($items, $i + 1));
|
1256 |
+
return array("interpolated", $item, $before, $after);
|
1257 |
+
}
|
1258 |
+
}
|
1259 |
+
return $list;
|
1260 |
+
}
|
1261 |
+
|
1262 |
+
// find the final set of selectors
|
1263 |
+
protected function multiplySelectors($env) {
|
1264 |
+
$envs = array();
|
1265 |
+
while (null !== $env) {
|
1266 |
+
if (!empty($env->selectors)) {
|
1267 |
+
$envs[] = $env;
|
1268 |
+
}
|
1269 |
+
$env = $env->parent;
|
1270 |
+
};
|
1271 |
+
|
1272 |
+
$selectors = array();
|
1273 |
+
$parentSelectors = array(array());
|
1274 |
+
while ($env = array_pop($envs)) {
|
1275 |
+
$selectors = array();
|
1276 |
+
foreach ($env->selectors as $selector) {
|
1277 |
+
foreach ($parentSelectors as $parent) {
|
1278 |
+
$selectors[] = $this->joinSelectors($parent, $selector);
|
1279 |
+
}
|
1280 |
+
}
|
1281 |
+
$parentSelectors = $selectors;
|
1282 |
+
}
|
1283 |
+
|
1284 |
+
return $selectors;
|
1285 |
+
}
|
1286 |
+
|
1287 |
+
// looks for & to replace, or append parent before child
|
1288 |
+
protected function joinSelectors($parent, $child) {
|
1289 |
+
$setSelf = false;
|
1290 |
+
$out = array();
|
1291 |
+
foreach ($child as $part) {
|
1292 |
+
$newPart = array();
|
1293 |
+
foreach ($part as $p) {
|
1294 |
+
if ($p == self::$selfSelector) {
|
1295 |
+
$setSelf = true;
|
1296 |
+
foreach ($parent as $i => $parentPart) {
|
1297 |
+
if ($i > 0) {
|
1298 |
+
$out[] = $newPart;
|
1299 |
+
$newPart = array();
|
1300 |
+
}
|
1301 |
+
|
1302 |
+
foreach ($parentPart as $pp) {
|
1303 |
+
$newPart[] = $pp;
|
1304 |
+
}
|
1305 |
+
}
|
1306 |
+
} else {
|
1307 |
+
$newPart[] = $p;
|
1308 |
+
}
|
1309 |
+
}
|
1310 |
+
|
1311 |
+
$out[] = $newPart;
|
1312 |
+
}
|
1313 |
+
|
1314 |
+
return $setSelf ? $out : array_merge($parent, $child);
|
1315 |
+
}
|
1316 |
+
|
1317 |
+
protected function multiplyMedia($env, $childQueries = null) {
|
1318 |
+
if (is_null($env) ||
|
1319 |
+
!empty($env->block->type) && $env->block->type != "media")
|
1320 |
+
{
|
1321 |
+
return $childQueries;
|
1322 |
+
}
|
1323 |
+
|
1324 |
+
// plain old block, skip
|
1325 |
+
if (empty($env->block->type)) {
|
1326 |
+
return $this->multiplyMedia($env->parent, $childQueries);
|
1327 |
+
}
|
1328 |
+
|
1329 |
+
$parentQueries = $env->block->queryList;
|
1330 |
+
if ($childQueries == null) {
|
1331 |
+
$childQueries = $parentQueries;
|
1332 |
+
} else {
|
1333 |
+
$originalQueries = $childQueries;
|
1334 |
+
$childQueries = array();
|
1335 |
+
|
1336 |
+
foreach ($parentQueries as $parentQuery){
|
1337 |
+
foreach ($originalQueries as $childQuery) {
|
1338 |
+
$childQueries []= array_merge($parentQuery, $childQuery);
|
1339 |
+
}
|
1340 |
+
}
|
1341 |
+
}
|
1342 |
+
|
1343 |
+
return $this->multiplyMedia($env->parent, $childQueries);
|
1344 |
+
}
|
1345 |
+
|
1346 |
+
// convert something to list
|
1347 |
+
protected function coerceList($item, $delim = ",") {
|
1348 |
+
if (!is_null($item) && $item[0] == "list") {
|
1349 |
+
return $item;
|
1350 |
+
}
|
1351 |
+
|
1352 |
+
return array("list", $delim, is_null($item) ? array(): array($item));
|
1353 |
+
}
|
1354 |
+
|
1355 |
+
protected function applyArguments($argDef, $argValues) {
|
1356 |
+
$hasVariable = false;
|
1357 |
+
$args = array();
|
1358 |
+
foreach ($argDef as $i => $arg) {
|
1359 |
+
list($name, $default, $isVariable) = $argDef[$i];
|
1360 |
+
$args[$name] = array($i, $name, $default, $isVariable);
|
1361 |
+
$hasVariable |= $isVariable;
|
1362 |
+
}
|
1363 |
+
|
1364 |
+
$keywordArgs = array();
|
1365 |
+
$deferredKeywordArgs = array();
|
1366 |
+
$remaining = array();
|
1367 |
+
// assign the keyword args
|
1368 |
+
foreach ((array) $argValues as $arg) {
|
1369 |
+
if (!empty($arg[0])) {
|
1370 |
+
if (!isset($args[$arg[0][1]])) {
|
1371 |
+
if ($hasVariable) {
|
1372 |
+
$deferredKeywordArgs[$arg[0][1]] = $arg[1];
|
1373 |
+
} else {
|
1374 |
+
$this->throwError("Mixin or function doesn't have an argument named $%s.", $arg[0][1]);
|
1375 |
+
}
|
1376 |
+
} elseif ($args[$arg[0][1]][0] < count($remaining)) {
|
1377 |
+
$this->throwError("The argument $%s was passed both by position and by name.", $arg[0][1]);
|
1378 |
+
} else {
|
1379 |
+
$keywordArgs[$arg[0][1]] = $arg[1];
|
1380 |
+
}
|
1381 |
+
} elseif (count($keywordArgs)) {
|
1382 |
+
$this->throwError('Positional arguments must come before keyword arguments.');
|
1383 |
+
} elseif ($arg[2] == true) {
|
1384 |
+
$val = $this->reduce($arg[1], true);
|
1385 |
+
if ($val[0] == "list") {
|
1386 |
+
foreach ($val[2] as $name => $item) {
|
1387 |
+
if (!is_numeric($name)) {
|
1388 |
+
$keywordArgs[$name] = $item;
|
1389 |
+
} else {
|
1390 |
+
$remaining[] = $item;
|
1391 |
+
}
|
1392 |
+
}
|
1393 |
+
} else {
|
1394 |
+
$remaining[] = $val;
|
1395 |
+
}
|
1396 |
+
} else {
|
1397 |
+
$remaining[] = $arg[1];
|
1398 |
+
}
|
1399 |
+
}
|
1400 |
+
|
1401 |
+
foreach ($args as $arg) {
|
1402 |
+
list($i, $name, $default, $isVariable) = $arg;
|
1403 |
+
if ($isVariable) {
|
1404 |
+
$val = array("list", ",", array());
|
1405 |
+
for ($count = count($remaining); $i < $count; $i++) {
|
1406 |
+
$val[2][] = $remaining[$i];
|
1407 |
+
}
|
1408 |
+
foreach ($deferredKeywordArgs as $itemName => $item) {
|
1409 |
+
$val[2][$itemName] = $item;
|
1410 |
+
}
|
1411 |
+
} elseif (isset($remaining[$i])) {
|
1412 |
+
$val = $remaining[$i];
|
1413 |
+
} elseif (isset($keywordArgs[$name])) {
|
1414 |
+
$val = $keywordArgs[$name];
|
1415 |
+
} elseif (!empty($default)) {
|
1416 |
+
$val = $default;
|
1417 |
+
} else {
|
1418 |
+
$this->throwError("Missing argument $name");
|
1419 |
+
}
|
1420 |
+
|
1421 |
+
$this->set($name, $this->reduce($val, true), true);
|
1422 |
+
}
|
1423 |
+
}
|
1424 |
+
|
1425 |
+
protected function pushEnv($block=null) {
|
1426 |
+
$env = new stdClass;
|
1427 |
+
$env->parent = $this->env;
|
1428 |
+
$env->store = array();
|
1429 |
+
$env->block = $block;
|
1430 |
+
$env->depth = isset($this->env->depth) ? $this->env->depth + 1 : 0;
|
1431 |
+
|
1432 |
+
$this->env = $env;
|
1433 |
+
return $env;
|
1434 |
+
}
|
1435 |
+
|
1436 |
+
protected function normalizeName($name) {
|
1437 |
+
return str_replace("-", "_", $name);
|
1438 |
+
}
|
1439 |
+
|
1440 |
+
protected function getStoreEnv() {
|
1441 |
+
return isset($this->storeEnv) ? $this->storeEnv : $this->env;
|
1442 |
+
}
|
1443 |
+
|
1444 |
+
protected function set($name, $value, $shadow=false) {
|
1445 |
+
$name = $this->normalizeName($name);
|
1446 |
+
|
1447 |
+
if ($shadow) {
|
1448 |
+
$this->setRaw($name, $value);
|
1449 |
+
} else {
|
1450 |
+
$this->setExisting($name, $value);
|
1451 |
+
}
|
1452 |
+
}
|
1453 |
+
|
1454 |
+
// todo: this is bugged?
|
1455 |
+
protected function setExisting($name, $value, $env = null) {
|
1456 |
+
if (is_null($env)) $env = $this->getStoreEnv();
|
1457 |
+
|
1458 |
+
if (isset($env->store[$name])) {
|
1459 |
+
$env->store[$name] = $value;
|
1460 |
+
} elseif (!is_null($env->parent)) {
|
1461 |
+
$this->setExisting($name, $value, $env->parent);
|
1462 |
+
} else {
|
1463 |
+
$this->env->store[$name] = $value;
|
1464 |
+
}
|
1465 |
+
}
|
1466 |
+
|
1467 |
+
protected function setRaw($name, $value) {
|
1468 |
+
$this->env->store[$name] = $value;
|
1469 |
+
}
|
1470 |
+
|
1471 |
+
protected function get($name, $defaultValue = null, $env = null) {
|
1472 |
+
$name = $this->normalizeName($name);
|
1473 |
+
|
1474 |
+
if (is_null($env)) $env = $this->getStoreEnv();
|
1475 |
+
if (is_null($defaultValue)) $defaultValue = self::$defaultValue;
|
1476 |
+
|
1477 |
+
if (isset($env->store[$name])) {
|
1478 |
+
return $env->store[$name];
|
1479 |
+
} elseif (isset($env->parent)) {
|
1480 |
+
return $this->get($name, $defaultValue, $env->parent);
|
1481 |
+
}
|
1482 |
+
|
1483 |
+
return $defaultValue; // found nothing
|
1484 |
+
}
|
1485 |
+
|
1486 |
+
protected function popEnv() {
|
1487 |
+
$env = $this->env;
|
1488 |
+
$this->env = $this->env->parent;
|
1489 |
+
return $env;
|
1490 |
+
}
|
1491 |
+
|
1492 |
+
public function getParsedFiles() {
|
1493 |
+
return $this->parsedFiles;
|
1494 |
+
}
|
1495 |
+
|
1496 |
+
public function addImportPath($path) {
|
1497 |
+
$this->importPaths[] = $path;
|
1498 |
+
}
|
1499 |
+
|
1500 |
+
public function setImportPaths($path) {
|
1501 |
+
$this->importPaths = (array)$path;
|
1502 |
+
}
|
1503 |
+
|
1504 |
+
public function setFormatter($formatterName) {
|
1505 |
+
$this->formatter = $formatterName;
|
1506 |
+
}
|
1507 |
+
|
1508 |
+
public function registerFunction($name, $func) {
|
1509 |
+
$this->userFunctions[$this->normalizeName($name)] = $func;
|
1510 |
+
}
|
1511 |
+
|
1512 |
+
public function unregisterFunction($name) {
|
1513 |
+
unset($this->userFunctions[$this->normalizeName($name)]);
|
1514 |
+
}
|
1515 |
+
|
1516 |
+
protected function importFile($path, $out) {
|
1517 |
+
// see if tree is cached
|
1518 |
+
$realPath = realpath($path);
|
1519 |
+
if (isset($this->importCache[$realPath])) {
|
1520 |
+
$tree = $this->importCache[$realPath];
|
1521 |
+
} else {
|
1522 |
+
$code = file_get_contents($path);
|
1523 |
+
$parser = new scss_parser($path, false);
|
1524 |
+
$tree = $parser->parse($code);
|
1525 |
+
$this->parsedFiles[] = $path;
|
1526 |
+
|
1527 |
+
$this->importCache[$realPath] = $tree;
|
1528 |
+
}
|
1529 |
+
|
1530 |
+
$pi = pathinfo($path);
|
1531 |
+
array_unshift($this->importPaths, $pi['dirname']);
|
1532 |
+
$this->compileChildren($tree->children, $out);
|
1533 |
+
array_shift($this->importPaths);
|
1534 |
+
}
|
1535 |
+
|
1536 |
+
// results the file path for an import url if it exists
|
1537 |
+
protected function findImport($url) {
|
1538 |
+
$urls = array();
|
1539 |
+
|
1540 |
+
// for "normal" scss imports (ignore vanilla css and external requests)
|
1541 |
+
if (!preg_match('/\.css|^http:\/\/$/', $url)) {
|
1542 |
+
// try both normal and the _partial filename
|
1543 |
+
$urls = array($url, preg_replace('/[^\/]+$/', '_\0', $url));
|
1544 |
+
}
|
1545 |
+
|
1546 |
+
foreach ($this->importPaths as $dir) {
|
1547 |
+
if (is_string($dir)) {
|
1548 |
+
// check urls for normal import paths
|
1549 |
+
foreach ($urls as $full) {
|
1550 |
+
$full = $dir .
|
1551 |
+
(!empty($dir) && substr($dir, -1) != '/' ? '/' : '') .
|
1552 |
+
$full;
|
1553 |
+
|
1554 |
+
if ($this->fileExists($file = $full.'.scss') ||
|
1555 |
+
$this->fileExists($file = $full))
|
1556 |
+
{
|
1557 |
+
return $file;
|
1558 |
+
}
|
1559 |
+
}
|
1560 |
+
} else {
|
1561 |
+
// check custom callback for import path
|
1562 |
+
$file = call_user_func($dir,$url,$this);
|
1563 |
+
if ($file !== null) {
|
1564 |
+
return $file;
|
1565 |
+
}
|
1566 |
+
}
|
1567 |
+
}
|
1568 |
+
|
1569 |
+
return null;
|
1570 |
+
}
|
1571 |
+
|
1572 |
+
protected function fileExists($name) {
|
1573 |
+
return is_file($name);
|
1574 |
+
}
|
1575 |
+
|
1576 |
+
protected function callBuiltin($name, $args, &$returnValue) {
|
1577 |
+
// try a lib function
|
1578 |
+
$name = $this->normalizeName($name);
|
1579 |
+
$libName = "lib_".$name;
|
1580 |
+
$f = array($this, $libName);
|
1581 |
+
$prototype = isset(self::$$libName) ? self::$$libName : null;
|
1582 |
+
|
1583 |
+
if (is_callable($f)) {
|
1584 |
+
$sorted = $this->sortArgs($prototype, $args);
|
1585 |
+
foreach ($sorted as &$val) {
|
1586 |
+
$val = $this->reduce($val, true);
|
1587 |
+
}
|
1588 |
+
$returnValue = call_user_func($f, $sorted, $this);
|
1589 |
+
} elseif (isset($this->userFunctions[$name])) {
|
1590 |
+
// see if we can find a user function
|
1591 |
+
$fn = $this->userFunctions[$name];
|
1592 |
+
|
1593 |
+
foreach ($args as &$val) {
|
1594 |
+
$val = $this->reduce($val[1], true);
|
1595 |
+
}
|
1596 |
+
|
1597 |
+
$returnValue = call_user_func($fn, $args, $this);
|
1598 |
+
}
|
1599 |
+
|
1600 |
+
if (isset($returnValue)) {
|
1601 |
+
// coerce a php value into a scss one
|
1602 |
+
if (is_numeric($returnValue)) {
|
1603 |
+
$returnValue = array('number', $returnValue, "");
|
1604 |
+
} elseif (is_bool($returnValue)) {
|
1605 |
+
$returnValue = $returnValue ? self::$true : self::$false;
|
1606 |
+
} elseif (!is_array($returnValue)) {
|
1607 |
+
$returnValue = array('keyword', $returnValue);
|
1608 |
+
}
|
1609 |
+
|
1610 |
+
return true;
|
1611 |
+
}
|
1612 |
+
|
1613 |
+
return false;
|
1614 |
+
}
|
1615 |
+
|
1616 |
+
// sorts any keyword arguments
|
1617 |
+
// TODO: merge with apply arguments
|
1618 |
+
protected function sortArgs($prototype, $args) {
|
1619 |
+
$keyArgs = array();
|
1620 |
+
$posArgs = array();
|
1621 |
+
|
1622 |
+
foreach ($args as $arg) {
|
1623 |
+
list($key, $value) = $arg;
|
1624 |
+
$key = $key[1];
|
1625 |
+
if (empty($key)) {
|
1626 |
+
$posArgs[] = $value;
|
1627 |
+
} else {
|
1628 |
+
$keyArgs[$key] = $value;
|
1629 |
+
}
|
1630 |
+
}
|
1631 |
+
|
1632 |
+
if (is_null($prototype)) return $posArgs;
|
1633 |
+
|
1634 |
+
$finalArgs = array();
|
1635 |
+
foreach ($prototype as $i => $names) {
|
1636 |
+
if (isset($posArgs[$i])) {
|
1637 |
+
$finalArgs[] = $posArgs[$i];
|
1638 |
+
continue;
|
1639 |
+
}
|
1640 |
+
|
1641 |
+
$set = false;
|
1642 |
+
foreach ((array)$names as $name) {
|
1643 |
+
if (isset($keyArgs[$name])) {
|
1644 |
+
$finalArgs[] = $keyArgs[$name];
|
1645 |
+
$set = true;
|
1646 |
+
break;
|
1647 |
+
}
|
1648 |
+
}
|
1649 |
+
|
1650 |
+
if (!$set) {
|
1651 |
+
$finalArgs[] = null;
|
1652 |
+
}
|
1653 |
+
}
|
1654 |
+
|
1655 |
+
return $finalArgs;
|
1656 |
+
}
|
1657 |
+
|
1658 |
+
protected function coerceForExpression($value) {
|
1659 |
+
if ($color = $this->coerceColor($value)) {
|
1660 |
+
return $color;
|
1661 |
+
}
|
1662 |
+
|
1663 |
+
return $value;
|
1664 |
+
}
|
1665 |
+
|
1666 |
+
protected function coerceColor($value) {
|
1667 |
+
switch ($value[0]) {
|
1668 |
+
case "color": return $value;
|
1669 |
+
case "keyword":
|
1670 |
+
$name = $value[1];
|
1671 |
+
if (isset(self::$cssColors[$name])) {
|
1672 |
+
@list($r, $g, $b, $a) = explode(',', self::$cssColors[$name]);
|
1673 |
+
return isset($a)
|
1674 |
+
? array('color', (int) $r, (int) $g, (int) $b, (int) $a)
|
1675 |
+
: array('color', (int) $r, (int) $g, (int) $b);
|
1676 |
+
}
|
1677 |
+
return null;
|
1678 |
+
}
|
1679 |
+
|
1680 |
+
return null;
|
1681 |
+
}
|
1682 |
+
|
1683 |
+
protected function coerceString($value) {
|
1684 |
+
switch ($value[0]) {
|
1685 |
+
case "string":
|
1686 |
+
return $value;
|
1687 |
+
case "keyword":
|
1688 |
+
return array("string", "", array($value[1]));
|
1689 |
+
}
|
1690 |
+
return null;
|
1691 |
+
}
|
1692 |
+
|
1693 |
+
protected function assertList($value) {
|
1694 |
+
if ($value[0] != "list")
|
1695 |
+
$this->throwError("expecting list");
|
1696 |
+
return $value;
|
1697 |
+
}
|
1698 |
+
|
1699 |
+
protected function assertColor($value) {
|
1700 |
+
if ($color = $this->coerceColor($value)) return $color;
|
1701 |
+
$this->throwError("expecting color");
|
1702 |
+
}
|
1703 |
+
|
1704 |
+
protected function assertNumber($value) {
|
1705 |
+
if ($value[0] != "number")
|
1706 |
+
$this->throwError("expecting number");
|
1707 |
+
return $value[1];
|
1708 |
+
}
|
1709 |
+
|
1710 |
+
protected function coercePercent($value) {
|
1711 |
+
if ($value[0] == "number") {
|
1712 |
+
if ($value[2] == "%") {
|
1713 |
+
return $value[1] / 100;
|
1714 |
+
}
|
1715 |
+
return $value[1];
|
1716 |
+
}
|
1717 |
+
return 0;
|
1718 |
+
}
|
1719 |
+
|
1720 |
+
// make sure a color's components don't go out of bounds
|
1721 |
+
protected function fixColor($c) {
|
1722 |
+
foreach (range(1, 3) as $i) {
|
1723 |
+
if ($c[$i] < 0) $c[$i] = 0;
|
1724 |
+
if ($c[$i] > 255) $c[$i] = 255;
|
1725 |
+
}
|
1726 |
+
|
1727 |
+
return $c;
|
1728 |
+
}
|
1729 |
+
|
1730 |
+
function toHSL($red, $green, $blue) {
|
1731 |
+
$r = $red / 255;
|
1732 |
+
$g = $green / 255;
|
1733 |
+
$b = $blue / 255;
|
1734 |
+
|
1735 |
+
$min = min($r, $g, $b);
|
1736 |
+
$max = max($r, $g, $b);
|
1737 |
+
$d = $max - $min;
|
1738 |
+
$l = ($min + $max) / 2;
|
1739 |
+
|
1740 |
+
if ($min == $max) {
|
1741 |
+
$s = $h = 0;
|
1742 |
+
} else {
|
1743 |
+
if ($l < 0.5)
|
1744 |
+
$s = $d / (2 * $l);
|
1745 |
+
else
|
1746 |
+
$s = $d / (2 - 2 * $l);
|
1747 |
+
|
1748 |
+
if ($r == $max)
|
1749 |
+
$h = 60 * ($g - $b) / $d;
|
1750 |
+
elseif ($g == $max)
|
1751 |
+
$h = 60 * ($b - $r) / $d + 120;
|
1752 |
+
elseif ($b == $max)
|
1753 |
+
$h = 60 * ($r - $g) / $d + 240;
|
1754 |
+
}
|
1755 |
+
|
1756 |
+
return array('hsl', fmod($h, 360), $s * 100, $l * 100);
|
1757 |
+
}
|
1758 |
+
|
1759 |
+
function hueToRGB($m1, $m2, $h) {
|
1760 |
+
if ($h < 0)
|
1761 |
+
$h += 1;
|
1762 |
+
elseif ($h > 1)
|
1763 |
+
$h -= 1;
|
1764 |
+
|
1765 |
+
if ($h * 6 < 1)
|
1766 |
+
return $m1 + ($m2 - $m1) * $h * 6;
|
1767 |
+
|
1768 |
+
if ($h * 2 < 1)
|
1769 |
+
return $m2;
|
1770 |
+
|
1771 |
+
if ($h * 3 < 2)
|
1772 |
+
return $m1 + ($m2 - $m1) * (2/3 - $h) * 6;
|
1773 |
+
|
1774 |
+
return $m1;
|
1775 |
+
}
|
1776 |
+
|
1777 |
+
// H from 0 to 360, S and L from 0 to 100
|
1778 |
+
function toRGB($hue, $saturation, $lightness) {
|
1779 |
+
if ($hue < 0) {
|
1780 |
+
$hue += 360;
|
1781 |
+
}
|
1782 |
+
|
1783 |
+
$h = $hue / 360;
|
1784 |
+
$s = min(100, max(0, $saturation)) / 100;
|
1785 |
+
$l = min(100, max(0, $lightness)) / 100;
|
1786 |
+
|
1787 |
+
$m2 = $l <= 0.5 ? $l * ($s + 1) : $l + $s - $l * $s;
|
1788 |
+
$m1 = $l * 2 - $m2;
|
1789 |
+
|
1790 |
+
$r = $this->hueToRGB($m1, $m2, $h + 1/3) * 255;
|
1791 |
+
$g = $this->hueToRGB($m1, $m2, $h) * 255;
|
1792 |
+
$b = $this->hueToRGB($m1, $m2, $h - 1/3) * 255;
|
1793 |
+
|
1794 |
+
$out = array('color', $r, $g, $b);
|
1795 |
+
return $out;
|
1796 |
+
}
|
1797 |
+
|
1798 |
+
// Built in functions
|
1799 |
+
|
1800 |
+
protected static $lib_if = array("condition", "if-true", "if-false");
|
1801 |
+
protected function lib_if($args) {
|
1802 |
+
list($cond,$t, $f) = $args;
|
1803 |
+
if ($cond == self::$false) return $f;
|
1804 |
+
return $t;
|
1805 |
+
}
|
1806 |
+
|
1807 |
+
protected static $lib_index = array("list", "value");
|
1808 |
+
protected function lib_index($args) {
|
1809 |
+
list($list, $value) = $args;
|
1810 |
+
$list = $this->assertList($list);
|
1811 |
+
|
1812 |
+
$values = array();
|
1813 |
+
foreach ($list[2] as $item) {
|
1814 |
+
$values[] = $this->normalizeValue($item);
|
1815 |
+
}
|
1816 |
+
$key = array_search($this->normalizeValue($value), $values);
|
1817 |
+
|
1818 |
+
return false === $key ? false : $key + 1;
|
1819 |
+
}
|
1820 |
+
|
1821 |
+
protected static $lib_rgb = array("red", "green", "blue");
|
1822 |
+
protected function lib_rgb($args) {
|
1823 |
+
list($r,$g,$b) = $args;
|
1824 |
+
return array("color", $r[1], $g[1], $b[1]);
|
1825 |
+
}
|
1826 |
+
|
1827 |
+
protected static $lib_rgba = array(
|
1828 |
+
array("red", "color"),
|
1829 |
+
"green", "blue", "alpha");
|
1830 |
+
protected function lib_rgba($args) {
|
1831 |
+
if ($color = $this->coerceColor($args[0])) {
|
1832 |
+
$num = is_null($args[1]) ? $args[3] : $args[1];
|
1833 |
+
$alpha = $this->assertNumber($num);
|
1834 |
+
$color[4] = $alpha;
|
1835 |
+
return $color;
|
1836 |
+
}
|
1837 |
+
|
1838 |
+
list($r,$g,$b, $a) = $args;
|
1839 |
+
return array("color", $r[1], $g[1], $b[1], $a[1]);
|
1840 |
+
}
|
1841 |
+
|
1842 |
+
// helper function for adjust_color, change_color, and scale_color
|
1843 |
+
protected function alter_color($args, $fn) {
|
1844 |
+
$color = $this->assertColor($args[0]);
|
1845 |
+
|
1846 |
+
foreach (array(1,2,3,7) as $i) {
|
1847 |
+
if (!is_null($args[$i])) {
|
1848 |
+
$val = $this->assertNumber($args[$i]);
|
1849 |
+
$ii = $i == 7 ? 4 : $i; // alpha
|
1850 |
+
$color[$ii] =
|
1851 |
+
$this->$fn(isset($color[$ii]) ? $color[$ii] : 0, $val, $i);
|
1852 |
+
}
|
1853 |
+
}
|
1854 |
+
|
1855 |
+
if (!is_null($args[4]) || !is_null($args[5]) || !is_null($args[6])) {
|
1856 |
+
$hsl = $this->toHSL($color[1], $color[2], $color[3]);
|
1857 |
+
foreach (array(4,5,6) as $i) {
|
1858 |
+
if (!is_null($args[$i])) {
|
1859 |
+
$val = $this->assertNumber($args[$i]);
|
1860 |
+
$hsl[$i - 3] = $this->$fn($hsl[$i - 3], $val, $i);
|
1861 |
+
}
|
1862 |
+
}
|
1863 |
+
|
1864 |
+
$rgb = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
|
1865 |
+
if (isset($color[4])) $rgb[4] = $color[4];
|
1866 |
+
$color = $rgb;
|
1867 |
+
}
|
1868 |
+
|
1869 |
+
return $color;
|
1870 |
+
}
|
1871 |
+
|
1872 |
+
protected static $lib_adjust_color = array(
|
1873 |
+
"color", "red", "green", "blue",
|
1874 |
+
"hue", "saturation", "lightness", "alpha"
|
1875 |
+
);
|
1876 |
+
protected function adjust_color_helper($base, $alter, $i) {
|
1877 |
+
return $base += $alter;
|
1878 |
+
}
|
1879 |
+
protected function lib_adjust_color($args) {
|
1880 |
+
return $this->alter_color($args, "adjust_color_helper");
|
1881 |
+
}
|
1882 |
+
|
1883 |
+
protected static $lib_change_color = array(
|
1884 |
+
"color", "red", "green", "blue",
|
1885 |
+
"hue", "saturation", "lightness", "alpha"
|
1886 |
+
);
|
1887 |
+
protected function change_color_helper($base, $alter, $i) {
|
1888 |
+
return $alter;
|
1889 |
+
}
|
1890 |
+
protected function lib_change_color($args) {
|
1891 |
+
return $this->alter_color($args, "change_color_helper");
|
1892 |
+
}
|
1893 |
+
|
1894 |
+
protected static $lib_scale_color = array(
|
1895 |
+
"color", "red", "green", "blue",
|
1896 |
+
"hue", "saturation", "lightness", "alpha"
|
1897 |
+
);
|
1898 |
+
protected function scale_color_helper($base, $scale, $i) {
|
1899 |
+
// 1,2,3 - rgb
|
1900 |
+
// 4, 5, 6 - hsl
|
1901 |
+
// 7 - a
|
1902 |
+
switch ($i) {
|
1903 |
+
case 1:
|
1904 |
+
case 2:
|
1905 |
+
case 3:
|
1906 |
+
$max = 255; break;
|
1907 |
+
case 4:
|
1908 |
+
$max = 360; break;
|
1909 |
+
case 7:
|
1910 |
+
$max = 1; break;
|
1911 |
+
default:
|
1912 |
+
$max = 100;
|
1913 |
+
}
|
1914 |
+
|
1915 |
+
$scale = $scale / 100;
|
1916 |
+
if ($scale < 0) {
|
1917 |
+
return $base * $scale + $base;
|
1918 |
+
} else {
|
1919 |
+
return ($max - $base) * $scale + $base;
|
1920 |
+
}
|
1921 |
+
}
|
1922 |
+
protected function lib_scale_color($args) {
|
1923 |
+
return $this->alter_color($args, "scale_color_helper");
|
1924 |
+
}
|
1925 |
+
|
1926 |
+
protected static $lib_ie_hex_str = array("color");
|
1927 |
+
protected function lib_ie_hex_str($args) {
|
1928 |
+
$color = $this->coerceColor($args[0]);
|
1929 |
+
$color[4] = isset($color[4]) ? round(255*$color[4]) : 255;
|
1930 |
+
|
1931 |
+
return sprintf('#%02X%02X%02X%02X', $color[4], $color[1], $color[2], $color[3]);
|
1932 |
+
}
|
1933 |
+
|
1934 |
+
protected static $lib_red = array("color");
|
1935 |
+
protected function lib_red($args) {
|
1936 |
+
$color = $this->coerceColor($args[0]);
|
1937 |
+
return $color[1];
|
1938 |
+
}
|
1939 |
+
|
1940 |
+
protected static $lib_green = array("color");
|
1941 |
+
protected function lib_green($args) {
|
1942 |
+
$color = $this->coerceColor($args[0]);
|
1943 |
+
return $color[2];
|
1944 |
+
}
|
1945 |
+
|
1946 |
+
protected static $lib_blue = array("color");
|
1947 |
+
protected function lib_blue($args) {
|
1948 |
+
$color = $this->coerceColor($args[0]);
|
1949 |
+
return $color[3];
|
1950 |
+
}
|
1951 |
+
|
1952 |
+
protected static $lib_alpha = array("color");
|
1953 |
+
protected function lib_alpha($args) {
|
1954 |
+
if ($color = $this->coerceColor($args[0])) {
|
1955 |
+
return isset($color[4]) ? $color[4] : 1;
|
1956 |
+
}
|
1957 |
+
|
1958 |
+
// this might be the IE function, so return value unchanged
|
1959 |
+
return null;
|
1960 |
+
}
|
1961 |
+
|
1962 |
+
protected static $lib_opacity = array("color");
|
1963 |
+
protected function lib_opacity($args) {
|
1964 |
+
$value = $args[0];
|
1965 |
+
if ($value[0] === 'number') return null;
|
1966 |
+
return $this->lib_alpha($args);
|
1967 |
+
}
|
1968 |
+
|
1969 |
+
// mix two colors
|
1970 |
+
protected static $lib_mix = array("color-1", "color-2", "weight");
|
1971 |
+
protected function lib_mix($args) {
|
1972 |
+
list($first, $second, $weight) = $args;
|
1973 |
+
$first = $this->assertColor($first);
|
1974 |
+
$second = $this->assertColor($second);
|
1975 |
+
|
1976 |
+
if (is_null($weight)) {
|
1977 |
+
$weight = 0.5;
|
1978 |
+
} else {
|
1979 |
+
$weight = $this->coercePercent($weight);
|
1980 |
+
}
|
1981 |
+
|
1982 |
+
$firstAlpha = isset($first[4]) ? $first[4] : 1;
|
1983 |
+
$secondAlpha = isset($second[4]) ? $second[4] : 1;
|
1984 |
+
|
1985 |
+
$w = $weight * 2 - 1;
|
1986 |
+
$a = $firstAlpha - $secondAlpha;
|
1987 |
+
|
1988 |
+
$w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0;
|
1989 |
+
$w2 = 1.0 - $w1;
|
1990 |
+
|
1991 |
+
$new = array('color',
|
1992 |
+
$w1 * $first[1] + $w2 * $second[1],
|
1993 |
+
$w1 * $first[2] + $w2 * $second[2],
|
1994 |
+
$w1 * $first[3] + $w2 * $second[3],
|
1995 |
+
);
|
1996 |
+
|
1997 |
+
if ($firstAlpha != 1.0 || $secondAlpha != 1.0) {
|
1998 |
+
$new[] = $firstAlpha * $weight + $secondAlpha * ($weight - 1);
|
1999 |
+
}
|
2000 |
+
|
2001 |
+
return $this->fixColor($new);
|
2002 |
+
}
|
2003 |
+
|
2004 |
+
protected static $lib_hsl = array("hue", "saturation", "lightness");
|
2005 |
+
protected function lib_hsl($args) {
|
2006 |
+
list($h, $s, $l) = $args;
|
2007 |
+
return $this->toRGB($h[1], $s[1], $l[1]);
|
2008 |
+
}
|
2009 |
+
|
2010 |
+
protected static $lib_hsla = array("hue", "saturation",
|
2011 |
+
"lightness", "alpha");
|
2012 |
+
protected function lib_hsla($args) {
|
2013 |
+
list($h, $s, $l, $a) = $args;
|
2014 |
+
$color = $this->toRGB($h[1], $s[1], $l[1]);
|
2015 |
+
$color[4] = $a[1];
|
2016 |
+
return $color;
|
2017 |
+
}
|
2018 |
+
|
2019 |
+
protected static $lib_hue = array("color");
|
2020 |
+
protected function lib_hue($args) {
|
2021 |
+
$color = $this->assertColor($args[0]);
|
2022 |
+
$hsl = $this->toHSL($color[1], $color[2], $color[3]);
|
2023 |
+
return array("number", $hsl[1], "deg");
|
2024 |
+
}
|
2025 |
+
|
2026 |
+
protected static $lib_saturation = array("color");
|
2027 |
+
protected function lib_saturation($args) {
|
2028 |
+
$color = $this->assertColor($args[0]);
|
2029 |
+
$hsl = $this->toHSL($color[1], $color[2], $color[3]);
|
2030 |
+
return array("number", $hsl[2], "%");
|
2031 |
+
}
|
2032 |
+
|
2033 |
+
protected static $lib_lightness = array("color");
|
2034 |
+
protected function lib_lightness($args) {
|
2035 |
+
$color = $this->assertColor($args[0]);
|
2036 |
+
$hsl = $this->toHSL($color[1], $color[2], $color[3]);
|
2037 |
+
return array("number", $hsl[3], "%");
|
2038 |
+
}
|
2039 |
+
|
2040 |
+
protected function adjustHsl($color, $idx, $amount) {
|
2041 |
+
$hsl = $this->toHSL($color[1], $color[2], $color[3]);
|
2042 |
+
$hsl[$idx] += $amount;
|
2043 |
+
$out = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
|
2044 |
+
if (isset($color[4])) $out[4] = $color[4];
|
2045 |
+
return $out;
|
2046 |
+
}
|
2047 |
+
|
2048 |
+
protected static $lib_adjust_hue = array("color", "degrees");
|
2049 |
+
protected function lib_adjust_hue($args) {
|
2050 |
+
$color = $this->assertColor($args[0]);
|
2051 |
+
$degrees = $this->assertNumber($args[1]);
|
2052 |
+
return $this->adjustHsl($color, 1, $degrees);
|
2053 |
+
}
|
2054 |
+
|
2055 |
+
protected static $lib_lighten = array("color", "amount");
|
2056 |
+
protected function lib_lighten($args) {
|
2057 |
+
$color = $this->assertColor($args[0]);
|
2058 |
+
$amount = 100*$this->coercePercent($args[1]);
|
2059 |
+
return $this->adjustHsl($color, 3, $amount);
|
2060 |
+
}
|
2061 |
+
|
2062 |
+
protected static $lib_darken = array("color", "amount");
|
2063 |
+
protected function lib_darken($args) {
|
2064 |
+
$color = $this->assertColor($args[0]);
|
2065 |
+
$amount = 100*$this->coercePercent($args[1]);
|
2066 |
+
return $this->adjustHsl($color, 3, -$amount);
|
2067 |
+
}
|
2068 |
+
|
2069 |
+
protected static $lib_saturate = array("color", "amount");
|
2070 |
+
protected function lib_saturate($args) {
|
2071 |
+
$value = $args[0];
|
2072 |
+
if ($value[0] === 'number') return null;
|
2073 |
+
$color = $this->assertColor($value);
|
2074 |
+
$amount = 100*$this->coercePercent($args[1]);
|
2075 |
+
return $this->adjustHsl($color, 2, $amount);
|
2076 |
+
}
|
2077 |
+
|
2078 |
+
protected static $lib_desaturate = array("color", "amount");
|
2079 |
+
protected function lib_desaturate($args) {
|
2080 |
+
$color = $this->assertColor($args[0]);
|
2081 |
+
$amount = 100*$this->coercePercent($args[1]);
|
2082 |
+
return $this->adjustHsl($color, 2, -$amount);
|
2083 |
+
}
|
2084 |
+
|
2085 |
+
protected static $lib_grayscale = array("color");
|
2086 |
+
protected function lib_grayscale($args) {
|
2087 |
+
$value = $args[0];
|
2088 |
+
if ($value[0] === 'number') return null;
|
2089 |
+
return $this->adjustHsl($this->assertColor($value), 2, -100);
|
2090 |
+
}
|
2091 |
+
|
2092 |
+
protected static $lib_complement = array("color");
|
2093 |
+
protected function lib_complement($args) {
|
2094 |
+
return $this->adjustHsl($this->assertColor($args[0]), 1, 180);
|
2095 |
+
}
|
2096 |
+
|
2097 |
+
protected static $lib_invert = array("color");
|
2098 |
+
protected function lib_invert($args) {
|
2099 |
+
$value = $args[0];
|
2100 |
+
if ($value[0] === 'number') return null;
|
2101 |
+
$color = $this->assertColor($value);
|
2102 |
+
$color[1] = 255 - $color[1];
|
2103 |
+
$color[2] = 255 - $color[2];
|
2104 |
+
$color[3] = 255 - $color[3];
|
2105 |
+
return $color;
|
2106 |
+
}
|
2107 |
+
|
2108 |
+
// increases opacity by amount
|
2109 |
+
protected static $lib_opacify = array("color", "amount");
|
2110 |
+
protected function lib_opacify($args) {
|
2111 |
+
$color = $this->assertColor($args[0]);
|
2112 |
+
$amount = $this->coercePercent($args[1]);
|
2113 |
+
|
2114 |
+
$color[4] = (isset($color[4]) ? $color[4] : 1) + $amount;
|
2115 |
+
$color[4] = min(1, max(0, $color[4]));
|
2116 |
+
return $color;
|
2117 |
+
}
|
2118 |
+
|
2119 |
+
protected static $lib_fade_in = array("color", "amount");
|
2120 |
+
protected function lib_fade_in($args) {
|
2121 |
+
return $this->lib_opacify($args);
|
2122 |
+
}
|
2123 |
+
|
2124 |
+
// decreases opacity by amount
|
2125 |
+
protected static $lib_transparentize = array("color", "amount");
|
2126 |
+
protected function lib_transparentize($args) {
|
2127 |
+
$color = $this->assertColor($args[0]);
|
2128 |
+
$amount = $this->coercePercent($args[1]);
|
2129 |
+
|
2130 |
+
$color[4] = (isset($color[4]) ? $color[4] : 1) - $amount;
|
2131 |
+
$color[4] = min(1, max(0, $color[4]));
|
2132 |
+
return $color;
|
2133 |
+
}
|
2134 |
+
|
2135 |
+
protected static $lib_fade_out = array("color", "amount");
|
2136 |
+
protected function lib_fade_out($args) {
|
2137 |
+
return $this->lib_transparentize($args);
|
2138 |
+
}
|
2139 |
+
|
2140 |
+
protected static $lib_unquote = array("string");
|
2141 |
+
protected function lib_unquote($args) {
|
2142 |
+
$str = $args[0];
|
2143 |
+
if ($str[0] == "string") $str[1] = "";
|
2144 |
+
return $str;
|
2145 |
+
}
|
2146 |
+
|
2147 |
+
protected static $lib_quote = array("string");
|
2148 |
+
protected function lib_quote($args) {
|
2149 |
+
$value = $args[0];
|
2150 |
+
if ($value[0] == "string" && !empty($value[1]))
|
2151 |
+
return $value;
|
2152 |
+
return array("string", '"', array($value));
|
2153 |
+
}
|
2154 |
+
|
2155 |
+
protected static $lib_percentage = array("value");
|
2156 |
+
protected function lib_percentage($args) {
|
2157 |
+
return array("number",
|
2158 |
+
$this->coercePercent($args[0]) * 100,
|
2159 |
+
"%");
|
2160 |
+
}
|
2161 |
+
|
2162 |
+
protected static $lib_round = array("value");
|
2163 |
+
protected function lib_round($args) {
|
2164 |
+
$num = $args[0];
|
2165 |
+
$num[1] = round($num[1]);
|
2166 |
+
return $num;
|
2167 |
+
}
|
2168 |
+
|
2169 |
+
protected static $lib_floor = array("value");
|
2170 |
+
protected function lib_floor($args) {
|
2171 |
+
$num = $args[0];
|
2172 |
+
$num[1] = floor($num[1]);
|
2173 |
+
return $num;
|
2174 |
+
}
|
2175 |
+
|
2176 |
+
protected static $lib_ceil = array("value");
|
2177 |
+
protected function lib_ceil($args) {
|
2178 |
+
$num = $args[0];
|
2179 |
+
$num[1] = ceil($num[1]);
|
2180 |
+
return $num;
|
2181 |
+
}
|
2182 |
+
|
2183 |
+
protected static $lib_abs = array("value");
|
2184 |
+
protected function lib_abs($args) {
|
2185 |
+
$num = $args[0];
|
2186 |
+
$num[1] = abs($num[1]);
|
2187 |
+
return $num;
|
2188 |
+
}
|
2189 |
+
|
2190 |
+
protected function lib_min($args) {
|
2191 |
+
$numbers = $this->getNormalizedNumbers($args);
|
2192 |
+
$min = null;
|
2193 |
+
foreach ($numbers as $key => $number) {
|
2194 |
+
if (null === $min || $number[1] <= $min[1]) {
|
2195 |
+
$min = array($key, $number[1]);
|
2196 |
+
}
|
2197 |
+
}
|
2198 |
+
|
2199 |
+
return $args[$min[0]];
|
2200 |
+
}
|
2201 |
+
|
2202 |
+
protected function lib_max($args) {
|
2203 |
+
$numbers = $this->getNormalizedNumbers($args);
|
2204 |
+
$max = null;
|
2205 |
+
foreach ($numbers as $key => $number) {
|
2206 |
+
if (null === $max || $number[1] >= $max[1]) {
|
2207 |
+
$max = array($key, $number[1]);
|
2208 |
+
}
|
2209 |
+
}
|
2210 |
+
|
2211 |
+
return $args[$max[0]];
|
2212 |
+
}
|
2213 |
+
|
2214 |
+
protected function getNormalizedNumbers($args) {
|
2215 |
+
$unit = null;
|
2216 |
+
$originalUnit = null;
|
2217 |
+
$numbers = array();
|
2218 |
+
foreach ($args as $key => $item) {
|
2219 |
+
if ('number' != $item[0]) {
|
2220 |
+
$this->throwError("%s is not a number", $item[0]);
|
2221 |
+
}
|
2222 |
+
$number = $this->normalizeNumber($item);
|
2223 |
+
|
2224 |
+
if (null === $unit) {
|
2225 |
+
$unit = $number[2];
|
2226 |
+
$originalUnit = $item[2];
|
2227 |
+
} elseif ($unit !== $number[2]) {
|
2228 |
+
$this->throwError('Incompatible units: "%s" and "%s".', $originalUnit, $item[2]);
|
2229 |
+
}
|
2230 |
+
|
2231 |
+
$numbers[$key] = $number;
|
2232 |
+
}
|
2233 |
+
|
2234 |
+
return $numbers;
|
2235 |
+
}
|
2236 |
+
|
2237 |
+
protected static $lib_length = array("list");
|
2238 |
+
protected function lib_length($args) {
|
2239 |
+
$list = $this->coerceList($args[0]);
|
2240 |
+
return count($list[2]);
|
2241 |
+
}
|
2242 |
+
|
2243 |
+
protected static $lib_nth = array("list", "n");
|
2244 |
+
protected function lib_nth($args) {
|
2245 |
+
$list = $this->coerceList($args[0]);
|
2246 |
+
$n = $this->assertNumber($args[1]) - 1;
|
2247 |
+
return isset($list[2][$n]) ? $list[2][$n] : self::$defaultValue;
|
2248 |
+
}
|
2249 |
+
|
2250 |
+
protected function listSeparatorForJoin($list1, $sep) {
|
2251 |
+
if (is_null($sep)) return $list1[1];
|
2252 |
+
switch ($this->compileValue($sep)) {
|
2253 |
+
case "comma":
|
2254 |
+
return ",";
|
2255 |
+
case "space":
|
2256 |
+
return "";
|
2257 |
+
default:
|
2258 |
+
return $list1[1];
|
2259 |
+
}
|
2260 |
+
}
|
2261 |
+
|
2262 |
+
protected static $lib_join = array("list1", "list2", "separator");
|
2263 |
+
protected function lib_join($args) {
|
2264 |
+
list($list1, $list2, $sep) = $args;
|
2265 |
+
$list1 = $this->coerceList($list1, " ");
|
2266 |
+
$list2 = $this->coerceList($list2, " ");
|
2267 |
+
$sep = $this->listSeparatorForJoin($list1, $sep);
|
2268 |
+
return array("list", $sep, array_merge($list1[2], $list2[2]));
|
2269 |
+
}
|
2270 |
+
|
2271 |
+
protected static $lib_append = array("list", "val", "separator");
|
2272 |
+
protected function lib_append($args) {
|
2273 |
+
list($list1, $value, $sep) = $args;
|
2274 |
+
$list1 = $this->coerceList($list1, " ");
|
2275 |
+
$sep = $this->listSeparatorForJoin($list1, $sep);
|
2276 |
+
return array("list", $sep, array_merge($list1[2], array($value)));
|
2277 |
+
}
|
2278 |
+
|
2279 |
+
protected function lib_zip($args) {
|
2280 |
+
foreach ($args as $arg) {
|
2281 |
+
$this->assertList($arg);
|
2282 |
+
}
|
2283 |
+
|
2284 |
+
$lists = array();
|
2285 |
+
$firstList = array_shift($args);
|
2286 |
+
foreach ($firstList[2] as $key => $item) {
|
2287 |
+
$list = array("list", "", array($item));
|
2288 |
+
foreach ($args as $arg) {
|
2289 |
+
if (isset($arg[2][$key])) {
|
2290 |
+
$list[2][] = $arg[2][$key];
|
2291 |
+
} else {
|
2292 |
+
break 2;
|
2293 |
+
}
|
2294 |
+
}
|
2295 |
+
$lists[] = $list;
|
2296 |
+
}
|
2297 |
+
|
2298 |
+
return array("list", ",", $lists);
|
2299 |
+
}
|
2300 |
+
|
2301 |
+
protected static $lib_type_of = array("value");
|
2302 |
+
protected function lib_type_of($args) {
|
2303 |
+
$value = $args[0];
|
2304 |
+
switch ($value[0]) {
|
2305 |
+
case "keyword":
|
2306 |
+
if ($value == self::$true || $value == self::$false) {
|
2307 |
+
return "bool";
|
2308 |
+
}
|
2309 |
+
|
2310 |
+
if ($this->coerceColor($value)) {
|
2311 |
+
return "color";
|
2312 |
+
}
|
2313 |
+
|
2314 |
+
return "string";
|
2315 |
+
default:
|
2316 |
+
return $value[0];
|
2317 |
+
}
|
2318 |
+
}
|
2319 |
+
|
2320 |
+
protected static $lib_unit = array("number");
|
2321 |
+
protected function lib_unit($args) {
|
2322 |
+
$num = $args[0];
|
2323 |
+
if ($num[0] == "number") {
|
2324 |
+
return array("string", '"', array($num[2]));
|
2325 |
+
}
|
2326 |
+
return "";
|
2327 |
+
}
|
2328 |
+
|
2329 |
+
protected static $lib_unitless = array("number");
|
2330 |
+
protected function lib_unitless($args) {
|
2331 |
+
$value = $args[0];
|
2332 |
+
return $value[0] == "number" && empty($value[2]);
|
2333 |
+
}
|
2334 |
+
|
2335 |
+
protected static $lib_comparable = array("number-1", "number-2");
|
2336 |
+
protected function lib_comparable($args) {
|
2337 |
+
list($number1, $number2) = $args;
|
2338 |
+
if (!isset($number1[0]) || $number1[0] != "number" || !isset($number2[0]) || $number2[0] != "number") {
|
2339 |
+
$this->throwError('Invalid argument(s) for "comparable"');
|
2340 |
+
}
|
2341 |
+
|
2342 |
+
$number1 = $this->normalizeNumber($number1);
|
2343 |
+
$number2 = $this->normalizeNumber($number2);
|
2344 |
+
|
2345 |
+
return $number1[2] == $number2[2] || $number1[2] == "" || $number2[2] == "";
|
2346 |
+
}
|
2347 |
+
|
2348 |
+
/**
|
2349 |
+
* Workaround IE7's content counter bug.
|
2350 |
+
*
|
2351 |
+
* @param array $args
|
2352 |
+
*/
|
2353 |
+
protected function lib_counter($args) {
|
2354 |
+
$list = array_map(array($this, 'compileValue'), $args);
|
2355 |
+
return array('string', '', array('counter(' . implode(',', $list) . ')'));
|
2356 |
+
}
|
2357 |
+
|
2358 |
+
protected function throwError($msg = null) {
|
2359 |
+
if (func_num_args() > 1) {
|
2360 |
+
$msg = call_user_func_array("sprintf", func_get_args());
|
2361 |
+
}
|
2362 |
+
|
2363 |
+
if ($this->sourcePos >= 0 && isset($this->sourceParser)) {
|
2364 |
+
$this->sourceParser->throwParseError($msg, $this->sourcePos);
|
2365 |
+
}
|
2366 |
+
|
2367 |
+
throw new Exception($msg);
|
2368 |
+
}
|
2369 |
+
|
2370 |
+
/**
|
2371 |
+
* CSS Colors
|
2372 |
+
*
|
2373 |
+
* @see http://www.w3.org/TR/css3-color
|
2374 |
+
*/
|
2375 |
+
static protected $cssColors = array(
|
2376 |
+
'aliceblue' => '240,248,255',
|
2377 |
+
'antiquewhite' => '250,235,215',
|
2378 |
+
'aqua' => '0,255,255',
|
2379 |
+
'aquamarine' => '127,255,212',
|
2380 |
+
'azure' => '240,255,255',
|
2381 |
+
'beige' => '245,245,220',
|
2382 |
+
'bisque' => '255,228,196',
|
2383 |
+
'black' => '0,0,0',
|
2384 |
+
'blanchedalmond' => '255,235,205',
|
2385 |
+
'blue' => '0,0,255',
|
2386 |
+
'blueviolet' => '138,43,226',
|
2387 |
+
'brown' => '165,42,42',
|
2388 |
+
'burlywood' => '222,184,135',
|
2389 |
+
'cadetblue' => '95,158,160',
|
2390 |
+
'chartreuse' => '127,255,0',
|
2391 |
+
'chocolate' => '210,105,30',
|
2392 |
+
'coral' => '255,127,80',
|
2393 |
+
'cornflowerblue' => '100,149,237',
|
2394 |
+
'cornsilk' => '255,248,220',
|
2395 |
+
'crimson' => '220,20,60',
|
2396 |
+
'cyan' => '0,255,255',
|
2397 |
+
'darkblue' => '0,0,139',
|
2398 |
+
'darkcyan' => '0,139,139',
|
2399 |
+
'darkgoldenrod' => '184,134,11',
|
2400 |
+
'darkgray' => '169,169,169',
|
2401 |
+
'darkgreen' => '0,100,0',
|
2402 |
+
'darkgrey' => '169,169,169',
|
2403 |
+
'darkkhaki' => '189,183,107',
|
2404 |
+
'darkmagenta' => '139,0,139',
|
2405 |
+
'darkolivegreen' => '85,107,47',
|
2406 |
+
'darkorange' => '255,140,0',
|
2407 |
+
'darkorchid' => '153,50,204',
|
2408 |
+
'darkred' => '139,0,0',
|
2409 |
+
'darksalmon' => '233,150,122',
|
2410 |
+
'darkseagreen' => '143,188,143',
|
2411 |
+
'darkslateblue' => '72,61,139',
|
2412 |
+
'darkslategray' => '47,79,79',
|
2413 |
+
'darkslategrey' => '47,79,79',
|
2414 |
+
'darkturquoise' => '0,206,209',
|
2415 |
+
'darkviolet' => '148,0,211',
|
2416 |
+
'deeppink' => '255,20,147',
|
2417 |
+
'deepskyblue' => '0,191,255',
|
2418 |
+
'dimgray' => '105,105,105',
|
2419 |
+
'dimgrey' => '105,105,105',
|
2420 |
+
'dodgerblue' => '30,144,255',
|
2421 |
+
'firebrick' => '178,34,34',
|
2422 |
+
'floralwhite' => '255,250,240',
|
2423 |
+
'forestgreen' => '34,139,34',
|
2424 |
+
'fuchsia' => '255,0,255',
|
2425 |
+
'gainsboro' => '220,220,220',
|
2426 |
+
'ghostwhite' => '248,248,255',
|
2427 |
+
'gold' => '255,215,0',
|
2428 |
+
'goldenrod' => '218,165,32',
|
2429 |
+
'gray' => '128,128,128',
|
2430 |
+
'green' => '0,128,0',
|
2431 |
+
'greenyellow' => '173,255,47',
|
2432 |
+
'grey' => '128,128,128',
|
2433 |
+
'honeydew' => '240,255,240',
|
2434 |
+
'hotpink' => '255,105,180',
|
2435 |
+
'indianred' => '205,92,92',
|
2436 |
+
'indigo' => '75,0,130',
|
2437 |
+
'ivory' => '255,255,240',
|
2438 |
+
'khaki' => '240,230,140',
|
2439 |
+
'lavender' => '230,230,250',
|
2440 |
+
'lavenderblush' => '255,240,245',
|
2441 |
+
'lawngreen' => '124,252,0',
|
2442 |
+
'lemonchiffon' => '255,250,205',
|
2443 |
+
'lightblue' => '173,216,230',
|
2444 |
+
'lightcoral' => '240,128,128',
|
2445 |
+
'lightcyan' => '224,255,255',
|
2446 |
+
'lightgoldenrodyellow' => '250,250,210',
|
2447 |
+
'lightgray' => '211,211,211',
|
2448 |
+
'lightgreen' => '144,238,144',
|
2449 |
+
'lightgrey' => '211,211,211',
|
2450 |
+
'lightpink' => '255,182,193',
|
2451 |
+
'lightsalmon' => '255,160,122',
|
2452 |
+
'lightseagreen' => '32,178,170',
|
2453 |
+
'lightskyblue' => '135,206,250',
|
2454 |
+
'lightslategray' => '119,136,153',
|
2455 |
+
'lightslategrey' => '119,136,153',
|
2456 |
+
'lightsteelblue' => '176,196,222',
|
2457 |
+
'lightyellow' => '255,255,224',
|
2458 |
+
'lime' => '0,255,0',
|
2459 |
+
'limegreen' => '50,205,50',
|
2460 |
+
'linen' => '250,240,230',
|
2461 |
+
'magenta' => '255,0,255',
|
2462 |
+
'maroon' => '128,0,0',
|
2463 |
+
'mediumaquamarine' => '102,205,170',
|
2464 |
+
'mediumblue' => '0,0,205',
|
2465 |
+
'mediumorchid' => '186,85,211',
|
2466 |
+
'mediumpurple' => '147,112,219',
|
2467 |
+
'mediumseagreen' => '60,179,113',
|
2468 |
+
'mediumslateblue' => '123,104,238',
|
2469 |
+
'mediumspringgreen' => '0,250,154',
|
2470 |
+
'mediumturquoise' => '72,209,204',
|
2471 |
+
'mediumvioletred' => '199,21,133',
|
2472 |
+
'midnightblue' => '25,25,112',
|
2473 |
+
'mintcream' => '245,255,250',
|
2474 |
+
'mistyrose' => '255,228,225',
|
2475 |
+
'moccasin' => '255,228,181',
|
2476 |
+
'navajowhite' => '255,222,173',
|
2477 |
+
'navy' => '0,0,128',
|
2478 |
+
'oldlace' => '253,245,230',
|
2479 |
+
'olive' => '128,128,0',
|
2480 |
+
'olivedrab' => '107,142,35',
|
2481 |
+
'orange' => '255,165,0',
|
2482 |
+
'orangered' => '255,69,0',
|
2483 |
+
'orchid' => '218,112,214',
|
2484 |
+
'palegoldenrod' => '238,232,170',
|
2485 |
+
'palegreen' => '152,251,152',
|
2486 |
+
'paleturquoise' => '175,238,238',
|
2487 |
+
'palevioletred' => '219,112,147',
|
2488 |
+
'papayawhip' => '255,239,213',
|
2489 |
+
'peachpuff' => '255,218,185',
|
2490 |
+
'peru' => '205,133,63',
|
2491 |
+
'pink' => '255,192,203',
|
2492 |
+
'plum' => '221,160,221',
|
2493 |
+
'powderblue' => '176,224,230',
|
2494 |
+
'purple' => '128,0,128',
|
2495 |
+
'red' => '255,0,0',
|
2496 |
+
'rosybrown' => '188,143,143',
|
2497 |
+
'royalblue' => '65,105,225',
|
2498 |
+
'saddlebrown' => '139,69,19',
|
2499 |
+
'salmon' => '250,128,114',
|
2500 |
+
'sandybrown' => '244,164,96',
|
2501 |
+
'seagreen' => '46,139,87',
|
2502 |
+
'seashell' => '255,245,238',
|
2503 |
+
'sienna' => '160,82,45',
|
2504 |
+
'silver' => '192,192,192',
|
2505 |
+
'skyblue' => '135,206,235',
|
2506 |
+
'slateblue' => '106,90,205',
|
2507 |
+
'slategray' => '112,128,144',
|
2508 |
+
'slategrey' => '112,128,144',
|
2509 |
+
'snow' => '255,250,250',
|
2510 |
+
'springgreen' => '0,255,127',
|
2511 |
+
'steelblue' => '70,130,180',
|
2512 |
+
'tan' => '210,180,140',
|
2513 |
+
'teal' => '0,128,128',
|
2514 |
+
'thistle' => '216,191,216',
|
2515 |
+
'tomato' => '255,99,71',
|
2516 |
+
'transparent' => '0,0,0,0',
|
2517 |
+
'turquoise' => '64,224,208',
|
2518 |
+
'violet' => '238,130,238',
|
2519 |
+
'wheat' => '245,222,179',
|
2520 |
+
'white' => '255,255,255',
|
2521 |
+
'whitesmoke' => '245,245,245',
|
2522 |
+
'yellow' => '255,255,0',
|
2523 |
+
'yellowgreen' => '154,205,50'
|
2524 |
+
);
|
2525 |
+
}
|
2526 |
+
|
2527 |
+
/**
|
2528 |
+
* SCSS parser
|
2529 |
+
*
|
2530 |
+
* @author Leaf Corcoran <leafot@gmail.com>
|
2531 |
+
*/
|
2532 |
+
class scss_parser {
|
2533 |
+
static protected $precedence = array(
|
2534 |
+
"or" => 0,
|
2535 |
+
"and" => 1,
|
2536 |
+
|
2537 |
+
'==' => 2,
|
2538 |
+
'!=' => 2,
|
2539 |
+
'<=' => 2,
|
2540 |
+
'>=' => 2,
|
2541 |
+
'=' => 2,
|
2542 |
+
'<' => 3,
|
2543 |
+
'>' => 2,
|
2544 |
+
|
2545 |
+
'+' => 3,
|
2546 |
+
'-' => 3,
|
2547 |
+
'*' => 4,
|
2548 |
+
'/' => 4,
|
2549 |
+
'%' => 4,
|
2550 |
+
);
|
2551 |
+
|
2552 |
+
static protected $operators = array("+", "-", "*", "/", "%",
|
2553 |
+
"==", "!=", "<=", ">=", "<", ">", "and", "or");
|
2554 |
+
|
2555 |
+
static protected $operatorStr;
|
2556 |
+
static protected $whitePattern;
|
2557 |
+
static protected $commentMulti;
|
2558 |
+
|
2559 |
+
static protected $commentSingle = "//";
|
2560 |
+
static protected $commentMultiLeft = "/*";
|
2561 |
+
static protected $commentMultiRight = "*/";
|
2562 |
+
|
2563 |
+
function __construct($sourceName = null, $rootParser = true) {
|
2564 |
+
$this->sourceName = $sourceName;
|
2565 |
+
$this->rootParser = $rootParser;
|
2566 |
+
|
2567 |
+
if (empty(self::$operatorStr)) {
|
2568 |
+
self::$operatorStr = $this->makeOperatorStr(self::$operators);
|
2569 |
+
|
2570 |
+
$commentSingle = $this->preg_quote(self::$commentSingle);
|
2571 |
+
$commentMultiLeft = $this->preg_quote(self::$commentMultiLeft);
|
2572 |
+
$commentMultiRight = $this->preg_quote(self::$commentMultiRight);
|
2573 |
+
self::$commentMulti = $commentMultiLeft.'.*?'.$commentMultiRight;
|
2574 |
+
self::$whitePattern = '/'.$commentSingle.'[^\n]*\s*|('.self::$commentMulti.')\s*|\s+/Ais';
|
2575 |
+
}
|
2576 |
+
}
|
2577 |
+
|
2578 |
+
static protected function makeOperatorStr($operators) {
|
2579 |
+
return '('.implode('|', array_map(array('scss_parser','preg_quote'),
|
2580 |
+
$operators)).')';
|
2581 |
+
}
|
2582 |
+
|
2583 |
+
function parse($buffer) {
|
2584 |
+
$this->count = 0;
|
2585 |
+
$this->env = null;
|
2586 |
+
$this->inParens = false;
|
2587 |
+
$this->pushBlock(null); // root block
|
2588 |
+
$this->eatWhiteDefault = true;
|
2589 |
+
$this->insertComments = true;
|
2590 |
+
|
2591 |
+
$this->buffer = $buffer;
|
2592 |
+
|
2593 |
+
$this->whitespace();
|
2594 |
+
while (false !== $this->parseChunk());
|
2595 |
+
|
2596 |
+
if ($this->count != strlen($this->buffer))
|
2597 |
+
$this->throwParseError();
|
2598 |
+
|
2599 |
+
if (!empty($this->env->parent)) {
|
2600 |
+
$this->throwParseError("unclosed block");
|
2601 |
+
}
|
2602 |
+
|
2603 |
+
$this->env->isRoot = true;
|
2604 |
+
return $this->env;
|
2605 |
+
}
|
2606 |
+
|
2607 |
+
/**
|
2608 |
+
* Parse a single chunk off the head of the buffer and append it to the
|
2609 |
+
* current parse environment.
|
2610 |
+
*
|
2611 |
+
* Returns false when the buffer is empty, or when there is an error.
|
2612 |
+
*
|
2613 |
+
* This function is called repeatedly until the entire document is
|
2614 |
+
* parsed.
|
2615 |
+
*
|
2616 |
+
* This parser is most similar to a recursive descent parser. Single
|
2617 |
+
* functions represent discrete grammatical rules for the language, and
|
2618 |
+
* they are able to capture the text that represents those rules.
|
2619 |
+
*
|
2620 |
+
* Consider the function scssc::keyword(). (All parse functions are
|
2621 |
+
* structured the same.)
|
2622 |
+
*
|
2623 |
+
* The function takes a single reference argument. When calling the
|
2624 |
+
* function it will attempt to match a keyword on the head of the buffer.
|
2625 |
+
* If it is successful, it will place the keyword in the referenced
|
2626 |
+
* argument, advance the position in the buffer, and return true. If it
|
2627 |
+
* fails then it won't advance the buffer and it will return false.
|
2628 |
+
*
|
2629 |
+
* All of these parse functions are powered by scssc::match(), which behaves
|
2630 |
+
* the same way, but takes a literal regular expression. Sometimes it is
|
2631 |
+
* more convenient to use match instead of creating a new function.
|
2632 |
+
*
|
2633 |
+
* Because of the format of the functions, to parse an entire string of
|
2634 |
+
* grammatical rules, you can chain them together using &&.
|
2635 |
+
*
|
2636 |
+
* But, if some of the rules in the chain succeed before one fails, then
|
2637 |
+
* the buffer position will be left at an invalid state. In order to
|
2638 |
+
* avoid this, scssc::seek() is used to remember and set buffer positions.
|
2639 |
+
*
|
2640 |
+
* Before parsing a chain, use $s = $this->seek() to remember the current
|
2641 |
+
* position into $s. Then if a chain fails, use $this->seek($s) to
|
2642 |
+
* go back where we started.
|
2643 |
+
*
|
2644 |
+
* @return boolean
|
2645 |
+
*/
|
2646 |
+
protected function parseChunk() {
|
2647 |
+
$s = $this->seek();
|
2648 |
+
|
2649 |
+
// the directives
|
2650 |
+
if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "@") {
|
2651 |
+
if ($this->literal("@media") && $this->mediaQueryList($mediaQueryList) && $this->literal("{")) {
|
2652 |
+
$media = $this->pushSpecialBlock("media");
|
2653 |
+
$media->queryList = $mediaQueryList[2];
|
2654 |
+
return true;
|
2655 |
+
} else {
|
2656 |
+
$this->seek($s);
|
2657 |
+
}
|
2658 |
+
|
2659 |
+
if ($this->literal("@mixin") &&
|
2660 |
+
$this->keyword($mixinName) &&
|
2661 |
+
($this->argumentDef($args) || true) &&
|
2662 |
+
$this->literal("{"))
|
2663 |
+
{
|
2664 |
+
$mixin = $this->pushSpecialBlock("mixin");
|
2665 |
+
$mixin->name = $mixinName;
|
2666 |
+
$mixin->args = $args;
|
2667 |
+
return true;
|
2668 |
+
} else {
|
2669 |
+
$this->seek($s);
|
2670 |
+
}
|
2671 |
+
|
2672 |
+
if ($this->literal("@include") &&
|
2673 |
+
$this->keyword($mixinName) &&
|
2674 |
+
($this->literal("(") &&
|
2675 |
+
($this->argValues($argValues) || true) &&
|
2676 |
+
$this->literal(")") || true) &&
|
2677 |
+
($this->end() ||
|
2678 |
+
$this->literal("{") && $hasBlock = true))
|
2679 |
+
{
|
2680 |
+
$child = array("include",
|
2681 |
+
$mixinName, isset($argValues) ? $argValues : null, null);
|
2682 |
+
|
2683 |
+
if (!empty($hasBlock)) {
|
2684 |
+
$include = $this->pushSpecialBlock("include");
|
2685 |
+
$include->child = $child;
|
2686 |
+
} else {
|
2687 |
+
$this->append($child, $s);
|
2688 |
+
}
|
2689 |
+
|
2690 |
+
return true;
|
2691 |
+
} else {
|
2692 |
+
$this->seek($s);
|
2693 |
+
}
|
2694 |
+
|
2695 |
+
if ($this->literal("@import") &&
|
2696 |
+
$this->valueList($importPath) &&
|
2697 |
+
$this->end())
|
2698 |
+
{
|
2699 |
+
$this->append(array("import", $importPath), $s);
|
2700 |
+
return true;
|
2701 |
+
} else {
|
2702 |
+
$this->seek($s);
|
2703 |
+
}
|
2704 |
+
|
2705 |
+
if ($this->literal("@extend") &&
|
2706 |
+
$this->selectors($selector) &&
|
2707 |
+
$this->end())
|
2708 |
+
{
|
2709 |
+
$this->append(array("extend", $selector), $s);
|
2710 |
+
return true;
|
2711 |
+
} else {
|
2712 |
+
$this->seek($s);
|
2713 |
+
}
|
2714 |
+
|
2715 |
+
if ($this->literal("@function") &&
|
2716 |
+
$this->keyword($fnName) &&
|
2717 |
+
$this->argumentDef($args) &&
|
2718 |
+
$this->literal("{"))
|
2719 |
+
{
|
2720 |
+
$func = $this->pushSpecialBlock("function");
|
2721 |
+
$func->name = $fnName;
|
2722 |
+
$func->args = $args;
|
2723 |
+
return true;
|
2724 |
+
} else {
|
2725 |
+
$this->seek($s);
|
2726 |
+
}
|
2727 |
+
|
2728 |
+
if ($this->literal("@return") && $this->valueList($retVal) && $this->end()) {
|
2729 |
+
$this->append(array("return", $retVal), $s);
|
2730 |
+
return true;
|
2731 |
+
} else {
|
2732 |
+
$this->seek($s);
|
2733 |
+
}
|
2734 |
+
|
2735 |
+
if ($this->literal("@each") &&
|
2736 |
+
$this->variable($varName) &&
|
2737 |
+
$this->literal("in") &&
|
2738 |
+
$this->valueList($list) &&
|
2739 |
+
$this->literal("{"))
|
2740 |
+
{
|
2741 |
+
$each = $this->pushSpecialBlock("each");
|
2742 |
+
$each->var = $varName[1];
|
2743 |
+
$each->list = $list;
|
2744 |
+
return true;
|
2745 |
+
} else {
|
2746 |
+
$this->seek($s);
|
2747 |
+
}
|
2748 |
+
|
2749 |
+
if ($this->literal("@while") &&
|
2750 |
+
$this->expression($cond) &&
|
2751 |
+
$this->literal("{"))
|
2752 |
+
{
|
2753 |
+
$while = $this->pushSpecialBlock("while");
|
2754 |
+
$while->cond = $cond;
|
2755 |
+
return true;
|
2756 |
+
} else {
|
2757 |
+
$this->seek($s);
|
2758 |
+
}
|
2759 |
+
|
2760 |
+
if ($this->literal("@for") &&
|
2761 |
+
$this->variable($varName) &&
|
2762 |
+
$this->literal("from") &&
|
2763 |
+
$this->expression($start) &&
|
2764 |
+
($this->literal("through") ||
|
2765 |
+
($forUntil = true && $this->literal("to"))) &&
|
2766 |
+
$this->expression($end) &&
|
2767 |
+
$this->literal("{"))
|
2768 |
+
{
|
2769 |
+
$for = $this->pushSpecialBlock("for");
|
2770 |
+
$for->var = $varName[1];
|
2771 |
+
$for->start = $start;
|
2772 |
+
$for->end = $end;
|
2773 |
+
$for->until = isset($forUntil);
|
2774 |
+
return true;
|
2775 |
+
} else {
|
2776 |
+
$this->seek($s);
|
2777 |
+
}
|
2778 |
+
|
2779 |
+
if ($this->literal("@if") && $this->valueList($cond) && $this->literal("{")) {
|
2780 |
+
$if = $this->pushSpecialBlock("if");
|
2781 |
+
$if->cond = $cond;
|
2782 |
+
$if->cases = array();
|
2783 |
+
return true;
|
2784 |
+
} else {
|
2785 |
+
$this->seek($s);
|
2786 |
+
}
|
2787 |
+
|
2788 |
+
if (($this->literal("@debug") || $this->literal("@warn")) &&
|
2789 |
+
$this->valueList($value) &&
|
2790 |
+
$this->end()) {
|
2791 |
+
$this->append(array("debug", $value, $s), $s);
|
2792 |
+
return true;
|
2793 |
+
} else {
|
2794 |
+
$this->seek($s);
|
2795 |
+
}
|
2796 |
+
|
2797 |
+
if ($this->literal("@content") && $this->end()) {
|
2798 |
+
$this->append(array("mixin_content"), $s);
|
2799 |
+
return true;
|
2800 |
+
} else {
|
2801 |
+
$this->seek($s);
|
2802 |
+
}
|
2803 |
+
|
2804 |
+
$last = $this->last();
|
2805 |
+
if (!is_null($last) && $last[0] == "if") {
|
2806 |
+
list(, $if) = $last;
|
2807 |
+
if ($this->literal("@else")) {
|
2808 |
+
if ($this->literal("{")) {
|
2809 |
+
$else = $this->pushSpecialBlock("else");
|
2810 |
+
} elseif ($this->literal("if") && $this->valueList($cond) && $this->literal("{")) {
|
2811 |
+
$else = $this->pushSpecialBlock("elseif");
|
2812 |
+
$else->cond = $cond;
|
2813 |
+
}
|
2814 |
+
|
2815 |
+
if (isset($else)) {
|
2816 |
+
$else->dontAppend = true;
|
2817 |
+
$if->cases[] = $else;
|
2818 |
+
return true;
|
2819 |
+
}
|
2820 |
+
}
|
2821 |
+
|
2822 |
+
$this->seek($s);
|
2823 |
+
}
|
2824 |
+
|
2825 |
+
if ($this->literal("@charset") &&
|
2826 |
+
$this->valueList($charset) && $this->end())
|
2827 |
+
{
|
2828 |
+
$this->append(array("charset", $charset), $s);
|
2829 |
+
return true;
|
2830 |
+
} else {
|
2831 |
+
$this->seek($s);
|
2832 |
+
}
|
2833 |
+
|
2834 |
+
// doesn't match built in directive, do generic one
|
2835 |
+
if ($this->literal("@", false) && $this->keyword($dirName) &&
|
2836 |
+
($this->openString("{", $dirValue) || true) &&
|
2837 |
+
$this->literal("{"))
|
2838 |
+
{
|
2839 |
+
$directive = $this->pushSpecialBlock("directive");
|
2840 |
+
$directive->name = $dirName;
|
2841 |
+
if (isset($dirValue)) $directive->value = $dirValue;
|
2842 |
+
return true;
|
2843 |
+
}
|
2844 |
+
|
2845 |
+
$this->seek($s);
|
2846 |
+
return false;
|
2847 |
+
}
|
2848 |
+
|
2849 |
+
// property shortcut
|
2850 |
+
// captures most properties before having to parse a selector
|
2851 |
+
if ($this->keyword($name, false) &&
|
2852 |
+
$this->literal(": ") &&
|
2853 |
+
$this->valueList($value) &&
|
2854 |
+
$this->end())
|
2855 |
+
{
|
2856 |
+
$name = array("string", "", array($name));
|
2857 |
+
$this->append(array("assign", $name, $value), $s);
|
2858 |
+
return true;
|
2859 |
+
} else {
|
2860 |
+
$this->seek($s);
|
2861 |
+
}
|
2862 |
+
|
2863 |
+
// variable assigns
|
2864 |
+
if ($this->variable($name) &&
|
2865 |
+
$this->literal(":") &&
|
2866 |
+
$this->valueList($value) && $this->end())
|
2867 |
+
{
|
2868 |
+
// check for !default
|
2869 |
+
$defaultVar = $value[0] == "list" && $this->stripDefault($value);
|
2870 |
+
$this->append(array("assign", $name, $value, $defaultVar), $s);
|
2871 |
+
return true;
|
2872 |
+
} else {
|
2873 |
+
$this->seek($s);
|
2874 |
+
}
|
2875 |
+
|
2876 |
+
// misc
|
2877 |
+
if ($this->literal("-->")) {
|
2878 |
+
return true;
|
2879 |
+
}
|
2880 |
+
|
2881 |
+
// opening css block
|
2882 |
+
$oldComments = $this->insertComments;
|
2883 |
+
$this->insertComments = false;
|
2884 |
+
if ($this->selectors($selectors) && $this->literal("{")) {
|
2885 |
+
$this->pushBlock($selectors);
|
2886 |
+
$this->insertComments = $oldComments;
|
2887 |
+
return true;
|
2888 |
+
} else {
|
2889 |
+
$this->seek($s);
|
2890 |
+
}
|
2891 |
+
$this->insertComments = $oldComments;
|
2892 |
+
|
2893 |
+
// property assign, or nested assign
|
2894 |
+
if ($this->propertyName($name) && $this->literal(":")) {
|
2895 |
+
$foundSomething = false;
|
2896 |
+
if ($this->valueList($value)) {
|
2897 |
+
$this->append(array("assign", $name, $value), $s);
|
2898 |
+
$foundSomething = true;
|
2899 |
+
}
|
2900 |
+
|
2901 |
+
if ($this->literal("{")) {
|
2902 |
+
$propBlock = $this->pushSpecialBlock("nestedprop");
|
2903 |
+
$propBlock->prefix = $name;
|
2904 |
+
$foundSomething = true;
|
2905 |
+
} elseif ($foundSomething) {
|
2906 |
+
$foundSomething = $this->end();
|
2907 |
+
}
|
2908 |
+
|
2909 |
+
if ($foundSomething) {
|
2910 |
+
return true;
|
2911 |
+
}
|
2912 |
+
|
2913 |
+
$this->seek($s);
|
2914 |
+
} else {
|
2915 |
+
$this->seek($s);
|
2916 |
+
}
|
2917 |
+
|
2918 |
+
// closing a block
|
2919 |
+
if ($this->literal("}")) {
|
2920 |
+
$block = $this->popBlock();
|
2921 |
+
if (isset($block->type) && $block->type == "include") {
|
2922 |
+
$include = $block->child;
|
2923 |
+
unset($block->child);
|
2924 |
+
$include[3] = $block;
|
2925 |
+
$this->append($include, $s);
|
2926 |
+
} elseif (empty($block->dontAppend)) {
|
2927 |
+
$type = isset($block->type) ? $block->type : "block";
|
2928 |
+
$this->append(array($type, $block), $s);
|
2929 |
+
}
|
2930 |
+
return true;
|
2931 |
+
}
|
2932 |
+
|
2933 |
+
// extra stuff
|
2934 |
+
if ($this->literal(";") ||
|
2935 |
+
$this->literal("<!--"))
|
2936 |
+
{
|
2937 |
+
return true;
|
2938 |
+
}
|
2939 |
+
|
2940 |
+
return false;
|
2941 |
+
}
|
2942 |
+
|
2943 |
+
protected function stripDefault(&$value) {
|
2944 |
+
$def = end($value[2]);
|
2945 |
+
if ($def[0] == "keyword" && $def[1] == "!default") {
|
2946 |
+
array_pop($value[2]);
|
2947 |
+
$value = $this->flattenList($value);
|
2948 |
+
return true;
|
2949 |
+
}
|
2950 |
+
|
2951 |
+
if ($def[0] == "list") {
|
2952 |
+
return $this->stripDefault($value[2][count($value[2]) - 1]);
|
2953 |
+
}
|
2954 |
+
|
2955 |
+
return false;
|
2956 |
+
}
|
2957 |
+
|
2958 |
+
protected function literal($what, $eatWhitespace = null) {
|
2959 |
+
if (is_null($eatWhitespace)) $eatWhitespace = $this->eatWhiteDefault;
|
2960 |
+
|
2961 |
+
// shortcut on single letter
|
2962 |
+
if (!isset($what[1]) && isset($this->buffer[$this->count])) {
|
2963 |
+
if ($this->buffer[$this->count] == $what) {
|
2964 |
+
if (!$eatWhitespace) {
|
2965 |
+
$this->count++;
|
2966 |
+
return true;
|
2967 |
+
}
|
2968 |
+
// goes below...
|
2969 |
+
} else {
|
2970 |
+
return false;
|
2971 |
+
}
|
2972 |
+
}
|
2973 |
+
|
2974 |
+
return $this->match($this->preg_quote($what), $m, $eatWhitespace);
|
2975 |
+
}
|
2976 |
+
|
2977 |
+
// tree builders
|
2978 |
+
|
2979 |
+
protected function pushBlock($selectors) {
|
2980 |
+
$b = new stdClass;
|
2981 |
+
$b->parent = $this->env; // not sure if we need this yet
|
2982 |
+
|
2983 |
+
$b->selectors = $selectors;
|
2984 |
+
$b->children = array();
|
2985 |
+
|
2986 |
+
$this->env = $b;
|
2987 |
+
return $b;
|
2988 |
+
}
|
2989 |
+
|
2990 |
+
protected function pushSpecialBlock($type) {
|
2991 |
+
$block = $this->pushBlock(null);
|
2992 |
+
$block->type = $type;
|
2993 |
+
return $block;
|
2994 |
+
}
|
2995 |
+
|
2996 |
+
protected function popBlock() {
|
2997 |
+
if (empty($this->env->parent)) {
|
2998 |
+
$this->throwParseError("unexpected }");
|
2999 |
+
}
|
3000 |
+
|
3001 |
+
$old = $this->env;
|
3002 |
+
$this->env = $this->env->parent;
|
3003 |
+
unset($old->parent);
|
3004 |
+
return $old;
|
3005 |
+
}
|
3006 |
+
|
3007 |
+
protected function append($statement, $pos=null) {
|
3008 |
+
if ($pos !== null) {
|
3009 |
+
$statement[-1] = $pos;
|
3010 |
+
if (!$this->rootParser) $statement[-2] = $this;
|
3011 |
+
}
|
3012 |
+
$this->env->children[] = $statement;
|
3013 |
+
}
|
3014 |
+
|
3015 |
+
// last child that was appended
|
3016 |
+
protected function last() {
|
3017 |
+
$i = count($this->env->children) - 1;
|
3018 |
+
if (isset($this->env->children[$i]))
|
3019 |
+
return $this->env->children[$i];
|
3020 |
+
}
|
3021 |
+
|
3022 |
+
// high level parsers (they return parts of ast)
|
3023 |
+
|
3024 |
+
protected function mediaQueryList(&$out) {
|
3025 |
+
return $this->genericList($out, "mediaQuery", ",", false);
|
3026 |
+
}
|
3027 |
+
|
3028 |
+
protected function mediaQuery(&$out) {
|
3029 |
+
$s = $this->seek();
|
3030 |
+
|
3031 |
+
$expressions = null;
|
3032 |
+
$parts = array();
|
3033 |
+
|
3034 |
+
if (($this->literal("only") && ($only = true) || $this->literal("not") && ($not = true) || true) && $this->mixedKeyword($mediaType)) {
|
3035 |
+
$prop = array("mediaType");
|
3036 |
+
if (isset($only)) $prop[] = array("keyword", "only");
|
3037 |
+
if (isset($not)) $prop[] = array("keyword", "not");
|
3038 |
+
$media = array("list", "", array());
|
3039 |
+
foreach ((array)$mediaType as $type) {
|
3040 |
+
if (is_array($type)) {
|
3041 |
+
$media[2][] = $type;
|
3042 |
+
} else {
|
3043 |
+
$media[2][] = array("keyword", $type);
|
3044 |
+
}
|
3045 |
+
}
|
3046 |
+
$prop[] = $media;
|
3047 |
+
$parts[] = $prop;
|
3048 |
+
}
|
3049 |
+
|
3050 |
+
if (empty($parts) || $this->literal("and")) {
|
3051 |
+
$this->genericList($expressions, "mediaExpression", "and", false);
|
3052 |
+
if (is_array($expressions)) $parts = array_merge($parts, $expressions[2]);
|
3053 |
+
}
|
3054 |
+
|
3055 |
+
$out = $parts;
|
3056 |
+
return true;
|
3057 |
+
}
|
3058 |
+
|
3059 |
+
protected function mediaExpression(&$out) {
|
3060 |
+
$s = $this->seek();
|
3061 |
+
$value = null;
|
3062 |
+
if ($this->literal("(") &&
|
3063 |
+
$this->expression($feature) &&
|
3064 |
+
($this->literal(":") && $this->expression($value) || true) &&
|
3065 |
+
$this->literal(")"))
|
3066 |
+
{
|
3067 |
+
$out = array("mediaExp", $feature);
|
3068 |
+
if ($value) $out[] = $value;
|
3069 |
+
return true;
|
3070 |
+
}
|
3071 |
+
|
3072 |
+
$this->seek($s);
|
3073 |
+
return false;
|
3074 |
+
}
|
3075 |
+
|
3076 |
+
protected function argValues(&$out) {
|
3077 |
+
if ($this->genericList($list, "argValue", ",", false)) {
|
3078 |
+
$out = $list[2];
|
3079 |
+
return true;
|
3080 |
+
}
|
3081 |
+
return false;
|
3082 |
+
}
|
3083 |
+
|
3084 |
+
protected function argValue(&$out) {
|
3085 |
+
$s = $this->seek();
|
3086 |
+
|
3087 |
+
$keyword = null;
|
3088 |
+
if (!$this->variable($keyword) || !$this->literal(":")) {
|
3089 |
+
$this->seek($s);
|
3090 |
+
$keyword = null;
|
3091 |
+
}
|
3092 |
+
|
3093 |
+
if ($this->genericList($value, "expression")) {
|
3094 |
+
$out = array($keyword, $value, false);
|
3095 |
+
$s = $this->seek();
|
3096 |
+
if ($this->literal("...")) {
|
3097 |
+
$out[2] = true;
|
3098 |
+
} else {
|
3099 |
+
$this->seek($s);
|
3100 |
+
}
|
3101 |
+
return true;
|
3102 |
+
}
|
3103 |
+
|
3104 |
+
return false;
|
3105 |
+
}
|
3106 |
+
|
3107 |
+
|
3108 |
+
protected function valueList(&$out) {
|
3109 |
+
return $this->genericList($out, "spaceList", ",");
|
3110 |
+
}
|
3111 |
+
|
3112 |
+
protected function spaceList(&$out) {
|
3113 |
+
return $this->genericList($out, "expression");
|
3114 |
+
}
|
3115 |
+
|
3116 |
+
protected function genericList(&$out, $parseItem, $delim="", $flatten=true) {
|
3117 |
+
$s = $this->seek();
|
3118 |
+
$items = array();
|
3119 |
+
while ($this->$parseItem($value)) {
|
3120 |
+
$items[] = $value;
|
3121 |
+
if ($delim) {
|
3122 |
+
if (!$this->literal($delim)) break;
|
3123 |
+
}
|
3124 |
+
}
|
3125 |
+
|
3126 |
+
if (count($items) == 0) {
|
3127 |
+
$this->seek($s);
|
3128 |
+
return false;
|
3129 |
+
}
|
3130 |
+
|
3131 |
+
if ($flatten && count($items) == 1) {
|
3132 |
+
$out = $items[0];
|
3133 |
+
} else {
|
3134 |
+
$out = array("list", $delim, $items);
|
3135 |
+
}
|
3136 |
+
|
3137 |
+
return true;
|
3138 |
+
}
|
3139 |
+
|
3140 |
+
protected function expression(&$out) {
|
3141 |
+
$s = $this->seek();
|
3142 |
+
|
3143 |
+
if ($this->literal("(")) {
|
3144 |
+
if ($this->literal(")")) {
|
3145 |
+
$out = array("list", "", array());
|
3146 |
+
return true;
|
3147 |
+
}
|
3148 |
+
|
3149 |
+
if ($this->valueList($out) && $this->literal(')') && $out[0] == "list") {
|
3150 |
+
return true;
|
3151 |
+
}
|
3152 |
+
|
3153 |
+
$this->seek($s);
|
3154 |
+
}
|
3155 |
+
|
3156 |
+
if ($this->value($lhs)) {
|
3157 |
+
$out = $this->expHelper($lhs, 0);
|
3158 |
+
return true;
|
3159 |
+
}
|
3160 |
+
|
3161 |
+
return false;
|
3162 |
+
}
|
3163 |
+
|
3164 |
+
protected function expHelper($lhs, $minP) {
|
3165 |
+
$opstr = self::$operatorStr;
|
3166 |
+
|
3167 |
+
$ss = $this->seek();
|
3168 |
+
$whiteBefore = isset($this->buffer[$this->count - 1]) &&
|
3169 |
+
ctype_space($this->buffer[$this->count - 1]);
|
3170 |
+
while ($this->match($opstr, $m) && self::$precedence[$m[1]] >= $minP) {
|
3171 |
+
$whiteAfter = isset($this->buffer[$this->count - 1]) &&
|
3172 |
+
ctype_space($this->buffer[$this->count - 1]);
|
3173 |
+
|
3174 |
+
$op = $m[1];
|
3175 |
+
|
3176 |
+
// don't turn negative numbers into expressions
|
3177 |
+
if ($op == "-" && $whiteBefore) {
|
3178 |
+
if (!$whiteAfter) break;
|
3179 |
+
}
|
3180 |
+
|
3181 |
+
if (!$this->value($rhs)) break;
|
3182 |
+
|
3183 |
+
// peek and see if rhs belongs to next operator
|
3184 |
+
if ($this->peek($opstr, $next) && self::$precedence[$next[1]] > self::$precedence[$op]) {
|
3185 |
+
$rhs = $this->expHelper($rhs, self::$precedence[$next[1]]);
|
3186 |
+
}
|
3187 |
+
|
3188 |
+
$lhs = array("exp", $op, $lhs, $rhs, $this->inParens, $whiteBefore, $whiteAfter);
|
3189 |
+
$ss = $this->seek();
|
3190 |
+
$whiteBefore = isset($this->buffer[$this->count - 1]) &&
|
3191 |
+
ctype_space($this->buffer[$this->count - 1]);
|
3192 |
+
}
|
3193 |
+
|
3194 |
+
$this->seek($ss);
|
3195 |
+
return $lhs;
|
3196 |
+
}
|
3197 |
+
|
3198 |
+
protected function value(&$out) {
|
3199 |
+
$s = $this->seek();
|
3200 |
+
|
3201 |
+
if ($this->literal("not", false) && $this->whitespace() && $this->value($inner)) {
|
3202 |
+
$out = array("unary", "not", $inner, $this->inParens);
|
3203 |
+
return true;
|
3204 |
+
} else {
|
3205 |
+
$this->seek($s);
|
3206 |
+
}
|
3207 |
+
|
3208 |
+
if ($this->literal("+") && $this->value($inner)) {
|
3209 |
+
$out = array("unary", "+", $inner, $this->inParens);
|
3210 |
+
return true;
|
3211 |
+
} else {
|
3212 |
+
$this->seek($s);
|
3213 |
+
}
|
3214 |
+
|
3215 |
+
// negation
|
3216 |
+
if ($this->literal("-", false) &&
|
3217 |
+
($this->variable($inner) ||
|
3218 |
+
$this->unit($inner) ||
|
3219 |
+
$this->parenValue($inner)))
|
3220 |
+
{
|
3221 |
+
$out = array("unary", "-", $inner, $this->inParens);
|
3222 |
+
return true;
|
3223 |
+
} else {
|
3224 |
+
$this->seek($s);
|
3225 |
+
}
|
3226 |
+
|
3227 |
+
if ($this->parenValue($out)) return true;
|
3228 |
+
if ($this->interpolation($out)) return true;
|
3229 |
+
if ($this->variable($out)) return true;
|
3230 |
+
if ($this->color($out)) return true;
|
3231 |
+
if ($this->unit($out)) return true;
|
3232 |
+
if ($this->string($out)) return true;
|
3233 |
+
if ($this->func($out)) return true;
|
3234 |
+
if ($this->progid($out)) return true;
|
3235 |
+
|
3236 |
+
if ($this->keyword($keyword)) {
|
3237 |
+
if ($keyword == "null") {
|
3238 |
+
$out = array("null");
|
3239 |
+
} else {
|
3240 |
+
$out = array("keyword", $keyword);
|
3241 |
+
}
|
3242 |
+
return true;
|
3243 |
+
}
|
3244 |
+
|
3245 |
+
return false;
|
3246 |
+
}
|
3247 |
+
|
3248 |
+
// value wrappen in parentheses
|
3249 |
+
protected function parenValue(&$out) {
|
3250 |
+
$s = $this->seek();
|
3251 |
+
|
3252 |
+
$inParens = $this->inParens;
|
3253 |
+
if ($this->literal("(") &&
|
3254 |
+
($this->inParens = true) && $this->expression($exp) &&
|
3255 |
+
$this->literal(")"))
|
3256 |
+
{
|
3257 |
+
$out = $exp;
|
3258 |
+
$this->inParens = $inParens;
|
3259 |
+
return true;
|
3260 |
+
} else {
|
3261 |
+
$this->inParens = $inParens;
|
3262 |
+
$this->seek($s);
|
3263 |
+
}
|
3264 |
+
|
3265 |
+
return false;
|
3266 |
+
}
|
3267 |
+
|
3268 |
+
protected function progid(&$out) {
|
3269 |
+
$s = $this->seek();
|
3270 |
+
if ($this->literal("progid:", false) &&
|
3271 |
+
$this->openString("(", $fn) &&
|
3272 |
+
$this->literal("("))
|
3273 |
+
{
|
3274 |
+
$this->openString(")", $args, "(");
|
3275 |
+
if ($this->literal(")")) {
|
3276 |
+
$out = array("string", "", array(
|
3277 |
+
"progid:", $fn, "(", $args, ")"
|
3278 |
+
));
|
3279 |
+
return true;
|
3280 |
+
}
|
3281 |
+
}
|
3282 |
+
|
3283 |
+
$this->seek($s);
|
3284 |
+
return false;
|
3285 |
+
}
|
3286 |
+
|
3287 |
+
protected function func(&$func) {
|
3288 |
+
$s = $this->seek();
|
3289 |
+
|
3290 |
+
if ($this->keyword($name, false) &&
|
3291 |
+
$this->literal("("))
|
3292 |
+
{
|
3293 |
+
if ($name == "alpha" && $this->argumentList($args)) {
|
3294 |
+
$func = array("function", $name, array("string", "", $args));
|
3295 |
+
return true;
|
3296 |
+
}
|
3297 |
+
|
3298 |
+
if ($name != "expression" && !preg_match("/^(-[a-z]+-)?calc$/", $name)) {
|
3299 |
+
$ss = $this->seek();
|
3300 |
+
if ($this->argValues($args) && $this->literal(")")) {
|
3301 |
+
$func = array("fncall", $name, $args);
|
3302 |
+
return true;
|
3303 |
+
}
|
3304 |
+
$this->seek($ss);
|
3305 |
+
}
|
3306 |
+
|
3307 |
+
if (($this->openString(")", $str, "(") || true ) &&
|
3308 |
+
$this->literal(")"))
|
3309 |
+
{
|
3310 |
+
$args = array();
|
3311 |
+
if (!empty($str)) {
|
3312 |
+
$args[] = array(null, array("string", "", array($str)));
|
3313 |
+
}
|
3314 |
+
|
3315 |
+
$func = array("fncall", $name, $args);
|
3316 |
+
return true;
|
3317 |
+
}
|
3318 |
+
}
|
3319 |
+
|
3320 |
+
$this->seek($s);
|
3321 |
+
return false;
|
3322 |
+
}
|
3323 |
+
|
3324 |
+
protected function argumentList(&$out) {
|
3325 |
+
$s = $this->seek();
|
3326 |
+
$this->literal("(");
|
3327 |
+
|
3328 |
+
$args = array();
|
3329 |
+
while ($this->keyword($var)) {
|
3330 |
+
$ss = $this->seek();
|
3331 |
+
|
3332 |
+
if ($this->literal("=") && $this->expression($exp)) {
|
3333 |
+
$args[] = array("string", "", array($var."="));
|
3334 |
+
$arg = $exp;
|
3335 |
+
} else {
|
3336 |
+
break;
|
3337 |
+
}
|
3338 |
+
|
3339 |
+
$args[] = $arg;
|
3340 |
+
|
3341 |
+
if (!$this->literal(",")) break;
|
3342 |
+
|
3343 |
+
$args[] = array("string", "", array(", "));
|
3344 |
+
}
|
3345 |
+
|
3346 |
+
if (!$this->literal(")") || !count($args)) {
|
3347 |
+
$this->seek($s);
|
3348 |
+
return false;
|
3349 |
+
}
|
3350 |
+
|
3351 |
+
$out = $args;
|
3352 |
+
return true;
|
3353 |
+
}
|
3354 |
+
|
3355 |
+
protected function argumentDef(&$out) {
|
3356 |
+
$s = $this->seek();
|
3357 |
+
$this->literal("(");
|
3358 |
+
|
3359 |
+
$args = array();
|
3360 |
+
while ($this->variable($var)) {
|
3361 |
+
$arg = array($var[1], null, false);
|
3362 |
+
|
3363 |
+
$ss = $this->seek();
|
3364 |
+
if ($this->literal(":") && $this->genericList($defaultVal, "expression")) {
|
3365 |
+
$arg[1] = $defaultVal;
|
3366 |
+
} else {
|
3367 |
+
$this->seek($ss);
|
3368 |
+
}
|
3369 |
+
|
3370 |
+
$ss = $this->seek();
|
3371 |
+
if ($this->literal("...")) {
|
3372 |
+
$sss = $this->seek();
|
3373 |
+
if (!$this->literal(")")) {
|
3374 |
+
$this->throwParseError("... has to be after the final argument");
|
3375 |
+
}
|
3376 |
+
$arg[2] = true;
|
3377 |
+
$this->seek($sss);
|
3378 |
+
} else {
|
3379 |
+
$this->seek($ss);
|
3380 |
+
}
|
3381 |
+
|
3382 |
+
$args[] = $arg;
|
3383 |
+
if (!$this->literal(",")) break;
|
3384 |
+
}
|
3385 |
+
|
3386 |
+
if (!$this->literal(")")) {
|
3387 |
+
$this->seek($s);
|
3388 |
+
return false;
|
3389 |
+
}
|
3390 |
+
|
3391 |
+
$out = $args;
|
3392 |
+
return true;
|
3393 |
+
}
|
3394 |
+
|
3395 |
+
protected function color(&$out) {
|
3396 |
+
$color = array('color');
|
3397 |
+
|
3398 |
+
if ($this->match('(#([0-9a-f]{6})|#([0-9a-f]{3}))', $m)) {
|
3399 |
+
if (isset($m[3])) {
|
3400 |
+
$num = $m[3];
|
3401 |
+
$width = 16;
|
3402 |
+
} else {
|
3403 |
+
$num = $m[2];
|
3404 |
+
$width = 256;
|
3405 |
+
}
|
3406 |
+
|
3407 |
+
$num = hexdec($num);
|
3408 |
+
foreach (array(3,2,1) as $i) {
|
3409 |
+
$t = $num % $width;
|
3410 |
+
$num /= $width;
|
3411 |
+
|
3412 |
+
$color[$i] = $t * (256/$width) + $t * floor(16/$width);
|
3413 |
+
}
|
3414 |
+
|
3415 |
+
$out = $color;
|
3416 |
+
return true;
|
3417 |
+
}
|
3418 |
+
|
3419 |
+
return false;
|
3420 |
+
}
|
3421 |
+
|
3422 |
+
protected function unit(&$unit) {
|
3423 |
+
if ($this->match('([0-9]*(\.)?[0-9]+)([%a-zA-Z]+)?', $m)) {
|
3424 |
+
$unit = array("number", $m[1], empty($m[3]) ? "" : $m[3]);
|
3425 |
+
return true;
|
3426 |
+
}
|
3427 |
+
return false;
|
3428 |
+
}
|
3429 |
+
|
3430 |
+
protected function string(&$out) {
|
3431 |
+
$s = $this->seek();
|
3432 |
+
if ($this->literal('"', false)) {
|
3433 |
+
$delim = '"';
|
3434 |
+
} elseif ($this->literal("'", false)) {
|
3435 |
+
$delim = "'";
|
3436 |
+
} else {
|
3437 |
+
return false;
|
3438 |
+
}
|
3439 |
+
|
3440 |
+
$content = array();
|
3441 |
+
$oldWhite = $this->eatWhiteDefault;
|
3442 |
+
$this->eatWhiteDefault = false;
|
3443 |
+
|
3444 |
+
while ($this->matchString($m, $delim)) {
|
3445 |
+
$content[] = $m[1];
|
3446 |
+
if ($m[2] == "#{") {
|
3447 |
+
$this->count -= strlen($m[2]);
|
3448 |
+
if ($this->interpolation($inter, false)) {
|
3449 |
+
$content[] = $inter;
|
3450 |
+
} else {
|
3451 |
+
$this->count += strlen($m[2]);
|
3452 |
+
$content[] = "#{"; // ignore it
|
3453 |
+
}
|
3454 |
+
} elseif ($m[2] == '\\') {
|
3455 |
+
$content[] = $m[2];
|
3456 |
+
if ($this->literal($delim, false)) {
|
3457 |
+
$content[] = $delim;
|
3458 |
+
}
|
3459 |
+
} else {
|
3460 |
+
$this->count -= strlen($delim);
|
3461 |
+
break; // delim
|
3462 |
+
}
|
3463 |
+
}
|
3464 |
+
|
3465 |
+
$this->eatWhiteDefault = $oldWhite;
|
3466 |
+
|
3467 |
+
if ($this->literal($delim)) {
|
3468 |
+
$out = array("string", $delim, $content);
|
3469 |
+
return true;
|
3470 |
+
}
|
3471 |
+
|
3472 |
+
$this->seek($s);
|
3473 |
+
return false;
|
3474 |
+
}
|
3475 |
+
|
3476 |
+
protected function mixedKeyword(&$out) {
|
3477 |
+
$s = $this->seek();
|
3478 |
+
|
3479 |
+
$parts = array();
|
3480 |
+
|
3481 |
+
$oldWhite = $this->eatWhiteDefault;
|
3482 |
+
$this->eatWhiteDefault = false;
|
3483 |
+
|
3484 |
+
while (true) {
|
3485 |
+
if ($this->keyword($key)) {
|
3486 |
+
$parts[] = $key;
|
3487 |
+
continue;
|
3488 |
+
}
|
3489 |
+
|
3490 |
+
if ($this->interpolation($inter)) {
|
3491 |
+
$parts[] = $inter;
|
3492 |
+
continue;
|
3493 |
+
}
|
3494 |
+
|
3495 |
+
break;
|
3496 |
+
}
|
3497 |
+
|
3498 |
+
$this->eatWhiteDefault = $oldWhite;
|
3499 |
+
|
3500 |
+
if (count($parts) == 0) return false;
|
3501 |
+
|
3502 |
+
if ($this->eatWhiteDefault) {
|
3503 |
+
$this->whitespace();
|
3504 |
+
}
|
3505 |
+
|
3506 |
+
$out = $parts;
|
3507 |
+
return true;
|
3508 |
+
}
|
3509 |
+
|
3510 |
+
// an unbounded string stopped by $end
|
3511 |
+
protected function openString($end, &$out, $nestingOpen=null) {
|
3512 |
+
$oldWhite = $this->eatWhiteDefault;
|
3513 |
+
$this->eatWhiteDefault = false;
|
3514 |
+
|
3515 |
+
$stop = array("'", '"', "#{", $end);
|
3516 |
+
$stop = array_map(array($this, "preg_quote"), $stop);
|
3517 |
+
$stop[] = self::$commentMulti;
|
3518 |
+
|
3519 |
+
$patt = '(.*?)('.implode("|", $stop).')';
|
3520 |
+
|
3521 |
+
$nestingLevel = 0;
|
3522 |
+
|
3523 |
+
$content = array();
|
3524 |
+
while ($this->match($patt, $m, false)) {
|
3525 |
+
if (isset($m[1]) && $m[1] !== '') {
|
3526 |
+
$content[] = $m[1];
|
3527 |
+
if ($nestingOpen) {
|
3528 |
+
$nestingLevel += substr_count($m[1], $nestingOpen);
|
3529 |
+
}
|
3530 |
+
}
|
3531 |
+
|
3532 |
+
$tok = $m[2];
|
3533 |
+
|
3534 |
+
$this->count-= strlen($tok);
|
3535 |
+
if ($tok == $end) {
|
3536 |
+
if ($nestingLevel == 0) {
|
3537 |
+
break;
|
3538 |
+
} else {
|
3539 |
+
$nestingLevel--;
|
3540 |
+
}
|
3541 |
+
}
|
3542 |
+
|
3543 |
+
if (($tok == "'" || $tok == '"') && $this->string($str)) {
|
3544 |
+
$content[] = $str;
|
3545 |
+
continue;
|
3546 |
+
}
|
3547 |
+
|
3548 |
+
if ($tok == "#{" && $this->interpolation($inter)) {
|
3549 |
+
$content[] = $inter;
|
3550 |
+
continue;
|
3551 |
+
}
|
3552 |
+
|
3553 |
+
$content[] = $tok;
|
3554 |
+
$this->count+= strlen($tok);
|
3555 |
+
}
|
3556 |
+
|
3557 |
+
$this->eatWhiteDefault = $oldWhite;
|
3558 |
+
|
3559 |
+
if (count($content) == 0) return false;
|
3560 |
+
|
3561 |
+
// trim the end
|
3562 |
+
if (is_string(end($content))) {
|
3563 |
+
$content[count($content) - 1] = rtrim(end($content));
|
3564 |
+
}
|
3565 |
+
|
3566 |
+
$out = array("string", "", $content);
|
3567 |
+
return true;
|
3568 |
+
}
|
3569 |
+
|
3570 |
+
// $lookWhite: save information about whitespace before and after
|
3571 |
+
protected function interpolation(&$out, $lookWhite=true) {
|
3572 |
+
$oldWhite = $this->eatWhiteDefault;
|
3573 |
+
$this->eatWhiteDefault = true;
|
3574 |
+
|
3575 |
+
$s = $this->seek();
|
3576 |
+
if ($this->literal("#{") && $this->valueList($value) && $this->literal("}", false)) {
|
3577 |
+
|
3578 |
+
// TODO: don't error if out of bounds
|
3579 |
+
|
3580 |
+
if ($lookWhite) {
|
3581 |
+
$left = preg_match('/\s/', $this->buffer[$s - 1]) ? " " : "";
|
3582 |
+
$right = preg_match('/\s/', $this->buffer[$this->count]) ? " ": "";
|
3583 |
+
} else {
|
3584 |
+
$left = $right = false;
|
3585 |
+
}
|
3586 |
+
|
3587 |
+
$out = array("interpolate", $value, $left, $right);
|
3588 |
+
$this->eatWhiteDefault = $oldWhite;
|
3589 |
+
if ($this->eatWhiteDefault) $this->whitespace();
|
3590 |
+
return true;
|
3591 |
+
}
|
3592 |
+
|
3593 |
+
$this->seek($s);
|
3594 |
+
$this->eatWhiteDefault = $oldWhite;
|
3595 |
+
return false;
|
3596 |
+
}
|
3597 |
+
|
3598 |
+
// low level parsers
|
3599 |
+
|
3600 |
+
// returns an array of parts or a string
|
3601 |
+
protected function propertyName(&$out) {
|
3602 |
+
$s = $this->seek();
|
3603 |
+
$parts = array();
|
3604 |
+
|
3605 |
+
$oldWhite = $this->eatWhiteDefault;
|
3606 |
+
$this->eatWhiteDefault = false;
|
3607 |
+
|
3608 |
+
while (true) {
|
3609 |
+
if ($this->interpolation($inter)) {
|
3610 |
+
$parts[] = $inter;
|
3611 |
+
} elseif ($this->keyword($text)) {
|
3612 |
+
$parts[] = $text;
|
3613 |
+
} elseif (count($parts) == 0 && $this->match('[:.#]', $m, false)) {
|
3614 |
+
// css hacks
|
3615 |
+
$parts[] = $m[0];
|
3616 |
+
} else {
|
3617 |
+
break;
|
3618 |
+
}
|
3619 |
+
}
|
3620 |
+
|
3621 |
+
$this->eatWhiteDefault = $oldWhite;
|
3622 |
+
if (count($parts) == 0) return false;
|
3623 |
+
|
3624 |
+
// match comment hack
|
3625 |
+
if (preg_match(self::$whitePattern,
|
3626 |
+
$this->buffer, $m, null, $this->count))
|
3627 |
+
{
|
3628 |
+
if (!empty($m[0])) {
|
3629 |
+
$parts[] = $m[0];
|
3630 |
+
$this->count += strlen($m[0]);
|
3631 |
+
}
|
3632 |
+
}
|
3633 |
+
|
3634 |
+
$this->whitespace(); // get any extra whitespace
|
3635 |
+
|
3636 |
+
$out = array("string", "", $parts);
|
3637 |
+
return true;
|
3638 |
+
}
|
3639 |
+
|
3640 |
+
// comma separated list of selectors
|
3641 |
+
protected function selectors(&$out) {
|
3642 |
+
$s = $this->seek();
|
3643 |
+
$selectors = array();
|
3644 |
+
while ($this->selector($sel)) {
|
3645 |
+
$selectors[] = $sel;
|
3646 |
+
if (!$this->literal(",")) break;
|
3647 |
+
while ($this->literal(",")); // ignore extra
|
3648 |
+
}
|
3649 |
+
|
3650 |
+
if (count($selectors) == 0) {
|
3651 |
+
$this->seek($s);
|
3652 |
+
return false;
|
3653 |
+
}
|
3654 |
+
|
3655 |
+
$out = $selectors;
|
3656 |
+
return true;
|
3657 |
+
}
|
3658 |
+
|
3659 |
+
// whitespace separated list of selectorSingle
|
3660 |
+
protected function selector(&$out) {
|
3661 |
+
$selector = array();
|
3662 |
+
|
3663 |
+
while (true) {
|
3664 |
+
if ($this->match('[>+~]+', $m)) {
|
3665 |
+
$selector[] = array($m[0]);
|
3666 |
+
} elseif ($this->selectorSingle($part)) {
|
3667 |
+
$selector[] = $part;
|
3668 |
+
$this->whitespace();
|
3669 |
+
} elseif ($this->match('\/[^\/]+\/', $m)) {
|
3670 |
+
$selector[] = array($m[0]);
|
3671 |
+
} else {
|
3672 |
+
break;
|
3673 |
+
}
|
3674 |
+
|
3675 |
+
}
|
3676 |
+
|
3677 |
+
if (count($selector) == 0) {
|
3678 |
+
return false;
|
3679 |
+
}
|
3680 |
+
|
3681 |
+
$out = $selector;
|
3682 |
+
return true;
|
3683 |
+
}
|
3684 |
+
|
3685 |
+
// the parts that make up
|
3686 |
+
// div[yes=no]#something.hello.world:nth-child(-2n+1)%placeholder
|
3687 |
+
protected function selectorSingle(&$out) {
|
3688 |
+
$oldWhite = $this->eatWhiteDefault;
|
3689 |
+
$this->eatWhiteDefault = false;
|
3690 |
+
|
3691 |
+
$parts = array();
|
3692 |
+
|
3693 |
+
if ($this->literal("*", false)) {
|
3694 |
+
$parts[] = "*";
|
3695 |
+
}
|
3696 |
+
|
3697 |
+
while (true) {
|
3698 |
+
// see if we can stop early
|
3699 |
+
if ($this->match("\s*[{,]", $m)) {
|
3700 |
+
$this->count--;
|
3701 |
+
break;
|
3702 |
+
}
|
3703 |
+
|
3704 |
+
$s = $this->seek();
|
3705 |
+
// self
|
3706 |
+
if ($this->literal("&", false)) {
|
3707 |
+
$parts[] = scssc::$selfSelector;
|
3708 |
+
continue;
|
3709 |
+
}
|
3710 |
+
|
3711 |
+
if ($this->literal(".", false)) {
|
3712 |
+
$parts[] = ".";
|
3713 |
+
continue;
|
3714 |
+
}
|
3715 |
+
|
3716 |
+
if ($this->literal("|", false)) {
|
3717 |
+
$parts[] = "|";
|
3718 |
+
continue;
|
3719 |
+
}
|
3720 |
+
|
3721 |
+
// for keyframes
|
3722 |
+
if ($this->unit($unit)) {
|
3723 |
+
$parts[] = $unit;
|
3724 |
+
continue;
|
3725 |
+
}
|
3726 |
+
|
3727 |
+
if ($this->keyword($name)) {
|
3728 |
+
$parts[] = $name;
|
3729 |
+
continue;
|
3730 |
+
}
|
3731 |
+
|
3732 |
+
if ($this->interpolation($inter)) {
|
3733 |
+
$parts[] = $inter;
|
3734 |
+
continue;
|
3735 |
+
}
|
3736 |
+
|
3737 |
+
if ($this->literal('%', false) && $this->placeholder($placeholder)) {
|
3738 |
+
$parts[] = '%';
|
3739 |
+
$parts[] = $placeholder;
|
3740 |
+
continue;
|
3741 |
+
}
|
3742 |
+
|
3743 |
+
if ($this->literal("#", false)) {
|
3744 |
+
$parts[] = "#";
|
3745 |
+
continue;
|
3746 |
+
}
|
3747 |
+
|
3748 |
+
// a pseudo selector
|
3749 |
+
if ($this->match("::?", $m) && $this->mixedKeyword($nameParts)) {
|
3750 |
+
$parts[] = $m[0];
|
3751 |
+
foreach ($nameParts as $sub) {
|
3752 |
+
$parts[] = $sub;
|
3753 |
+
}
|
3754 |
+
|
3755 |
+
$ss = $this->seek();
|
3756 |
+
if ($this->literal("(") &&
|
3757 |
+
($this->openString(")", $str, "(") || true ) &&
|
3758 |
+
$this->literal(")"))
|
3759 |
+
{
|
3760 |
+
$parts[] = "(";
|
3761 |
+
if (!empty($str)) $parts[] = $str;
|
3762 |
+
$parts[] = ")";
|
3763 |
+
} else {
|
3764 |
+
$this->seek($ss);
|
3765 |
+
}
|
3766 |
+
|
3767 |
+
continue;
|
3768 |
+
} else {
|
3769 |
+
$this->seek($s);
|
3770 |
+
}
|
3771 |
+
|
3772 |
+
// attribute selector
|
3773 |
+
// TODO: replace with open string?
|
3774 |
+
if ($this->literal("[", false)) {
|
3775 |
+
$attrParts = array("[");
|
3776 |
+
// keyword, string, operator
|
3777 |
+
while (true) {
|
3778 |
+
if ($this->literal("]", false)) {
|
3779 |
+
$this->count--;
|
3780 |
+
break; // get out early
|
3781 |
+
}
|
3782 |
+
|
3783 |
+
if ($this->match('\s+', $m)) {
|
3784 |
+
$attrParts[] = " ";
|
3785 |
+
continue;
|
3786 |
+
}
|
3787 |
+
if ($this->string($str)) {
|
3788 |
+
$attrParts[] = $str;
|
3789 |
+
continue;
|
3790 |
+
}
|
3791 |
+
|
3792 |
+
if ($this->keyword($word)) {
|
3793 |
+
$attrParts[] = $word;
|
3794 |
+
continue;
|
3795 |
+
}
|
3796 |
+
|
3797 |
+
if ($this->interpolation($inter, false)) {
|
3798 |
+
$attrParts[] = $inter;
|
3799 |
+
continue;
|
3800 |
+
}
|
3801 |
+
|
3802 |
+
// operator, handles attr namespace too
|
3803 |
+
if ($this->match('[|-~\$\*\^=]+', $m)) {
|
3804 |
+
$attrParts[] = $m[0];
|
3805 |
+
continue;
|
3806 |
+
}
|
3807 |
+
|
3808 |
+
break;
|
3809 |
+
}
|
3810 |
+
|
3811 |
+
if ($this->literal("]", false)) {
|
3812 |
+
$attrParts[] = "]";
|
3813 |
+
foreach ($attrParts as $part) {
|
3814 |
+
$parts[] = $part;
|
3815 |
+
}
|
3816 |
+
continue;
|
3817 |
+
}
|
3818 |
+
$this->seek($s);
|
3819 |
+
// should just break here?
|
3820 |
+
}
|
3821 |
+
|
3822 |
+
break;
|
3823 |
+
}
|
3824 |
+
|
3825 |
+
$this->eatWhiteDefault = $oldWhite;
|
3826 |
+
|
3827 |
+
if (count($parts) == 0) return false;
|
3828 |
+
|
3829 |
+
$out = $parts;
|
3830 |
+
return true;
|
3831 |
+
}
|
3832 |
+
|
3833 |
+
protected function variable(&$out) {
|
3834 |
+
$s = $this->seek();
|
3835 |
+
if ($this->literal("$", false) && $this->keyword($name)) {
|
3836 |
+
$out = array("var", $name);
|
3837 |
+
return true;
|
3838 |
+
}
|
3839 |
+
$this->seek($s);
|
3840 |
+
return false;
|
3841 |
+
}
|
3842 |
+
|
3843 |
+
protected function keyword(&$word, $eatWhitespace = null) {
|
3844 |
+
if ($this->match('([\w_\-\*!"\'\\\\][\w\-_"\'\\\\]*)',
|
3845 |
+
$m, $eatWhitespace))
|
3846 |
+
{
|
3847 |
+
$word = $m[1];
|
3848 |
+
return true;
|
3849 |
+
}
|
3850 |
+
return false;
|
3851 |
+
}
|
3852 |
+
|
3853 |
+
protected function placeholder(&$placeholder) {
|
3854 |
+
if ($this->match('([\w\-_]+)', $m)) {
|
3855 |
+
$placeholder = $m[1];
|
3856 |
+
return true;
|
3857 |
+
}
|
3858 |
+
return false;
|
3859 |
+
}
|
3860 |
+
|
3861 |
+
// consume an end of statement delimiter
|
3862 |
+
protected function end() {
|
3863 |
+
if ($this->literal(';')) {
|
3864 |
+
return true;
|
3865 |
+
} elseif ($this->count == strlen($this->buffer) || $this->buffer[$this->count] == '}') {
|
3866 |
+
// if there is end of file or a closing block next then we don't need a ;
|
3867 |
+
return true;
|
3868 |
+
}
|
3869 |
+
return false;
|
3870 |
+
}
|
3871 |
+
|
3872 |
+
// advance counter to next occurrence of $what
|
3873 |
+
// $until - don't include $what in advance
|
3874 |
+
// $allowNewline, if string, will be used as valid char set
|
3875 |
+
protected function to($what, &$out, $until = false, $allowNewline = false) {
|
3876 |
+
if (is_string($allowNewline)) {
|
3877 |
+
$validChars = $allowNewline;
|
3878 |
+
} else {
|
3879 |
+
$validChars = $allowNewline ? "." : "[^\n]";
|
3880 |
+
}
|
3881 |
+
if (!$this->match('('.$validChars.'*?)'.$this->preg_quote($what), $m, !$until)) return false;
|
3882 |
+
if ($until) $this->count -= strlen($what); // give back $what
|
3883 |
+
$out = $m[1];
|
3884 |
+
return true;
|
3885 |
+
}
|
3886 |
+
|
3887 |
+
public function throwParseError($msg = "parse error", $count = null) {
|
3888 |
+
$count = is_null($count) ? $this->count : $count;
|
3889 |
+
|
3890 |
+
$line = $this->getLineNo($count);
|
3891 |
+
|
3892 |
+
if (!empty($this->sourceName)) {
|
3893 |
+
$loc = "$this->sourceName on line $line";
|
3894 |
+
} else {
|
3895 |
+
$loc = "line: $line";
|
3896 |
+
}
|
3897 |
+
|
3898 |
+
if ($this->peek("(.*?)(\n|$)", $m, $count)) {
|
3899 |
+
throw new Exception("$msg: failed at `$m[1]` $loc");
|
3900 |
+
} else {
|
3901 |
+
throw new Exception("$msg: $loc");
|
3902 |
+
}
|
3903 |
+
}
|
3904 |
+
|
3905 |
+
public function getLineNo($pos) {
|
3906 |
+
return 1 + substr_count(substr($this->buffer, 0, $pos), "\n");
|
3907 |
+
}
|
3908 |
+
|
3909 |
+
/**
|
3910 |
+
* Match string looking for either ending delim, escape, or string interpolation
|
3911 |
+
*
|
3912 |
+
* {@internal This is a workaround for preg_match's 250K string match limit. }}
|
3913 |
+
*
|
3914 |
+
* @param array $m Matches (passed by reference)
|
3915 |
+
* @param string $delim Delimeter
|
3916 |
+
*
|
3917 |
+
* @return boolean True if match; false otherwise
|
3918 |
+
*/
|
3919 |
+
protected function matchString(&$m, $delim) {
|
3920 |
+
$token = null;
|
3921 |
+
|
3922 |
+
$end = strpos($this->buffer, "\n", $this->count);
|
3923 |
+
if ($end === false) {
|
3924 |
+
$end = strlen($this->buffer);
|
3925 |
+
}
|
3926 |
+
|
3927 |
+
// look for either ending delim, escape, or string interpolation
|
3928 |
+
foreach (array('#{', '\\', $delim) as $lookahead) {
|
3929 |
+
$pos = strpos($this->buffer, $lookahead, $this->count);
|
3930 |
+
if ($pos !== false && $pos < $end) {
|
3931 |
+
$end = $pos;
|
3932 |
+
$token = $lookahead;
|
3933 |
+
}
|
3934 |
+
}
|
3935 |
+
|
3936 |
+
if (!isset($token)) {
|
3937 |
+
return false;
|
3938 |
+
}
|
3939 |
+
|
3940 |
+
$match = substr($this->buffer, $this->count, $end - $this->count);
|
3941 |
+
$m = array(
|
3942 |
+
$match . $token,
|
3943 |
+
$match,
|
3944 |
+
$token
|
3945 |
+
);
|
3946 |
+
$this->count = $end + strlen($token);
|
3947 |
+
|
3948 |
+
return true;
|
3949 |
+
}
|
3950 |
+
|
3951 |
+
// try to match something on head of buffer
|
3952 |
+
protected function match($regex, &$out, $eatWhitespace = null) {
|
3953 |
+
if (is_null($eatWhitespace)) $eatWhitespace = $this->eatWhiteDefault;
|
3954 |
+
|
3955 |
+
$r = '/'.$regex.'/Ais';
|
3956 |
+
if (preg_match($r, $this->buffer, $out, null, $this->count)) {
|
3957 |
+
$this->count += strlen($out[0]);
|
3958 |
+
if ($eatWhitespace) $this->whitespace();
|
3959 |
+
return true;
|
3960 |
+
}
|
3961 |
+
return false;
|
3962 |
+
}
|
3963 |
+
|
3964 |
+
// match some whitespace
|
3965 |
+
protected function whitespace() {
|
3966 |
+
$gotWhite = false;
|
3967 |
+
while (preg_match(self::$whitePattern, $this->buffer, $m, null, $this->count)) {
|
3968 |
+
if ($this->insertComments) {
|
3969 |
+
if (isset($m[1]) && empty($this->commentsSeen[$this->count])) {
|
3970 |
+
$this->append(array("comment", $m[1]));
|
3971 |
+
$this->commentsSeen[$this->count] = true;
|
3972 |
+
}
|
3973 |
+
}
|
3974 |
+
$this->count += strlen($m[0]);
|
3975 |
+
$gotWhite = true;
|
3976 |
+
}
|
3977 |
+
return $gotWhite;
|
3978 |
+
}
|
3979 |
+
|
3980 |
+
protected function peek($regex, &$out, $from=null) {
|
3981 |
+
if (is_null($from)) $from = $this->count;
|
3982 |
+
|
3983 |
+
$r = '/'.$regex.'/Ais';
|
3984 |
+
$result = preg_match($r, $this->buffer, $out, null, $from);
|
3985 |
+
|
3986 |
+
return $result;
|
3987 |
+
}
|
3988 |
+
|
3989 |
+
protected function seek($where = null) {
|
3990 |
+
if ($where === null) return $this->count;
|
3991 |
+
else $this->count = $where;
|
3992 |
+
return true;
|
3993 |
+
}
|
3994 |
+
|
3995 |
+
static function preg_quote($what) {
|
3996 |
+
return preg_quote($what, '/');
|
3997 |
+
}
|
3998 |
+
|
3999 |
+
protected function show() {
|
4000 |
+
if ($this->peek("(.*?)(\n|$)", $m, $this->count)) {
|
4001 |
+
return $m[1];
|
4002 |
+
}
|
4003 |
+
return "";
|
4004 |
+
}
|
4005 |
+
|
4006 |
+
// turn list of length 1 into value type
|
4007 |
+
protected function flattenList($value) {
|
4008 |
+
if ($value[0] == "list" && count($value[2]) == 1) {
|
4009 |
+
return $this->flattenList($value[2][0]);
|
4010 |
+
}
|
4011 |
+
return $value;
|
4012 |
+
}
|
4013 |
+
}
|
4014 |
+
|
4015 |
+
/**
|
4016 |
+
* SCSS base formatter
|
4017 |
+
*
|
4018 |
+
* @author Leaf Corcoran <leafot@gmail.com>
|
4019 |
+
*/
|
4020 |
+
class scss_formatter {
|
4021 |
+
public $indentChar = " ";
|
4022 |
+
|
4023 |
+
public $break = "\n";
|
4024 |
+
public $open = " {";
|
4025 |
+
public $close = "}";
|
4026 |
+
public $tagSeparator = ", ";
|
4027 |
+
public $assignSeparator = ": ";
|
4028 |
+
|
4029 |
+
public function __construct() {
|
4030 |
+
$this->indentLevel = 0;
|
4031 |
+
}
|
4032 |
+
|
4033 |
+
public function indentStr($n = 0) {
|
4034 |
+
return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));
|
4035 |
+
}
|
4036 |
+
|
4037 |
+
public function property($name, $value) {
|
4038 |
+
return $name . $this->assignSeparator . $value . ";";
|
4039 |
+
}
|
4040 |
+
|
4041 |
+
protected function block($block) {
|
4042 |
+
if (empty($block->lines) && empty($block->children)) return;
|
4043 |
+
|
4044 |
+
$inner = $pre = $this->indentStr();
|
4045 |
+
|
4046 |
+
if (!empty($block->selectors)) {
|
4047 |
+
echo $pre .
|
4048 |
+
implode($this->tagSeparator, $block->selectors) .
|
4049 |
+
$this->open . $this->break;
|
4050 |
+
$this->indentLevel++;
|
4051 |
+
$inner = $this->indentStr();
|
4052 |
+
}
|
4053 |
+
|
4054 |
+
if (!empty($block->lines)) {
|
4055 |
+
$glue = $this->break.$inner;
|
4056 |
+
echo $inner . implode($glue, $block->lines);
|
4057 |
+
if (!empty($block->children)) {
|
4058 |
+
echo $this->break;
|
4059 |
+
}
|
4060 |
+
}
|
4061 |
+
|
4062 |
+
foreach ($block->children as $child) {
|
4063 |
+
$this->block($child);
|
4064 |
+
}
|
4065 |
+
|
4066 |
+
if (!empty($block->selectors)) {
|
4067 |
+
$this->indentLevel--;
|
4068 |
+
if (empty($block->children)) echo $this->break;
|
4069 |
+
echo $pre . $this->close . $this->break;
|
4070 |
+
}
|
4071 |
+
}
|
4072 |
+
|
4073 |
+
public function format($block) {
|
4074 |
+
ob_start();
|
4075 |
+
$this->block($block);
|
4076 |
+
$out = ob_get_clean();
|
4077 |
+
|
4078 |
+
return $out;
|
4079 |
+
}
|
4080 |
+
}
|
4081 |
+
|
4082 |
+
/**
|
4083 |
+
* SCSS nested formatter
|
4084 |
+
*
|
4085 |
+
* @author Leaf Corcoran <leafot@gmail.com>
|
4086 |
+
*/
|
4087 |
+
class scss_formatter_nested extends scss_formatter {
|
4088 |
+
public $close = " }";
|
4089 |
+
|
4090 |
+
// adjust the depths of all children, depth first
|
4091 |
+
public function adjustAllChildren($block) {
|
4092 |
+
// flatten empty nested blocks
|
4093 |
+
$children = array();
|
4094 |
+
foreach ($block->children as $i => $child) {
|
4095 |
+
if (empty($child->lines) && empty($child->children)) {
|
4096 |
+
if (isset($block->children[$i + 1])) {
|
4097 |
+
$block->children[$i + 1]->depth = $child->depth;
|
4098 |
+
}
|
4099 |
+
continue;
|
4100 |
+
}
|
4101 |
+
$children[] = $child;
|
4102 |
+
}
|
4103 |
+
|
4104 |
+
$count = count($children);
|
4105 |
+
for ($i = 0; $i < $count; $i++) {
|
4106 |
+
$depth = $children[$i]->depth;
|
4107 |
+
$j = $i + 1;
|
4108 |
+
if (isset($children[$j]) && $depth < $children[$j]->depth) {
|
4109 |
+
$childDepth = $children[$j]->depth;
|
4110 |
+
for (; $j < $count; $j++) {
|
4111 |
+
if ($depth < $children[$j]->depth && $childDepth >= $children[$j]->depth) {
|
4112 |
+
$children[$j]->depth = $depth + 1;
|
4113 |
+
}
|
4114 |
+
}
|
4115 |
+
}
|
4116 |
+
}
|
4117 |
+
|
4118 |
+
$block->children = $children;
|
4119 |
+
|
4120 |
+
// make relative to parent
|
4121 |
+
foreach ($block->children as $child) {
|
4122 |
+
$this->adjustAllChildren($child);
|
4123 |
+
$child->depth = $child->depth - $block->depth;
|
4124 |
+
}
|
4125 |
+
}
|
4126 |
+
|
4127 |
+
protected function block($block) {
|
4128 |
+
if ($block->type == "root") {
|
4129 |
+
$this->adjustAllChildren($block);
|
4130 |
+
}
|
4131 |
+
|
4132 |
+
$inner = $pre = $this->indentStr($block->depth - 1);
|
4133 |
+
if (!empty($block->selectors)) {
|
4134 |
+
echo $pre .
|
4135 |
+
implode($this->tagSeparator, $block->selectors) .
|
4136 |
+
$this->open . $this->break;
|
4137 |
+
$this->indentLevel++;
|
4138 |
+
$inner = $this->indentStr($block->depth - 1);
|
4139 |
+
}
|
4140 |
+
|
4141 |
+
if (!empty($block->lines)) {
|
4142 |
+
$glue = $this->break.$inner;
|
4143 |
+
echo $inner . implode($glue, $block->lines);
|
4144 |
+
if (!empty($block->children)) echo $this->break;
|
4145 |
+
}
|
4146 |
+
|
4147 |
+
foreach ($block->children as $i => $child) {
|
4148 |
+
// echo "*** block: ".$block->depth." child: ".$child->depth."\n";
|
4149 |
+
$this->block($child);
|
4150 |
+
if ($i < count($block->children) - 1) {
|
4151 |
+
echo $this->break;
|
4152 |
+
|
4153 |
+
if (isset($block->children[$i + 1])) {
|
4154 |
+
$next = $block->children[$i + 1];
|
4155 |
+
if ($next->depth == max($block->depth, 1) && $child->depth >= $next->depth) {
|
4156 |
+
echo $this->break;
|
4157 |
+
}
|
4158 |
+
}
|
4159 |
+
}
|
4160 |
+
}
|
4161 |
+
|
4162 |
+
if (!empty($block->selectors)) {
|
4163 |
+
$this->indentLevel--;
|
4164 |
+
echo $this->close;
|
4165 |
+
}
|
4166 |
+
|
4167 |
+
if ($block->type == "root") {
|
4168 |
+
echo $this->break;
|
4169 |
+
}
|
4170 |
+
}
|
4171 |
+
}
|
4172 |
+
|
4173 |
+
/**
|
4174 |
+
* SCSS compressed formatter
|
4175 |
+
*
|
4176 |
+
* @author Leaf Corcoran <leafot@gmail.com>
|
4177 |
+
*/
|
4178 |
+
class scss_formatter_compressed extends scss_formatter {
|
4179 |
+
public $open = "{";
|
4180 |
+
public $tagSeparator = ",";
|
4181 |
+
public $assignSeparator = ":";
|
4182 |
+
public $break = "";
|
4183 |
+
|
4184 |
+
public function indentStr($n = 0) {
|
4185 |
+
return "";
|
4186 |
+
}
|
4187 |
+
}
|
4188 |
+
|
4189 |
+
/**
|
4190 |
+
* SCSS server
|
4191 |
+
*
|
4192 |
+
* @author Leaf Corcoran <leafot@gmail.com>
|
4193 |
+
*/
|
4194 |
+
class scss_server {
|
4195 |
+
/**
|
4196 |
+
* Join path components
|
4197 |
+
*
|
4198 |
+
* @param string $left Path component, left of the directory separator
|
4199 |
+
* @param string $right Path component, right of the directory separator
|
4200 |
+
*
|
4201 |
+
* @return string
|
4202 |
+
*/
|
4203 |
+
protected function join($left, $right) {
|
4204 |
+
return rtrim($left, '/\\') . DIRECTORY_SEPARATOR . ltrim($right, '/\\');
|
4205 |
+
}
|
4206 |
+
|
4207 |
+
/**
|
4208 |
+
* Get name of requested .scss file
|
4209 |
+
*
|
4210 |
+
* @return string|null
|
4211 |
+
*/
|
4212 |
+
protected function inputName() {
|
4213 |
+
switch (true) {
|
4214 |
+
case isset($_GET['p']):
|
4215 |
+
return $_GET['p'];
|
4216 |
+
case isset($_SERVER['PATH_INFO']):
|
4217 |
+
return $_SERVER['PATH_INFO'];
|
4218 |
+
case isset($_SERVER['DOCUMENT_URI']):
|
4219 |
+
return substr($_SERVER['DOCUMENT_URI'], strlen($_SERVER['SCRIPT_NAME']));
|
4220 |
+
}
|
4221 |
+
}
|
4222 |
+
|
4223 |
+
/**
|
4224 |
+
* Get path to requested .scss file
|
4225 |
+
*
|
4226 |
+
* @return string
|
4227 |
+
*/
|
4228 |
+
protected function findInput() {
|
4229 |
+
if (($input = $this->inputName())
|
4230 |
+
&& strpos($input, '..') === false
|
4231 |
+
&& substr($input, -5) === '.scss'
|
4232 |
+
) {
|
4233 |
+
$name = $this->join($this->dir, $input);
|
4234 |
+
|
4235 |
+
if (is_file($name) && is_readable($name)) {
|
4236 |
+
return $name;
|
4237 |
+
}
|
4238 |
+
}
|
4239 |
+
|
4240 |
+
return false;
|
4241 |
+
}
|
4242 |
+
|
4243 |
+
/**
|
4244 |
+
* Get path to cached .css file
|
4245 |
+
*
|
4246 |
+
* @return string
|
4247 |
+
*/
|
4248 |
+
protected function cacheName($fname) {
|
4249 |
+
return $this->join($this->cacheDir, md5($fname) . '.css');
|
4250 |
+
}
|
4251 |
+
|
4252 |
+
/**
|
4253 |
+
* Get path to cached imports
|
4254 |
+
*
|
4255 |
+
* @return string
|
4256 |
+
*/
|
4257 |
+
protected function importsCacheName($out) {
|
4258 |
+
return $out . '.imports';
|
4259 |
+
}
|
4260 |
+
|
4261 |
+
/**
|
4262 |
+
* Determine whether .scss file needs to be re-compiled.
|
4263 |
+
*
|
4264 |
+
* @param string $in Input path
|
4265 |
+
* @param string $out Output path
|
4266 |
+
*
|
4267 |
+
* @return boolean True if compile required.
|
4268 |
+
*/
|
4269 |
+
protected function needsCompile($in, $out) {
|
4270 |
+
if (!is_file($out)) return true;
|
4271 |
+
|
4272 |
+
$mtime = filemtime($out);
|
4273 |
+
if (filemtime($in) > $mtime) return true;
|
4274 |
+
|
4275 |
+
// look for modified imports
|
4276 |
+
$icache = $this->importsCacheName($out);
|
4277 |
+
if (is_readable($icache)) {
|
4278 |
+
$imports = unserialize(file_get_contents($icache));
|
4279 |
+
foreach ($imports as $import) {
|
4280 |
+
if (filemtime($import) > $mtime) return true;
|
4281 |
+
}
|
4282 |
+
}
|
4283 |
+
return false;
|
4284 |
+
}
|
4285 |
+
|
4286 |
+
/**
|
4287 |
+
* Compile .scss file
|
4288 |
+
*
|
4289 |
+
* @param string $in Input path (.scss)
|
4290 |
+
* @param string $out Output path (.css)
|
4291 |
+
*
|
4292 |
+
* @return string
|
4293 |
+
*/
|
4294 |
+
protected function compile($in, $out) {
|
4295 |
+
$start = microtime(true);
|
4296 |
+
$css = $this->scss->compile(file_get_contents($in), $in);
|
4297 |
+
$elapsed = round((microtime(true) - $start), 4);
|
4298 |
+
|
4299 |
+
$v = scssc::$VERSION;
|
4300 |
+
$t = date('r');
|
4301 |
+
$css = "/* compiled by scssphp $v on $t (${elapsed}s) */\n\n" . $css;
|
4302 |
+
|
4303 |
+
file_put_contents($out, $css);
|
4304 |
+
file_put_contents($this->importsCacheName($out),
|
4305 |
+
serialize($this->scss->getParsedFiles()));
|
4306 |
+
return $css;
|
4307 |
+
}
|
4308 |
+
|
4309 |
+
/**
|
4310 |
+
* Compile requested scss and serve css. Outputs HTTP response.
|
4311 |
+
*/
|
4312 |
+
public function serve() {
|
4313 |
+
if ($input = $this->findInput()) {
|
4314 |
+
$output = $this->cacheName($input);
|
4315 |
+
header('Content-type: text/css');
|
4316 |
+
|
4317 |
+
if ($this->needsCompile($input, $output)) {
|
4318 |
+
try {
|
4319 |
+
echo $this->compile($input, $output);
|
4320 |
+
} catch (Exception $e) {
|
4321 |
+
header('HTTP/1.1 500 Internal Server Error');
|
4322 |
+
echo 'Parse error: ' . $e->getMessage() . "\n";
|
4323 |
+
}
|
4324 |
+
} else {
|
4325 |
+
header('X-SCSS-Cache: true');
|
4326 |
+
echo file_get_contents($output);
|
4327 |
+
}
|
4328 |
+
|
4329 |
+
return;
|
4330 |
+
}
|
4331 |
+
|
4332 |
+
header('HTTP/1.0 404 Not Found');
|
4333 |
+
header('Content-type: text');
|
4334 |
+
$v = scssc::$VERSION;
|
4335 |
+
echo "/* INPUT NOT FOUND scss $v */\n";
|
4336 |
+
}
|
4337 |
+
|
4338 |
+
/**
|
4339 |
+
* Constructor
|
4340 |
+
*
|
4341 |
+
* @param string $dir Root directory to .scss files
|
4342 |
+
* @param string $cacheDir Cache directory
|
4343 |
+
* @param \scssc|null $scss SCSS compiler instance
|
4344 |
+
*/
|
4345 |
+
public function __construct($dir, $cacheDir=null, $scss=null) {
|
4346 |
+
$this->dir = $dir;
|
4347 |
+
|
4348 |
+
if (is_null($cacheDir)) {
|
4349 |
+
$cacheDir = $this->join($dir, 'scss_cache');
|
4350 |
+
}
|
4351 |
+
|
4352 |
+
$this->cacheDir = $cacheDir;
|
4353 |
+
if (!is_dir($this->cacheDir)) mkdir($this->cacheDir, 0755, true);
|
4354 |
+
|
4355 |
+
if (is_null($scss)) {
|
4356 |
+
$scss = new scssc();
|
4357 |
+
$scss->setImportPaths($this->dir);
|
4358 |
+
}
|
4359 |
+
$this->scss = $scss;
|
4360 |
+
}
|
4361 |
+
|
4362 |
+
/**
|
4363 |
+
* Helper method to serve compiled scss
|
4364 |
+
*
|
4365 |
+
* @param string $path Root path
|
4366 |
+
*/
|
4367 |
+
static public function serveFrom($path) {
|
4368 |
+
$server = new self($path);
|
4369 |
+
$server->serve();
|
4370 |
+
}
|
4371 |
+
}
|
wp-scss.php
ADDED
@@ -0,0 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Plugin Name: WP-SCSS
|
4 |
+
* Plugin URI: https://github.com/ConnectThink/WP-SCSS
|
5 |
+
* Description: Compiles scss files live on wordpress.
|
6 |
+
* Version: 1.1.1
|
7 |
+
* Author: Connect Think
|
8 |
+
* Author URI: http://connectthink.com
|
9 |
+
* License: GPLv3
|
10 |
+
*/
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Plugin Workflow
|
14 |
+
* 1. Create plugin global variables
|
15 |
+
* 2. Require dependancies
|
16 |
+
* a. scssphp - does scss compiling using php (vendor)
|
17 |
+
* b. wp-scss class - manages compiling
|
18 |
+
* c. options class - builds settings page
|
19 |
+
* 3. Registering Settings Page and Options
|
20 |
+
* 4. Assign plugin settings
|
21 |
+
* 5. Instantiate wp_scss object and run compiler
|
22 |
+
* 6. Handle Errors
|
23 |
+
* 7. Enqueue Styles
|
24 |
+
*/
|
25 |
+
|
26 |
+
|
27 |
+
/*
|
28 |
+
* 1. PLUGIN GLOBAL VARIABLES
|
29 |
+
*/
|
30 |
+
|
31 |
+
// Plugin Paths
|
32 |
+
if (!defined('WPSCSS_THEME_DIR'))
|
33 |
+
define('WPSCSS_THEME_DIR', ABSPATH . 'wp-content/themes/' . get_stylesheet());
|
34 |
+
|
35 |
+
if (!defined('WPSCSS_PLUGIN_NAME'))
|
36 |
+
define('WPSCSS_PLUGIN_NAME', trim(dirname(plugin_basename(__FILE__)), '/'));
|
37 |
+
|
38 |
+
if (!defined('WPSCSS_PLUGIN_DIR'))
|
39 |
+
define('WPSCSS_PLUGIN_DIR', WP_PLUGIN_DIR . '/' . WPSCSS_PLUGIN_NAME);
|
40 |
+
|
41 |
+
if (!defined('WPSCSS_PLUGIN_URL'))
|
42 |
+
define('WPSCSS_PLUGIN_URL', WP_PLUGIN_URL . '/' . WPSCSS_PLUGIN_NAME);
|
43 |
+
|
44 |
+
// Plugin Version
|
45 |
+
if (!defined('WPSCSS_VERSION_KEY'))
|
46 |
+
define('WPSCSS_VERSION_KEY', 'wpscss_version');
|
47 |
+
|
48 |
+
if (!defined('WPSCSS_VERSION_NUM'))
|
49 |
+
define('WPSCSS_VERSION_NUM', '1.1.1');
|
50 |
+
|
51 |
+
// Add version to options table
|
52 |
+
add_option(WPSCSS_VERSION_KEY, WPSCSS_VERSION_NUM);
|
53 |
+
|
54 |
+
|
55 |
+
/*
|
56 |
+
* 2. REQUIRE DEPENDANCIES
|
57 |
+
*
|
58 |
+
* scssphp - scss compiler
|
59 |
+
* class-wp-scss
|
60 |
+
* options.php - settings for plugin page
|
61 |
+
*/
|
62 |
+
|
63 |
+
include_once WPSCSS_PLUGIN_DIR . '/scssphp/scss.inc.php'; // Sass Compiler (vendor)
|
64 |
+
include_once WPSCSS_PLUGIN_DIR . '/class/class-wp-scss.php'; // Compiling Manager
|
65 |
+
include_once WPSCSS_PLUGIN_DIR . '/options.php'; // Options page class
|
66 |
+
|
67 |
+
|
68 |
+
/**
|
69 |
+
* 3. REGISTER SETTINGS
|
70 |
+
*
|
71 |
+
* Instantiate Options Page
|
72 |
+
* Create link on plugin page to settings page
|
73 |
+
*/
|
74 |
+
|
75 |
+
if( is_admin() ) {
|
76 |
+
$wpscss_settings = new Wp_Scss_Settings();
|
77 |
+
}
|
78 |
+
|
79 |
+
add_filter('plugin_action_links', 'wpscss_plugin_action_links', 10, 2);
|
80 |
+
function wpscss_plugin_action_links($links, $file) {
|
81 |
+
static $this_plugin;
|
82 |
+
|
83 |
+
if( !$this_plugin ) {
|
84 |
+
$this_plugin = plugin_basename(__FILE__);
|
85 |
+
}
|
86 |
+
|
87 |
+
if ($file == $this_plugin) {
|
88 |
+
$settings_link = '<a href="' . get_bloginfo('wpurl') . '/wp-admin/admin.php?page=wpscss_options">Settings</a>';
|
89 |
+
array_unshift($links, $settings_link);
|
90 |
+
}
|
91 |
+
|
92 |
+
return $links;
|
93 |
+
}
|
94 |
+
|
95 |
+
|
96 |
+
/**
|
97 |
+
* 4. PLUGIN SETTINGS
|
98 |
+
*
|
99 |
+
* Pull settings from options table
|
100 |
+
* Scrub empty fields or directories that don't exists
|
101 |
+
* Assign settings via settings array to pass to object
|
102 |
+
*/
|
103 |
+
|
104 |
+
$wpscss_options = get_option( 'wpscss_options' );
|
105 |
+
$scss_dir_setting = $wpscss_options['scss_dir'];
|
106 |
+
$css_dir_setting = $wpscss_options['css_dir'];
|
107 |
+
|
108 |
+
// Checks if directories are empty
|
109 |
+
if( $scss_dir_setting == false || $css_dir_setting == false ) {
|
110 |
+
function wpscss_settings_error() {
|
111 |
+
echo '<div class="error">
|
112 |
+
<p><strong>Wp-Scss</strong> requires both directories be specified. <a href="' . get_bloginfo('wpurl') . '/wp-admin/admin.php?page=wpscss_options">Please update your settings.</a></p>
|
113 |
+
</div>';
|
114 |
+
}
|
115 |
+
add_action('admin_notices', 'wpscss_settings_error');
|
116 |
+
return 0; //exits
|
117 |
+
|
118 |
+
// Checks if directory exists
|
119 |
+
} elseif ( !file_exists(WPSCSS_THEME_DIR . $scss_dir_setting) || !file_exists(WPSCSS_THEME_DIR . $css_dir_setting) ) {
|
120 |
+
function wpscss_settings_error() {
|
121 |
+
echo '<div class="error">
|
122 |
+
<p><strong>Wp-Scss:</strong> One or more specified directories does not exist. Please create the directories or <a href="' . get_bloginfo('wpurl') . '/wp-admin/admin.php?page=wpscss_options">update your settings.</a></p>
|
123 |
+
</div>';
|
124 |
+
}
|
125 |
+
add_action('admin_notices', 'wpscss_settings_error');
|
126 |
+
return 0; //exits
|
127 |
+
}
|
128 |
+
|
129 |
+
// Plugin Settings
|
130 |
+
$wpscss_settings = array(
|
131 |
+
'scss_dir' => WPSCSS_THEME_DIR . $scss_dir_setting,
|
132 |
+
'css_dir' => WPSCSS_THEME_DIR . $css_dir_setting,
|
133 |
+
'compiling' => $wpscss_options['compiling_options'],
|
134 |
+
'errors' => $wpscss_options['errors'],
|
135 |
+
'enqueue' => $wpscss_options['enqueue']
|
136 |
+
);
|
137 |
+
|
138 |
+
|
139 |
+
/**
|
140 |
+
* 5. INSTANTIATE & EXECUTE COMPILER
|
141 |
+
*
|
142 |
+
* Passes settings to the object
|
143 |
+
* If needs_compiling passes, runs compile method
|
144 |
+
*/
|
145 |
+
|
146 |
+
$wpscss_compiler = new Wp_Scss(
|
147 |
+
$wpscss_settings['scss_dir'],
|
148 |
+
$wpscss_settings['css_dir'],
|
149 |
+
$wpscss_settings['compiling']
|
150 |
+
);
|
151 |
+
|
152 |
+
|
153 |
+
if ( $wpscss_compiler->needs_compiling() ) {
|
154 |
+
$wpscss_compiler->compile();
|
155 |
+
}
|
156 |
+
|
157 |
+
|
158 |
+
/**
|
159 |
+
* 6. HANDLE COMPILING ERRORS
|
160 |
+
*
|
161 |
+
* First block handles print errors to front end.
|
162 |
+
* This adds a small style block the header to help errors get noticed
|
163 |
+
*
|
164 |
+
* Second block handles print errors to log file.
|
165 |
+
* After the file gets over 1MB it does a purge and deletes the first
|
166 |
+
* half of entries in the file.
|
167 |
+
*/
|
168 |
+
$log_file = $wpscss_compiler->scss_dir.'error_log.log';
|
169 |
+
|
170 |
+
if ( !is_admin() && $wpscss_settings['errors'] === 'show' && count($wpscss_compiler->compile_errors) > 0) {
|
171 |
+
|
172 |
+
echo '<div class="scss_errors"><pre>';
|
173 |
+
echo '<h6 style="margin: 15px 0;">Sass Compiling Error</h6>';
|
174 |
+
|
175 |
+
foreach( $wpscss_compiler->compile_errors as $error) {
|
176 |
+
echo '<p class="sass_error">';
|
177 |
+
echo '<strong>'. $error['file'] .'</strong> <br/><em>"'. $error['message'] .'"</em>';
|
178 |
+
echo '<p class="sass_error">';
|
179 |
+
}
|
180 |
+
|
181 |
+
echo '</pre></div>';
|
182 |
+
|
183 |
+
function wpscss_error_styles() {
|
184 |
+
echo
|
185 |
+
'<style>
|
186 |
+
.scss_errors {
|
187 |
+
position: fixed;
|
188 |
+
top: 0px;
|
189 |
+
z-index: 99999;
|
190 |
+
width: 100%;
|
191 |
+
}
|
192 |
+
.scss_errors pre {
|
193 |
+
background: #f5f5f5;
|
194 |
+
border-left: 5px solid #DD3D36;
|
195 |
+
box-shadow: 0 2px 3px rgba(51,51,51, .4);
|
196 |
+
color: #666;
|
197 |
+
font-family: monospace;
|
198 |
+
font-size: 14px;
|
199 |
+
margin: 20px 0;
|
200 |
+
overflow: auto;
|
201 |
+
padding: 20px;
|
202 |
+
white-space: pre;
|
203 |
+
white-space: pre-wrap;
|
204 |
+
word-wrap: break-word;
|
205 |
+
}
|
206 |
+
</style>';
|
207 |
+
}
|
208 |
+
add_action('wp_print_styles', 'wpscss_error_styles');
|
209 |
+
|
210 |
+
} else { // Hide errors and print them to a log file.
|
211 |
+
foreach ($wpscss_compiler->compile_errors as $error) {
|
212 |
+
$error_string = date('m/d/y g:i:s', time()) .': ';
|
213 |
+
$error_string .= $error['file'] .' - '. $error['message'] . PHP_EOL;
|
214 |
+
file_put_contents($log_file, $error_string, FILE_APPEND);
|
215 |
+
$error_string = "";
|
216 |
+
}
|
217 |
+
}
|
218 |
+
|
219 |
+
// Clean out log file if it get's too large
|
220 |
+
if ( file_exists($log_file) ) {
|
221 |
+
if ( filesize($log_file) > 1000000) {
|
222 |
+
$log_contents = file_get_contents($log_file);
|
223 |
+
$log_arr = explode("\n", $log_contents);
|
224 |
+
$new_contents_arr = array_slice($log_arr, count($log_arr)/2);
|
225 |
+
$new_contents = implode(PHP_EOL, $new_contents_arr) . 'LOG FILE CLEANED ' . date('n/j/y g:i:s', time());
|
226 |
+
file_put_contents($log_file, $new_contents);
|
227 |
+
}
|
228 |
+
}
|
229 |
+
|
230 |
+
|
231 |
+
/**
|
232 |
+
* 7. ENQUEUE STYLES
|
233 |
+
*/
|
234 |
+
|
235 |
+
if ( $wpscss_settings['enqueue'] == '1' ) {
|
236 |
+
function wpscss_enqueue_styles() {
|
237 |
+
global $wpscss_compiler, $wpscss_options;
|
238 |
+
$wpscss_compiler->enqueue_files($wpscss_options['css_dir']);
|
239 |
+
}
|
240 |
+
add_action('wp_enqueue_scripts', 'wpscss_enqueue_styles', 50);
|
241 |
+
}
|