├── .distignore ├── .github └── workflows │ ├── release.yml │ └── update-assets.yml ├── .gitignore ├── .wordpress-org ├── banner-772x250.png ├── icon-128x128.png ├── icon-256x256.png ├── icon.svg.svg └── screenshot-1.png ├── README.md ├── admin-color-schemer.php ├── classes ├── color-scheme.php ├── plugin.php └── version-check.php ├── css └── admin-color-schemer.css ├── js └── admin-color-schemer.js ├── lib └── phpsass │ ├── .gitignore │ ├── .travis.yml │ ├── README.md │ ├── SassException.php │ ├── SassFile.php │ ├── SassLoader.php │ ├── SassParser.php │ ├── VERSION │ ├── compile-apache.php │ ├── composer.json │ ├── renderers │ ├── SassCompactRenderer.php │ ├── SassCompressedRenderer.php │ ├── SassExpandedRenderer.php │ ├── SassNestedRenderer.php │ └── SassRenderer.php │ ├── script │ ├── SassScriptFunction.php │ ├── SassScriptFunctions.php │ ├── SassScriptLexer.php │ ├── SassScriptOperation.php │ ├── SassScriptParser.php │ ├── SassScriptParserExceptions.php │ ├── SassScriptVariable.php │ └── literals │ │ ├── SassBoolean.php │ │ ├── SassColour.php │ │ ├── SassList.php │ │ ├── SassLiteral.php │ │ ├── SassLiteralExceptions.php │ │ ├── SassNumber.php │ │ └── SassString.php │ └── tree │ ├── SassCharsetNode.php │ ├── SassCommentNode.php │ ├── SassContentNode.php │ ├── SassContext.php │ ├── SassDebugNode.php │ ├── SassDirectiveNode.php │ ├── SassEachNode.php │ ├── SassElseNode.php │ ├── SassExtendNode.php │ ├── SassForNode.php │ ├── SassFunctionDefinitionNode.php │ ├── SassIfNode.php │ ├── SassImportNode.php │ ├── SassMediaNode.php │ ├── SassMixinDefinitionNode.php │ ├── SassMixinNode.php │ ├── SassNode.php │ ├── SassNodeExceptions.php │ ├── SassPropertyNode.php │ ├── SassReturnNode.php │ ├── SassRootNode.php │ ├── SassRuleNode.php │ ├── SassVariableNode.php │ ├── SassWarnNode.php │ └── SassWhileNode.php └── templates ├── admin-page.php ├── empty-scheme.php └── updated.php /.distignore: -------------------------------------------------------------------------------- 1 | /.git 2 | /.github 3 | /.wordpress-org 4 | 5 | .distignore 6 | .gitignore 7 | README.md 8 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to WordPress.org 2 | on: 3 | release: 4 | types: [published] 5 | jobs: 6 | tag: 7 | name: New release 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout code 11 | uses: actions/checkout@v2 12 | - name: Transform Plugin Readme 13 | run: | 14 | cp ./README.md ./readme.txt 15 | sed -i -e 's/^# \(.*\)$/=== \1 ===/' -e 's/ #* ===$/ ===/' -e 's/^## \(.*\)$/== \1 ==/' -e 's/ #* ==$/ ==/' -e 's/^### \(.*\)$/= \1 =/' -e 's/ #* =$/ =/' ./readme.txt 16 | - name: WordPress Plugin Deploy 17 | id: deploy 18 | uses: 10up/action-wordpress-plugin-deploy@stable 19 | with: 20 | generate-zip: true 21 | env: 22 | SVN_USERNAME: ${{ secrets.SVN_USERNAME }} 23 | SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} 24 | - name: Upload release asset 25 | uses: actions/upload-release-asset@v1 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | with: 29 | upload_url: ${{ github.event.release.upload_url }} 30 | asset_path: ${{github.workspace}}/${{ github.event.repository.name }}.zip 31 | asset_name: ${{ github.event.repository.name }}.zip 32 | asset_content_type: application/zip 33 | -------------------------------------------------------------------------------- /.github/workflows/update-assets.yml: -------------------------------------------------------------------------------- 1 | name: Plugin asset/readme update 2 | on: 3 | push: 4 | branches: 5 | - trunk 6 | jobs: 7 | trunk: 8 | name: Push to trunk 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@v2 13 | - name: Transform Plugin Readme 14 | run: | 15 | cp ./README.md ./readme.txt 16 | ls 17 | sed -i -e 's/^# \(.*\)$/=== \1 ===/' -e 's/ #* ===$/ ===/' -e 's/^## \(.*\)$/== \1 ==/' -e 's/ #* ==$/ ==/' -e 's/^### \(.*\)$/= \1 =/' -e 's/ #* =$/ =/' ./readme.txt 18 | - name: WordPress.org plugin asset/readme update 19 | uses: 10up/action-wordpress-plugin-asset-update@stable 20 | env: 21 | SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} 22 | SVN_USERNAME: ${{ secrets.SVN_USERNAME }} 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /.wordpress-org/banner-772x250.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helen/admin-color-schemer/3b2d2ae562134e4c8c47d627def1baf41dbb1003/.wordpress-org/banner-772x250.png -------------------------------------------------------------------------------- /.wordpress-org/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helen/admin-color-schemer/3b2d2ae562134e4c8c47d627def1baf41dbb1003/.wordpress-org/icon-128x128.png -------------------------------------------------------------------------------- /.wordpress-org/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helen/admin-color-schemer/3b2d2ae562134e4c8c47d627def1baf41dbb1003/.wordpress-org/icon-256x256.png -------------------------------------------------------------------------------- /.wordpress-org/icon.svg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.wordpress-org/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helen/admin-color-schemer/3b2d2ae562134e4c8c47d627def1baf41dbb1003/.wordpress-org/screenshot-1.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Admin Color Schemer # 2 | Contributors: wordpressdotorg, helen, markjaquith 3 | Requires at least: 3.8 4 | License: GPLv2 or later 5 | License URI: http://www.gnu.org/licenses/gpl-2.0.html 6 | Stable tag: 1.1 7 | 8 | Create your own admin color scheme, right in the WordPress admin under the Tools menu. 9 | 10 | ## Description ## 11 | 12 | Create your own admin color scheme, right in the WordPress admin under the Tools menu. Go simple with the four basic colors, or get into the details and customize to your heart's content. 13 | 14 | ### Contributing ### 15 | 16 | Pull requests and issues on [GitHub](https://github.com/helen/admin-color-schemer) welcome. 17 | 18 | ## Installation ## 19 | 20 | Admin Color Schemer is most easily installed automatically via the Plugins tab in your dashboard. Your uploads folder also needs to be writeable in order to save the scheme. 21 | 22 | ## Frequently Asked Questions ## 23 | 24 | ### Why do I have to click a button to preview? ### 25 | 26 | The preview currently operates by generating a complete stylesheet and reloading it. While in some environments this happens near-instantaneously, the time and resources it takes to reflect a change are not ideal for live previews. We do hope to solve this in a later release. 27 | 28 | ### I'm getting an ugly screen asking me for a username and password. ### 29 | 30 | This means that your uploads folder requires credentials in order to save the the scheme files. If you're not sure how to fix that, please try the [support forums](http://wordpress.org/support/). We'll also work on making this prompt more beautiful in the future. 31 | 32 | ### What should I do if I've updated this plugin or WordPress recently and now my color scheme doesn't look right? ### 33 | Some versions of WordPress or this plugin may change how the custom color scheme output is generated, so if you see something funny, first try re-saving your custom color scheme. If it's still broken, then please let us know. 34 | 35 | ## Screenshots ## 36 | 37 | 1. Admin color schemer in action 38 | 39 | ## Changelog ## 40 | 41 | ### 1.1 ### 42 | * Updated phpsass library to fix PHP 7 bug (with many thanks to @KZeni). 43 | * Ensure custom color scheme can be previewed when using the default admin color scheme. 44 | * Avoid a potential PHP notice. 45 | 46 | ### 1.0 ### 47 | * Initial release 48 | -------------------------------------------------------------------------------- /admin-color-schemer.php: -------------------------------------------------------------------------------- 1 | accessors = array_merge( $this->accessors, array_keys( $admin_schemer->get_colors() ) ); 21 | 22 | // set slug 23 | $this->slug = 'admin_color_schemer_' . $this->id; 24 | 25 | if ( is_array( $attr ) ) { 26 | foreach ( $this->accessors as $thing ) { 27 | if ( isset( $attr[$thing] ) && ! empty( $attr[$thing] ) ) { 28 | $this->{$thing} = $attr[$thing]; 29 | } 30 | } 31 | } else { 32 | // set defaults 33 | // @todo: make this really set defaults for the items that must have a color - what are those? 34 | $this->name = __( 'Custom', 'admin-color-schemer' ); 35 | } 36 | } 37 | 38 | public function __get( $key ) { 39 | if ( in_array( $key, $this->accessors ) ) { 40 | if ( isset( $this->{$key} ) ) { 41 | return $this->sanitize( $this->{$key}, $key, 'out' ); 42 | } else { 43 | return false; 44 | } 45 | } 46 | } 47 | 48 | public function __set( $key, $value ) { 49 | if ( in_array( $key, $this->accessors ) ) { 50 | $this->{$key} = $this->sanitize( $value, $key, 'in' ); 51 | } 52 | } 53 | 54 | public function __isset( $key ) { 55 | return isset( $this->$key ); 56 | } 57 | 58 | private function sanitize( $value, $key, $direction ) { 59 | switch ( $key ) { 60 | case 'id': 61 | $value = absint( $value ); 62 | break; 63 | case 'slug': 64 | $value = sanitize_key( $value ); 65 | case 'name': 66 | $value = esc_html( $value ); 67 | break; 68 | case 'uri': 69 | $value = esc_url_raw( $value ); 70 | break; 71 | default: 72 | // everything else should be a hex value 73 | // regex copied from core's sanitize_hex_value() 74 | if ( ! preg_match('|^#([A-Fa-f0-9]{3}){1,2}$|', $value ) ) { 75 | $value = ''; 76 | } 77 | break; 78 | } 79 | return $value; 80 | } 81 | 82 | public function to_array() { 83 | $return = array(); 84 | foreach ( $this->accessors as $thing ) { 85 | $return[$thing] = $this->{$thing}; 86 | } 87 | return $return; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /classes/version-check.php: -------------------------------------------------------------------------------- 1 | ' ); 21 | } 22 | 23 | public function plugins_loaded() { 24 | if ( ! $this->passes() ) { 25 | remove_action( 'init', array( Admin_Color_Schemer_Plugin::get_instance(), 'init' ) ); 26 | if ( current_user_can( 'activate_plugins' ) ) { 27 | add_action( 'admin_init', array( $this, 'admin_init' ) ); 28 | add_action( 'admin_notices', array( $this, 'admin_notices' ) ); 29 | } 30 | } 31 | } 32 | 33 | public function admin_init() { 34 | deactivate_plugins( plugin_basename( dirname( dirname( __FILE__ ) ) . '/admin-color-schemer.php' ) ); 35 | } 36 | 37 | public function admin_notices() { 38 | echo '

' . __('Admin Color Schemer requires WordPress 3.8 or higher, and has thus been deactivated. Please update your install and then try again!', 'admin-color-schemer' ) . '

'; 39 | if ( isset( $_GET['activate'] ) ) { 40 | unset( $_GET['activate'] ); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /css/admin-color-schemer.css: -------------------------------------------------------------------------------- 1 | .color-schemer-pickers { 2 | margin-top: 1em; 3 | padding: 0 1.5em; 4 | background: #fff !important; /* this is important so a user can't set foreground and background to be the same and essentially lock themselves out - unclear if this good enough or even necessary right now, but just in case. */ 5 | } 6 | 7 | .show-advanced { 8 | margin-bottom: 2em; 9 | } 10 | 11 | .schemer-advanced { 12 | margin-top: 0; 13 | } 14 | -------------------------------------------------------------------------------- /js/admin-color-schemer.js: -------------------------------------------------------------------------------- 1 | (function($){ 2 | $('.colorpicker').wpColorPicker(); 3 | 4 | $('.show-advanced a').on('click', function(e){ 5 | e.preventDefault(); 6 | var $this = $(this); 7 | 8 | $this.parent().prev('.schemer-advanced').show(); 9 | $this.remove(); 10 | }); 11 | 12 | $('#preview').on('click', function(e){ 13 | e.preventDefault(); 14 | 15 | // clear any existing messages 16 | $('h2').next('div').remove(); 17 | 18 | $.ajax({ 19 | type: 'POST', 20 | dataType: 'json', 21 | url: ajaxurl, 22 | data: $('.color-schemer-pickers').serialize(), 23 | success: function(r) { 24 | if ( typeof r.errors != 'undefined' ) { 25 | $('h2').after( '

' + r.message + '

' ); 26 | } else if ( typeof r.uri != 'undefined' ) { 27 | // Default admin color scheme doesn't have a #colors-css link to hot load into so let's make one if we need to 28 | if( ! $('#colors-css').length ) { 29 | $('head').append(""); 30 | } 31 | 32 | $('#colors-css').attr('href', r.uri); 33 | $('h2').after( '
' + r.message + '
' ); 34 | } 35 | } 36 | }) 37 | }) 38 | })(jQuery); 39 | -------------------------------------------------------------------------------- /lib/phpsass/.gitignore: -------------------------------------------------------------------------------- 1 | /sass/sass-cache 2 | .idea 3 | /tests/.sass-cache 4 | vendor 5 | composer.lock 6 | /.project 7 | /.settings 8 | /nbproject 9 | -------------------------------------------------------------------------------- /lib/phpsass/.travis.yml: -------------------------------------------------------------------------------- 1 | # Configuration file for unit test runner. 2 | language: php 3 | php: 4 | - 5.3 5 | - 5.4 6 | - 5.5 7 | - 5.6 8 | - 7.0 9 | - hhvm 10 | env: 11 | - PHPUNIT_ARGS=--group=sass 12 | matrix: 13 | allow_failures: 14 | - php: hhvm 15 | before_script: 16 | - cd tests 17 | script: phpunit $PHPUNIT_ARGS 18 | notifications: 19 | email: false 20 | -------------------------------------------------------------------------------- /lib/phpsass/README.md: -------------------------------------------------------------------------------- 1 | #PHPSass [![build status](https://travis-ci.org/richthegeek/phpsass.png)](https://travis-ci.org/richthegeek/phpsass) 2 | 3 | #IMPORTANT NOTICE 4 | I'm not involved with PHP development anymore, and just don't have the time to do much more than approve pull requests for this project. I'm happy to stick around as a hands-off dictator, but if there are others who wish to take over maintainership then please get in touch ([my username]@gmail.com). 5 | 6 | ## About 7 | This is fork of PHamlP primarily for inclusiong as a Drupal pre-processor. 8 | However, there is no Drupal-specific code and it will work for any PHP system. 9 | 10 | This version of the compiler is NOT compatible with other forks of PHamlP, and 11 | the name has been changed to reflect this. 12 | 13 | ## Other info 14 | Origin: 15 | 16 | License: 17 | 18 | -------------------------------------------------------------------------------- /lib/phpsass/SassException.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass 10 | */ 11 | 12 | /** 13 | * Sass exception class. 14 | * 15 | * @package PHamlP 16 | * @subpackage Sass 17 | */ 18 | class SassException extends Exception { 19 | 20 | /** 21 | * Sass Exception. 22 | * 23 | * @param string $message Exception message 24 | * @param mixed $additionalMessageMixed mixed resource for meta data 25 | */ 26 | public function __construct($message, $additionalMessageMixed = '') { 27 | if (is_object($additionalMessageMixed)) { 28 | $additionalMessageMixed = ": {$additionalMessageMixed->filename}::{$additionalMessageMixed->line}\nSource: {$additionalMessageMixed->source}"; 29 | } else if (is_array($additionalMessageMixed)) { 30 | $additionalMessageMixed = var_export($additionalMessageMixed, TRUE); 31 | } else if (!is_scalar($additionalMessageMixed)) { 32 | $additionalMessageMixed = ''; 33 | } 34 | parent::__construct($message . $additionalMessageMixed); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/phpsass/SassFile.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright Copyright (c) 2010 PBM Web Development 8 | * @license http://phamlp.googlecode.com/files/license.txt 9 | * @package PHamlP 10 | * @subpackage Sass 11 | */ 12 | 13 | /** 14 | * SassFile class. 15 | * @package PHamlP 16 | * @subpackage Sass 17 | */ 18 | class SassFile 19 | { 20 | const CSS = 'css'; 21 | const SASS = 'sass'; 22 | const SCSS = 'scss'; 23 | 24 | public static $path = FALSE; 25 | public static $parser = FALSE; 26 | 27 | /** 28 | * Returns the parse tree for a file. 29 | * @param string $filename filename to parse 30 | * @param SassParser $parser Sass parser 31 | * @return SassRootNode 32 | */ 33 | public static function get_tree($filename, &$parser) 34 | { 35 | $contents = self::get_file_contents($filename); 36 | 37 | $options = array_merge($parser->getOptions(), array('line'=>1)); 38 | 39 | # attempt at cross-syntax imports. 40 | $ext = substr($filename, strrpos($filename, '.') + 1); 41 | if ($ext == self::SASS || $ext == self::SCSS) { 42 | $options['syntax'] = $ext; 43 | } 44 | 45 | $dirName = dirname($filename); 46 | $options['load_paths'][] = $dirName; 47 | if (!in_array($dirName, $parser->load_paths)) { 48 | $parser->load_paths[] = dirname($filename); 49 | } 50 | 51 | $sassParser = new SassParser($options); 52 | $tree = $sassParser->parse($contents, FALSE); 53 | 54 | return $tree; 55 | } 56 | 57 | /** 58 | * Get the content of the given file 59 | * 60 | * @param string $filename 61 | * 62 | * @return mixed|string 63 | */ 64 | public static function get_file_contents($filename) 65 | { 66 | $content = file_get_contents($filename) . "\n\n "; #add some whitespace to fix bug 67 | # strip // comments at this stage, with allowances for http:// style locations. 68 | $content = preg_replace("/(^|\s)\/\/[^\n]+/", '', $content); 69 | // SassFile::$parser = $parser; 70 | // SassFile::$path = $filename; 71 | return $content; 72 | } 73 | 74 | /** 75 | * Returns the full path to a file to parse. 76 | * The file is looked for recursively under the load_paths directories 77 | * If the filename does not end in .sass or .scss try the current syntax first 78 | * then, if a file is not found, try the other syntax. 79 | * @param string $filename filename to find 80 | * @param SassParser $parser Sass parser 81 | * @param bool $sass_only 82 | * @return array of string path(s) to file(s) or FALSE if no such file 83 | */ 84 | public static function get_file($filename, &$parser, $sass_only = TRUE) 85 | { 86 | $ext = substr($filename, strrpos($filename, '.') + 1); 87 | // if the last char isn't *, and it's not (.sass|.scss|.css) 88 | if ($sass_only && substr($filename, -1) != '*' && $ext !== self::SASS && $ext !== self::SCSS && $ext !== self::CSS) { 89 | $sass = self::get_file($filename . '.' . self::SASS, $parser); 90 | 91 | return $sass ? $sass : self::get_file($filename . '.' . self::SCSS, $parser); 92 | } 93 | if (is_file($filename)) { 94 | return array($filename); 95 | } 96 | $paths = $parser->load_paths; 97 | if (is_string($parser->filename) && $path = dirname($parser->filename)) { 98 | $paths[] = $path; 99 | if (!in_array($path, $parser->load_paths)) { 100 | $parser->load_paths[] = $path; 101 | } 102 | } 103 | foreach ($paths as $path) { 104 | $filePath = self::find_file($filename, realpath($path)); 105 | if ($filePath !== false) { 106 | if (!is_array($filePath)) { 107 | return array($filePath); 108 | } 109 | return $filePath; 110 | } 111 | } 112 | foreach ($parser->load_path_functions as $function) { 113 | if (is_callable($function) && $paths = call_user_func($function, $filename, $parser)) { 114 | return $paths; 115 | } 116 | } 117 | 118 | return FALSE; 119 | } 120 | 121 | /** 122 | * Looks for the file recursively in the specified directory. 123 | * This will also look for _filename to handle Sass partials. 124 | * Internal cache to find the files quickly 125 | * 126 | * @param string $filename filename to look for 127 | * @param string $dir path to directory to look in and under 128 | * 129 | * @return mixed string: full path to file if found, false if not 130 | */ 131 | public static function find_file($filename, $dir) { 132 | // internal cache 133 | static $pathCache = array(); 134 | $cacheKey = $filename . '@' . $dir; 135 | if (isset($pathCache[$cacheKey])) { 136 | return $pathCache[$cacheKey]; 137 | } 138 | 139 | if (strstr($filename, DIRECTORY_SEPARATOR . '**')) { 140 | $specialDirectory = $dir . DIRECTORY_SEPARATOR . substr($filename, 0, strpos($filename, DIRECTORY_SEPARATOR . '**')); 141 | if (is_dir($specialDirectory)) { 142 | $paths = array(); 143 | $files = scandir($specialDirectory); 144 | foreach ($files as $file) { 145 | if ($file === '..') { 146 | continue; 147 | } 148 | if (is_dir($specialDirectory . DIRECTORY_SEPARATOR . $file)) { 149 | if ($file === '.') { 150 | $new_filename = str_replace(DIRECTORY_SEPARATOR . '**', '', $filename); 151 | } else { 152 | $new_filename = str_replace('**', $file, $filename); 153 | } 154 | $path = self::find_file($new_filename, $dir); 155 | if ($path !== FALSE) { 156 | if (!is_array($path)) { 157 | $path = array($path); 158 | } 159 | $paths = array_merge($paths, $path); 160 | } 161 | } 162 | } 163 | // cache and return 164 | $pathCache[$cacheKey] = $paths; 165 | return $paths; 166 | } 167 | } 168 | 169 | if (substr($filename, -2) == DIRECTORY_SEPARATOR . '*') { 170 | $checkDir = $dir . DIRECTORY_SEPARATOR . substr($filename, 0, strlen($filename) - 2); 171 | if (is_dir($checkDir)) { 172 | $dir = $checkDir; 173 | $paths = array(); 174 | $files = scandir($dir); 175 | foreach ($files as $file) { 176 | if (($file === '.') || ($file === '..')) { 177 | continue; 178 | } 179 | $ext = substr($file, strrpos($file, '.') + 1); 180 | if (substr($file, -1) != '*' && ($ext == self::SASS || $ext == self::SCSS || $ext == self::CSS)) { 181 | $paths[] = $dir . DIRECTORY_SEPARATOR . $file; 182 | } 183 | } 184 | // cache and return 185 | $pathCache[$cacheKey] = $paths; 186 | return $paths; 187 | } 188 | } 189 | 190 | $partialName = str_replace(basename($filename), ('_' . basename($filename)), $filename); 191 | 192 | foreach (array( 193 | $filename, 194 | $partialName 195 | ) as $file) { 196 | $checkFile = $dir . DIRECTORY_SEPARATOR . $file; 197 | if (is_file($checkFile)) { 198 | // cache and return 199 | $pathCache[$cacheKey] = realpath($checkFile); 200 | return realpath($checkFile); 201 | } 202 | } 203 | 204 | if (is_dir($dir)) { 205 | $dirs = glob($dir . DIRECTORY_SEPARATOR . '*', GLOB_ONLYDIR); 206 | foreach ($dirs as $deepDir) { 207 | $path = self::find_file($filename, $deepDir); 208 | if ($path !== FALSE) { 209 | // cache and return 210 | $pathCache[$cacheKey] = $path; 211 | return $path; 212 | } 213 | } 214 | } 215 | $pathCache[$cacheKey] = FALSE; 216 | 217 | return FALSE; 218 | } 219 | 220 | } 221 | -------------------------------------------------------------------------------- /lib/phpsass/SassLoader.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | 8 | /** 9 | * SASS Auto loader 10 | * 11 | * @author Tim Lochmüller 12 | */ 13 | class SassLoader { 14 | 15 | /** 16 | * Base dir of the SASS Framework 17 | * 18 | * @var string 19 | */ 20 | static private $basePath = NULL; 21 | 22 | /** 23 | * Loader file extension 24 | */ 25 | const FILE_EXTENSION = '.php'; 26 | 27 | /** 28 | * Load the given class 29 | * 30 | * @param string $className 31 | * 32 | * @todo More smarter Implementation, if the files have a smarter structure 33 | */ 34 | static public function load($className) { 35 | $base = self::getBasePath(); 36 | $psrFile = self::getPsrFilePath($className); 37 | if (file_exists($base . $psrFile)) { 38 | require_once($base . $psrFile); 39 | } else if (file_exists($base . $className . self::FILE_EXTENSION)) { 40 | require_once($base . $className . self::FILE_EXTENSION); 41 | } else if (file_exists($base . 'tree/' . $className . self::FILE_EXTENSION)) { 42 | require_once($base . 'tree/' . $className . self::FILE_EXTENSION); 43 | } else if (file_exists($base . 'renderers/' . $className . self::FILE_EXTENSION)) { 44 | require_once($base . 'renderers/' . $className . self::FILE_EXTENSION); 45 | } else if (file_exists($base . 'script/' . $className . self::FILE_EXTENSION)) { 46 | require_once($base . 'script/' . $className . self::FILE_EXTENSION); 47 | } else if (file_exists($base . 'script/literals/' . $className . self::FILE_EXTENSION)) { 48 | require_once($base . 'script/literals/' . $className . self::FILE_EXTENSION); 49 | } else { 50 | 51 | } 52 | } 53 | 54 | /** 55 | * Build the filename for a PSR conform className 56 | * 57 | * @param string $className 58 | * 59 | * @return string 60 | */ 61 | static private function getPsrFilePath($className) { 62 | $className = ltrim($className, '\\'); 63 | $fileName = ''; 64 | if ($lastNsPos = strrpos($className, '\\')) { 65 | $namespace = substr($className, 0, $lastNsPos); 66 | $className = substr($className, $lastNsPos + 1); 67 | $fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR; 68 | } 69 | $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . self::FILE_EXTENSION; 70 | return $fileName; 71 | } 72 | 73 | /** 74 | * Get the current base path 75 | * 76 | * @return string 77 | */ 78 | static private function getBasePath() { 79 | if (self::$basePath === NULL) { 80 | self::$basePath = dirname(__FILE__) . '/'; 81 | } 82 | return self::$basePath; 83 | } 84 | } 85 | 86 | spl_autoload_register('SassLoader::load'); -------------------------------------------------------------------------------- /lib/phpsass/VERSION: -------------------------------------------------------------------------------- 1 | Version 201403201700 - Version bump to reflect multiple merged pull requests 2 | Version 201306271500 - Verbump, merge pr #119 3 | Version 201306181400 - Run PHP-CS-Fixer on all files to fix coding style 4 | Version 201306071300 - Version bump to reflect multiple merged pull requests 5 | Version 201304222300 - Version bump to reflect multiple merged pull requests 6 | Version 201211282000 - Version bump to reflect multiple pull requests being merged over time. 7 | Version 201209040040 - Fixed a bunch of issues 8 | Version 201209031740 - Almost up to 3.2.1; Directive interp, splats, new functions, some fixes 9 | Version 201207301030 - Fix to issue 28, extending from included files 10 | Version 201207271000 - Fixed scale-color function (attrib: Steve Jones) 11 | Version 201207262300 - Various issue fixes, better testing framework 12 | Version 201206291700 - Fixed some tests, minor changes to string and import handling 13 | Version 201203071100 - Hotfix to functions, overhauled evaluation method to be less rubbish 14 | Version 201203061130 - Added min/max functions, fix to lists (evaluation instead of just lexing of values) 15 | Version 201203042100 - Fixes to interpolation in selectors, test coverage, and some cleanup 16 | Version 201203021700 - Placeholder selectors! Fixes to [selectors] when nested leaving a space. 17 | Version 201203011100 - Hotfix for unfound function arguments in url() turning up empty from @imported files. 18 | Version 201203010930 - Fix to minor issues and notices (regressions due to changes in prior version) 19 | Version 201203010000 - Proper list support, functions: if, adjust-color, scale-color, change-color, named parameters for functions+mixins, various minor bugfixes 20 | Version 201202100115 - fix to @import parsing of url(x.css) 21 | Version 201202100100 - fix to @each parsing of things like rgba(*,*,*,*) which previously removed braces incorrectly. 22 | Version 201202092300 - fix to @mixins calling @mixins involving variable-name collisions, minor fix to interpolation fail 23 | Version 201202081100 - fix to @media contexts 24 | Version 201202072300 - first version with versions! 25 | -------------------------------------------------------------------------------- /lib/phpsass/compile-apache.php: -------------------------------------------------------------------------------- 1 | node->token->line} of {$context->node->token->filename} **/\n"; 16 | } 17 | function debug($text, $context) { 18 | print "/** DEBUG: $text, on line {$context->node->token->line} of {$context->node->token->filename} **/\n"; 19 | } 20 | 21 | 22 | $file = $_SERVER['DOCUMENT_ROOT'] . $_SERVER['PATH_INFO']; 23 | $syntax = substr($file, -4, 4); 24 | 25 | $options = array( 26 | 'style' => 'expanded', 27 | 'cache' => FALSE, 28 | 'syntax' => $syntax, 29 | 'debug' => FALSE, 30 | 'callbacks' => array( 31 | 'warn' => 'warn', 32 | 'debug' => 'debug' 33 | ), 34 | ); 35 | 36 | // Execute the compiler. 37 | $parser = new SassParser($options); 38 | try { 39 | print "\n\n" . $parser->toCss($file); 40 | } catch (Exception $e) { 41 | print $e->getMessage(); 42 | } -------------------------------------------------------------------------------- /lib/phpsass/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "richthegeek/phpsass", 3 | "description": "PHPSass is a PHP compiler for SASS, a popular CSS pre-processor language.", 4 | "license": "BSD-2-Clause", 5 | "homepage": "http://phpsass.com", 6 | "support": { 7 | "issues": "https://github.com/richthegeek/phpsass/issues", 8 | "source": "https://github.com/richthegeek/phpsass" 9 | }, 10 | "authors": [ 11 | { 12 | "name": "Richard Lyon", 13 | "homepage": "https://github.com/richthegeek" 14 | }, 15 | { 16 | "name": "Sebastian Siemssen", 17 | "homepage": "https://twitter.com/thefubhy" 18 | }, 19 | { 20 | "name": "Steve Jones", 21 | "homepage": "https://github.com/darthsteven" 22 | }, 23 | { 24 | "name": "Sam Richard", 25 | "homepage": "https://github.com/snugug" 26 | } 27 | ], 28 | "autoload": { 29 | "classmap": [ 30 | "script/", 31 | "renderers/", 32 | "tree/", 33 | "SassException.php", 34 | "SassFile.php", 35 | "SassParser.php" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/phpsass/renderers/SassCompactRenderer.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.renderers 10 | */ 11 | 12 | require_once 'SassCompressedRenderer.php'; 13 | 14 | /** 15 | * SassCompactRenderer class. 16 | * Each CSS rule takes up only one line, with every property defined on that 17 | * line. Nested rules are placed next to each other with no newline, while 18 | * groups of rules have newlines between them. 19 | * @package PHamlP 20 | * @subpackage Sass.renderers 21 | */ 22 | class SassCompactRenderer extends SassCompressedRenderer 23 | { 24 | const DEBUG_INFO_RULE = '@media -sass-debug-info'; 25 | const DEBUG_INFO_PROPERTY = 'font-family'; 26 | 27 | /** 28 | * Renders the brace between the selectors and the properties 29 | * @return string the brace between the selectors and the properties 30 | */ 31 | protected function between() 32 | { 33 | return ' { '; 34 | } 35 | 36 | /** 37 | * Renders the brace at the end of the rule 38 | * @return string the brace between the rule and its properties 39 | */ 40 | protected function end() 41 | { 42 | return " }\n"; 43 | } 44 | 45 | /** 46 | * Renders a comment. 47 | * Comments preceeding a rule are on their own line. 48 | * Comments within a rule are on the same line as the rule. 49 | * @param SassNode $node the node being rendered 50 | * @return string the rendered commnt 51 | */ 52 | public function renderComment($node) 53 | { 54 | $nl = ($node->parent instanceof SassRuleNode?'':"\n"); 55 | 56 | return "$nl/* " . join("\n * ", $node->children) . " */$nl" ; 57 | } 58 | 59 | /** 60 | * Renders a directive. 61 | * @param SassNode $node the node being rendered 62 | * @param array $properties properties of the directive 63 | * @return string the rendered directive 64 | */ 65 | public function renderDirective($node, $properties) 66 | { 67 | return str_replace("\n", '', parent::renderDirective($node, $properties)) . 68 | "\n\n"; 69 | } 70 | 71 | /** 72 | * Renders properties. 73 | * @param SassNode $node the node being rendered 74 | * @param array $properties properties to render 75 | * @return string the rendered properties 76 | */ 77 | public function renderProperties($node, $properties) 78 | { 79 | return join(' ', $properties); 80 | } 81 | 82 | /** 83 | * Renders a property. 84 | * @param SassNode $node the node being rendered 85 | * @return string the rendered property 86 | */ 87 | public function renderProperty($node) 88 | { 89 | $node->important = $node->important ? ' !important' : ''; 90 | 91 | return "{$node->name}: {$node->value}{$node->important};"; 92 | } 93 | 94 | /** 95 | * Renders a rule. 96 | * @param SassNode $node the node being rendered 97 | * @param array $properties rule properties 98 | * @param string $rules rendered rules 99 | * @return string the rendered rule 100 | */ 101 | public function renderRule($node, $properties, $rules) 102 | { 103 | return $this->renderDebug($node) . parent::renderRule($node, $properties, 104 | str_replace("\n\n", "\n", $rules)) . "\n"; 105 | } 106 | 107 | /** 108 | * Renders debug information. 109 | * If the node has the debug_info options set true the line number and filename 110 | * are rendered in a format compatible with 111 | * {@link https://addons.mozilla.org/en-US/firefox/addon/firecompass-for-firebug/ FireCompass}. 112 | * Else if the node has the line_numbers option set true the line number and 113 | * filename are rendered in a comment. 114 | * @param SassNode $node the node being rendered 115 | * @return string the debug information 116 | */ 117 | protected function renderDebug($node) 118 | { 119 | $indent = $this->getIndent($node); 120 | $debug = ''; 121 | if ($node->getDebug_info() || $node->getLine_numbers()) { 122 | $debug .= "$indent/* line {$node->line}, {$node->filename} */\n"; 123 | } 124 | 125 | return $debug; 126 | } 127 | 128 | /** 129 | * Renders rule selectors. 130 | * @param SassNode $node the node being rendered 131 | * @return string the rendered selectors 132 | */ 133 | protected function renderSelectors($node) 134 | { 135 | $selectors = array(); 136 | foreach ($node->selectors as $selector) { 137 | if (!$node->isPlaceholder($selector)) { 138 | $selectors[] = $selector; 139 | } 140 | } 141 | 142 | return join(', ', $selectors); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /lib/phpsass/renderers/SassCompressedRenderer.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.renderers 10 | */ 11 | /** 12 | * SassCompressedRenderer class. 13 | * Compressed style takes up the minimum amount of space possible, having no 14 | * whitespace except that necessary to separate selectors and a newline at the 15 | * end of the file. It's not meant to be human-readable 16 | * @package PHamlP 17 | * @subpackage Sass.renderers 18 | */ 19 | class SassCompressedRenderer extends SassRenderer 20 | { 21 | /** 22 | * Renders the brace between the selectors and the properties 23 | * @return string the brace between the selectors and the properties 24 | */ 25 | protected function between() 26 | { 27 | return '{'; 28 | } 29 | 30 | /** 31 | * Renders the brace at the end of the rule 32 | * @return string the brace between the rule and its properties 33 | */ 34 | protected function end() 35 | { 36 | return '}'; 37 | } 38 | 39 | /** 40 | * Returns the indent string for the node 41 | * @param SassNode $node the node to return the indent string for 42 | * @return string the indent string for this SassNode 43 | */ 44 | protected function getIndent($node) 45 | { 46 | return ''; 47 | } 48 | 49 | /** 50 | * Renders a comment. 51 | * @param SassNode $node the node being rendered 52 | * @return string the rendered comment 53 | */ 54 | public function renderComment($node) 55 | { 56 | return ''; 57 | } 58 | 59 | /** 60 | * Renders a directive. 61 | * @param SassNode $node the node being rendered 62 | * @param array $properties properties of the directive 63 | * @return string the rendered directive 64 | */ 65 | public function renderDirective($node, $properties) 66 | { 67 | return $node->directive . $this->between() . $this->renderProperties($node, $properties) . $this->end(); 68 | } 69 | 70 | /** 71 | * Renders properties. 72 | * @param SassNode $node the node being rendered 73 | * @param array $properties properties to render 74 | * @return string the rendered properties 75 | */ 76 | public function renderProperties($node, $properties) 77 | { 78 | return join('', $properties); 79 | } 80 | 81 | /** 82 | * Renders a property. 83 | * @param SassNode $node the node being rendered 84 | * @return string the rendered property 85 | */ 86 | public function renderProperty($node) 87 | { 88 | $node->important = $node->important ? '!important' : ''; 89 | 90 | return "{$node->name}:{$node->value}{$node->important};"; 91 | } 92 | 93 | /** 94 | * Renders a rule. 95 | * @param SassNode $node the node being rendered 96 | * @param array $properties rule properties 97 | * @param string $rules rendered rules 98 | * @return string the rendered directive 99 | */ 100 | public function renderRule($node, $properties, $rules) 101 | { 102 | $selectors = $this->renderSelectors($node); 103 | if ($selectors) { 104 | return (!empty($properties) ? $selectors . $this->between() . $this->renderProperties($node, $properties) . $this->end() : '') . $rules; 105 | } 106 | } 107 | 108 | /** 109 | * Renders the rule's selectors 110 | * @param SassNode $node the node being rendered 111 | * @return string the rendered selectors 112 | */ 113 | protected function renderSelectors($node) 114 | { 115 | $selectors = array(); 116 | foreach ($node->selectors as $selector) { 117 | if (!$node->isPlaceholder($selector)) { 118 | $selectors[] = $selector; 119 | } 120 | } 121 | 122 | return join(',', $selectors); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /lib/phpsass/renderers/SassExpandedRenderer.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.renderers 10 | */ 11 | 12 | require_once 'SassCompactRenderer.php'; 13 | 14 | /** 15 | * SassExpandedRenderer class. 16 | * Expanded is the typical human-made CSS style, with each property and rule 17 | * taking up one line. Properties are indented within the rules, but the rules 18 | * are not indented in any special way. 19 | * @package PHamlP 20 | * @subpackage Sass.renderers 21 | */ 22 | class SassExpandedRenderer extends SassCompactRenderer 23 | { 24 | /** 25 | * Renders the brace between the selectors and the properties 26 | * @return string the brace between the selectors and the properties 27 | */ 28 | protected function between() 29 | { 30 | return " {\n" ; 31 | } 32 | 33 | /** 34 | * Renders the brace at the end of the rule 35 | * @return string the brace between the rule and its properties 36 | */ 37 | protected function end() 38 | { 39 | return "\n}\n\n"; 40 | } 41 | 42 | /** 43 | * Renders a comment. 44 | * @param SassNode $node the node being rendered 45 | * @return string the rendered commnt 46 | */ 47 | public function renderComment($node) 48 | { 49 | $indent = $this->getIndent($node); 50 | $lines = explode("\n", $node->value); 51 | foreach ($lines as &$line) { 52 | $line = trim($line); 53 | } 54 | 55 | return "$indent/*\n$indent * ".join("\n$indent * ", $lines)."\n$indent */".(empty($indent)?"\n":''); 56 | } 57 | 58 | /** 59 | * Renders properties. 60 | * @param mixed $node 61 | * @param array $properties properties to render 62 | * @return string the rendered properties 63 | */ 64 | public function renderProperties($node, $properties) 65 | { 66 | $indent = $this->getIndent($node).self::INDENT; 67 | 68 | return $indent.join("\n$indent", $properties); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/phpsass/renderers/SassNestedRenderer.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.renderers 10 | */ 11 | 12 | require_once 'SassExpandedRenderer.php'; 13 | 14 | /** 15 | * SassNestedRenderer class. 16 | * Nested style is the default Sass style, because it reflects the structure of 17 | * the document in much the same way Sass does. Each rule is indented based on 18 | * how deeply it's nested. Each property has its own line and is indented 19 | * within the rule. 20 | * @package PHamlP 21 | * @subpackage Sass.renderers 22 | */ 23 | class SassNestedRenderer extends SassExpandedRenderer 24 | { 25 | /** 26 | * Renders the brace at the end of the rule 27 | * @return string the brace between the rule and its properties 28 | */ 29 | protected function end() 30 | { 31 | return " }\n"; 32 | } 33 | 34 | /** 35 | * Returns the indent string for the node 36 | * @param SassNode $node the node being rendered 37 | * @return string the indent string for this SassNode 38 | */ 39 | protected function getIndent($node) 40 | { 41 | return str_repeat(self::INDENT, $node->level); 42 | } 43 | 44 | /** 45 | * Renders a directive. 46 | * @param SassNode $node the node being rendered 47 | * @param array $properties properties of the directive 48 | * @return string the rendered directive 49 | */ 50 | public function renderDirective($node, $properties) 51 | { 52 | $directive = $this->getIndent($node) . $node->directive . $this->between() . $this->renderProperties($node, $properties); 53 | 54 | return preg_replace('/(.*})\n$/', '\1', $directive) . $this->end(); 55 | } 56 | 57 | /** 58 | * Renders rule selectors. 59 | * @param SassNode $node the node being rendered 60 | * @return string the rendered selectors 61 | */ 62 | protected function renderSelectors($node) 63 | { 64 | $selectors = array(); 65 | foreach ($node->selectors as $selector) { 66 | if (!$node->isPlaceholder($selector)) { 67 | $selectors[] = $selector; 68 | } 69 | } 70 | 71 | $indent = $this->getIndent($node); 72 | 73 | return $indent.join(",\n$indent", $selectors); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/phpsass/renderers/SassRenderer.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.renderers 10 | */ 11 | 12 | #require_once 'SassCompactRenderer.php'; 13 | #require_once 'SassCompressedRenderer.php'; 14 | #require_once 'SassExpandedRenderer.php'; 15 | #require_once 'SassNestedRenderer.php'; 16 | 17 | /** 18 | * SassRenderer class. 19 | * @package PHamlP 20 | * @subpackage Sass.renderers 21 | */ 22 | class SassRenderer 23 | { 24 | /**#@+ 25 | * Output Styles 26 | */ 27 | const STYLE_COMPRESSED = 'compressed'; 28 | const STYLE_COMPACT = 'compact'; 29 | const STYLE_EXPANDED = 'expanded'; 30 | const STYLE_NESTED = 'nested'; 31 | /**#@-*/ 32 | 33 | const INDENT = ' '; 34 | 35 | /** 36 | * Returns the renderer for the required render style. 37 | * @param string $style render style 38 | * @return SassRenderer 39 | */ 40 | public static function getRenderer($style) 41 | { 42 | switch ($style) { 43 | case self::STYLE_COMPACT: 44 | return new SassCompactRenderer(); 45 | case self::STYLE_COMPRESSED: 46 | return new SassCompressedRenderer(); 47 | case self::STYLE_EXPANDED: 48 | return new SassExpandedRenderer(); 49 | case self::STYLE_NESTED: 50 | return new SassNestedRenderer(); 51 | } // switch 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/phpsass/script/SassScriptFunction.php: -------------------------------------------------------------------------------- 1 | 5 | * @copyright Copyright (c) 2010 PBM Web Development 6 | * @license http://phamlp.googlecode.com/files/license.txt 7 | * @package PHamlP 8 | * @subpackage Sass.script 9 | */ 10 | 11 | require_once 'SassScriptFunctions.php'; 12 | 13 | /** 14 | * SassScriptFunction class. 15 | * Preforms a SassScript function. 16 | * @package PHamlP 17 | * @subpackage Sass.script 18 | */ 19 | class SassScriptFunction 20 | { 21 | /**@#+ 22 | * Regexes for matching and extracting functions and arguments 23 | */ 24 | const MATCH = '/^(((-\w)|(\w))[-\w:.]*)\(/'; 25 | const MATCH_FUNC = '/^((?:(?:-\w)|(?:\w))[-\w:.]*)\((.*)\)/'; 26 | const SPLIT_ARGS = '/\s*((?:[\'"].*?["\'])|(?:.+?(?:\(.*\).*?)?))\s*(?:,|$)/'; 27 | const NAME = 1; 28 | const ARGS = 2; 29 | 30 | private $name; 31 | private $args; 32 | 33 | public static $context; 34 | 35 | /** 36 | * SassScriptFunction constructor 37 | * @param string $name name of the function 38 | * @param array $args arguments for the function 39 | * @return SassScriptFunction 40 | */ 41 | public function __construct($name, $args) 42 | { 43 | $this->name = $name; 44 | $this->args = $args; 45 | } 46 | 47 | private function process_arguments($input) 48 | { 49 | if (is_array($input)) { 50 | $output = array(); 51 | foreach ($input as $k => $token) { 52 | $output[$k] = trim($this->process_arguments($token), '\'"'); 53 | } 54 | 55 | return $output; 56 | } 57 | 58 | $token = $input; 59 | if ($token === null) 60 | return ' '; 61 | 62 | if (!is_object($token)) 63 | return (string) $token; 64 | 65 | if (method_exists($token, 'toString')) 66 | return $token->toString(); 67 | 68 | if (method_exists($token, '__toString')) 69 | return $token->__toString(); 70 | 71 | if (method_exists($token, 'perform')) 72 | return $token->perform(); 73 | 74 | return ''; 75 | } 76 | 77 | /** 78 | * Evaluates the function. 79 | * Look for a user defined function first - this allows users to override 80 | * pre-defined functions, then try the pre-defined functions. 81 | * 82 | * @throws Exception 83 | * @return object the value of this Function 84 | */ 85 | public function perform() 86 | { 87 | self::$context = new SassContext(SassScriptParser::$context); 88 | 89 | $name = preg_replace('/[^a-z0-9_]/', '_', strtolower($this->name)); 90 | $args = $this->process_arguments($this->args); 91 | 92 | foreach ($this->args as $k => $v) { 93 | if (!is_numeric($k)) { 94 | self::$context->setVariable($k, $v); 95 | } 96 | } 97 | 98 | try { 99 | if (SassScriptParser::$context->hasFunction($this->name)) { 100 | $return = SassScriptParser::$context->getFunction($this->name)->execute(SassScriptParser::$context, $this->args); 101 | 102 | return $return; 103 | } elseif (SassScriptParser::$context->hasFunction($name)) { 104 | $return = SassScriptParser::$context->getFunction($name)->execute(SassScriptParser::$context, $this->args); 105 | 106 | return $return; 107 | } 108 | } catch (Exception $e) { 109 | throw $e; 110 | } 111 | 112 | if (isset(SassParser::$functions) && count(SassParser::$functions)) { 113 | foreach (SassParser::$functions as $fn => $callback) { 114 | if (($fn == $name || $fn == $this->name) && is_callable($callback)) { 115 | $result = call_user_func_array($callback, $args); 116 | if (!is_object($result)) { 117 | $lexed = SassScriptLexer::$instance->lex($result, self::$context); 118 | if (count($lexed) === 1) { 119 | return $lexed[0]; 120 | } 121 | 122 | return new SassString(implode('', $this->process_arguments($lexed))); 123 | } 124 | 125 | return $result; 126 | } 127 | } 128 | } 129 | 130 | if (method_exists('SassScriptFunctions', $name) || method_exists('SassScriptFunctions', $name = '_' . $name)) { 131 | $sig = self::get_reflection(array('SassScriptFunctions', $name)); 132 | list($args) = self::fill_parameters($sig, $this->args, SassScriptParser::$context, $this); 133 | 134 | return call_user_func_array(array('SassScriptFunctions', $name), $args); 135 | } 136 | 137 | foreach ($this->args as $i => $arg) { 138 | if (is_object($arg) && isset($arg->quote)) { 139 | $args[$i] = (string)$arg; 140 | } 141 | if (!is_numeric($i) && SassScriptParser::$context->hasVariable($i)) { 142 | $args[$i] = SassScriptParser::$context->getVariable($i); 143 | } 144 | } 145 | 146 | // CSS function: create a SassString that will emit the function into the CSS 147 | return new SassString($this->name . '(' . join(', ', $args) . ')'); 148 | } 149 | 150 | /** 151 | * Imports files in the specified directory. 152 | * @param string $dir path to directory to import 153 | * @return array filenames imported 154 | */ 155 | private function import($dir) 156 | { 157 | $files = array(); 158 | 159 | foreach (scandir($dir) as $file) { 160 | if (($file === '.') || ($file === '..')) continue; 161 | if (is_file($dir . DIRECTORY_SEPARATOR . $file)) { 162 | $files[] = $file; 163 | require_once($dir . DIRECTORY_SEPARATOR . $file); 164 | } 165 | } // foreach 166 | 167 | return $files; 168 | } 169 | 170 | /** 171 | * Returns a value indicating if a token of this type can be matched at 172 | * the start of the subject string. 173 | * @param string $subject the subject string 174 | * @return mixed match at the start of the string or false if no match 175 | */ 176 | public static function isa($subject) 177 | { 178 | if (!preg_match(self::MATCH, $subject, $matches)) 179 | return false; 180 | 181 | $match = $matches[0]; 182 | $paren = 1; 183 | $strpos = strlen($match); 184 | $strlen = strlen($subject); 185 | $subject_str = (string) $subject; 186 | 187 | while ($paren && $strpos < $strlen) { 188 | $c = $subject_str[$strpos++]; 189 | 190 | $match .= $c; 191 | if ($c === '(') { 192 | $paren += 1; 193 | } elseif ($c === ')') { 194 | $paren -= 1; 195 | } 196 | } 197 | 198 | return $match; 199 | } 200 | 201 | public static function extractArgs($string, $include_null = TRUE, $context) 202 | { 203 | $args = array(); 204 | $arg = ''; 205 | $paren = 0; 206 | $strpos = 0; 207 | $strlen = strlen($string); 208 | 209 | $list = SassList::_build_list($string, ','); 210 | $return = array(); 211 | 212 | foreach ($list as $k => $value) { 213 | if (substr($value, -3, 3) == '...' && preg_match(SassVariableNode::MATCH, substr($value, 0, -3) . ':', $match)) { 214 | $list = new SassList($context->getVariable($match[SassVariableNode::NAME])); 215 | if (count($list->value) > 1) { 216 | $return = array_merge($return, $list->value); 217 | continue; 218 | } 219 | } 220 | 221 | if (strpos($value, ':') !== false && preg_match(SassVariableNode::MATCH, $value, $match)) { 222 | $return[$match[SassVariableNode::NAME]] = $match[SassVariableNode::VALUE]; 223 | } elseif (substr($value, 0, 1) == '$' && $include_null) { 224 | $return[str_replace('$', '', $value)] = NULL; 225 | } elseif ($include_null || $value !== NULL) { 226 | $return[] = $value; 227 | } 228 | } 229 | 230 | return $return; 231 | } 232 | 233 | public static function get_reflection($method) 234 | { 235 | if (is_array($method)) { 236 | $class = new ReflectionClass($method[0]); 237 | $function = $class->getMethod($method[1]); 238 | } else { 239 | $function = new ReflectionFunction($method); 240 | } 241 | 242 | $return = array(); 243 | foreach ($function->getParameters() as $parameter) { 244 | $default = $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : NULL; 245 | if ($default !== NULL) { 246 | $parsed = is_bool($default) ? new SassBoolean($default) : SassScriptParser::$instance->evaluate($default, new SassContext()); 247 | $parsed = ($parsed === NULL) ? new SassString($default) : $parsed; 248 | } else { 249 | $parsed = $default; 250 | } 251 | $return[$parameter->getName()] = $parsed; # we evaluate the defaults to get Sass objects. 252 | } 253 | 254 | return $return; 255 | } 256 | 257 | public static function fill_parameters($required, $provided, $context, $source) 258 | { 259 | $context = new SassContext($context); 260 | $_required = array_merge(array(), $required); // need to array_merge? 261 | $fill = $_required; 262 | 263 | foreach ($required as $name=>$default) { 264 | // we require that named variables provide a default. 265 | if (isset($provided[$name]) && $default !== NULL) { 266 | $_required[$name] = $provided[$name]; 267 | unset($provided[$name]); 268 | unset($required[$name]); 269 | } 270 | } 271 | 272 | // print_r(array($required, $provided, $_required)); 273 | $provided_copy = $provided; 274 | 275 | foreach ($required as $name=>$default) { 276 | if ($default === null && strpos($name, '=') !== FALSE) { 277 | list($name, $default) = explode('=', $name); 278 | $name = trim($name); 279 | $default = trim($default); 280 | } 281 | if (count($provided)) { 282 | $arg = array_shift($provided); 283 | } elseif ($default !== NULL) { 284 | $arg = $default; 285 | 286 | // for mixins with default values that refer to other arguments 287 | // (e.g. border-radius($topright: 0, $bottomright: $topright, $bottomleft: $topright, $topleft: $topright) 288 | if (is_string($default) && $default[0]=='$') { 289 | $referred = trim(trim($default, '$')); 290 | $pos = array_search($referred, array_keys($required)); 291 | if ($pos!==false && array_key_exists($pos, $provided_copy)) { 292 | $arg = $provided_copy[$pos]; 293 | } 294 | } 295 | } else { 296 | throw new SassMixinNodeException("Function::$name: Required variable ($name) not given.\nFunction defined: " . $source->token->filename . '::' . $source->token->line . "\nFunction used", $source); 297 | } 298 | // splats 299 | if (substr($name, -3, 3) == '...') { 300 | unset ($_required[$name]); 301 | $name = substr($name, 0, -3); 302 | $_required[$name] = new SassList('', ','); 303 | $_required[$name]->value = array_merge(array($arg), $provided); 304 | continue; 305 | } else { 306 | $_required[$name] = $arg; 307 | } 308 | } 309 | 310 | $_required = array_merge($_required, $provided); // any remaining args get tacked onto the end 311 | 312 | foreach ($_required as $key => $value) { 313 | if (!is_object($value)) { 314 | $_required[$key] = SassScriptParser::$instance->evaluate($value, $context); 315 | } 316 | } 317 | 318 | return array($_required, $context); 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /lib/phpsass/script/SassScriptLexer.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.script 10 | */ 11 | 12 | require_once 'literals/SassBoolean.php'; 13 | require_once 'literals/SassColour.php'; 14 | require_once 'literals/SassNumber.php'; 15 | require_once 'literals/SassString.php'; 16 | require_once 'literals/SassList.php'; 17 | require_once 'SassScriptFunction.php'; 18 | require_once 'SassScriptOperation.php'; 19 | require_once 'SassScriptVariable.php'; 20 | 21 | /** 22 | * SassScriptLexer class. 23 | * Lexes SassSCript into tokens for the parser. 24 | * 25 | * Implements a {@link http://en.wikipedia.org/wiki/Shunting-yard_algorithm Shunting-yard algorithm} to provide {@link http://en.wikipedia.org/wiki/Reverse_Polish_notation Reverse Polish notation} output. 26 | * @package PHamlP 27 | * @subpackage Sass.script 28 | */ 29 | class SassScriptLexer 30 | { 31 | const MATCH_WHITESPACE = '/^\s+/'; 32 | 33 | /** 34 | * Static holder for last instance of SassScriptLexer 35 | */ 36 | public static $instance; 37 | 38 | /** 39 | * @var SassScriptParser the parser object 40 | */ 41 | public $parser; 42 | 43 | /** 44 | * SassScriptLexer constructor. 45 | * @return SassScriptLexer 46 | */ 47 | public function __construct($parser) 48 | { 49 | $this->parser = $parser; 50 | self::$instance = $this; 51 | } 52 | 53 | /** 54 | * Lex an expression into SassScript tokens. 55 | * @param string $string expression to lex 56 | * @param SassContext $context the context in which the expression is lexed 57 | * @return array tokens 58 | */ 59 | public function lex($string, $context) 60 | { 61 | // if it's already lexed, just return it as-is 62 | if (is_object($string)) { 63 | return array($string); 64 | } 65 | if (is_array($string)) { 66 | return $string; 67 | } 68 | $tokens = array(); 69 | // whilst the string is not empty, split it into it's tokens. 70 | while ($string !== false) { 71 | if (($match = $this->isWhitespace($string)) !== false) { 72 | $tokens[] = null; 73 | } elseif (($match = SassScriptFunction::isa($string)) !== false) { 74 | preg_match(SassScriptFunction::MATCH_FUNC, $match, $matches); 75 | $args = array(); 76 | foreach (SassScriptFunction::extractArgs($matches[SassScriptFunction::ARGS], false, $context) as $key => $expression) { 77 | $args[$key] = $this->parser->evaluate($expression, $context); 78 | } 79 | $tokens[] = new SassScriptFunction($matches[SassScriptFunction::NAME], $args); 80 | } elseif (($match = SassBoolean::isa($string)) !== false) { 81 | $tokens[] = new SassBoolean($match); 82 | } elseif (($match = SassColour::isa($string)) !== false) { 83 | $tokens[] = new SassColour($match); 84 | } elseif (($match = SassNumber::isa($string)) !== false) { 85 | $tokens[] = new SassNumber($match); 86 | } elseif (($match = SassString::isa($string)) !== false) { 87 | $stringed = new SassString($match); 88 | if ( 89 | strlen($stringed->quote) == 0 && 90 | SassList::isa($string) !== false && 91 | // recursion loop prevented for expressions 92 | // like "transition: -webkit-transform 1s" 93 | !preg_match("/^\-\w+\-\w+$/", $stringed->value) 94 | ) { 95 | $tokens[] = new SassList($string); 96 | } else { 97 | $tokens[] = $stringed; 98 | } 99 | } elseif ($string == '()') { 100 | $match = $string; 101 | $tokens[] = new SassList($match); 102 | } elseif (($match = SassScriptOperation::isa($string)) !== false) { 103 | $tokens[] = new SassScriptOperation($match); 104 | } elseif (($match = SassScriptVariable::isa($string)) !== false) { 105 | $tokens[] = new SassScriptVariable($match); 106 | } else { 107 | $_string = $string; 108 | $match = ''; 109 | while (strlen($_string) && !$this->isWhitespace($_string)) { 110 | foreach (SassScriptOperation::$inStrOperators as $operator) { 111 | if (substr($_string, 0, strlen($operator)) == $operator) { 112 | break 2; 113 | } 114 | } 115 | $match .= $_string[0]; 116 | $_string = substr($_string, 1); 117 | } 118 | $tokens[] = new SassString($match); 119 | } 120 | $string = substr($string, strlen($match)); 121 | if ($string === '') { 122 | $string = false; 123 | } 124 | } 125 | 126 | return $tokens; 127 | } 128 | 129 | /** 130 | * Returns a value indicating if a token of this type can be matched at 131 | * the start of the subject string. 132 | * @param string $subject the subject string 133 | * @return mixed match at the start of the string or false if no match 134 | */ 135 | public function isWhitespace($subject) 136 | { 137 | return (preg_match(self::MATCH_WHITESPACE, $subject, $matches) ? $matches[0] : false); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /lib/phpsass/script/SassScriptOperation.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.script 10 | */ 11 | 12 | /** 13 | * SassScriptOperation class. 14 | * The operation to perform. 15 | * @package PHamlP 16 | * @subpackage Sass.script 17 | */ 18 | class SassScriptOperation 19 | { 20 | const MATCH = '/^(\(|\)|\+|-|\*|\/|%|<=|>=|<|>|==|!=|=|#{|}|,|and\b|or\b|xor\b|not\b)/'; 21 | 22 | /** 23 | * @var array map symbols to tokens. 24 | * A token is function, associativity, precedence, number of operands 25 | */ 26 | public static $operators = array( 27 | '*' => array('times', 'l', 8, 2), 28 | '/' => array('div', 'l', 8, 2), 29 | '%' => array('modulo', 'l', 8, 2), 30 | '+' => array('plus', 'l', 7, 2), 31 | '-' => array('minus', 'l', 7, 2), 32 | '<<' => array('shiftl', 'l', 6, 2), 33 | '>>' => array('shiftr', 'l', 6, 2), 34 | '<=' => array('lte', 'l', 5, 2), 35 | '>=' => array('gte', 'l', 5, 2), 36 | '<' => array('lt', 'l', 5, 2), 37 | '>' => array('gt', 'l', 5, 2), 38 | '==' => array('eq', 'l', 4, 2), 39 | '!=' => array('neq', 'l', 4, 2), 40 | 'and' => array('and', 'l', 3, 2), 41 | 'or' => array('or', 'l', 3, 2), 42 | 'xor' => array('xor', 'l', 3, 2), 43 | 'not' => array('not', 'l', 4, 1), # precedence higher than and. 44 | '=' => array('assign', 'l', 2, 2), 45 | ')' => array('rparen', 'l', 10), 46 | '(' => array('lparen', 'l', 10), 47 | ',' => array('comma', 'l', 0, 2), 48 | '#{' => array('begin_interpolation'), 49 | '}' => array('end_interpolation'), 50 | ); 51 | 52 | /** 53 | * @var array operators with meaning in uquoted strings; 54 | * selectors, property names and values 55 | */ 56 | public static $inStrOperators = array(',', '#{', ')', '('); 57 | 58 | /** 59 | * @var array default operator token. 60 | */ 61 | public static $defaultOperator = array('concat', 'l', 0, 2); 62 | 63 | /** 64 | * @var string operator for this operation 65 | */ 66 | private $operator; 67 | /** 68 | * @var string associativity of the operator; left or right 69 | */ 70 | private $associativity; 71 | /** 72 | * @var integer precedence of the operator 73 | */ 74 | private $precedence; 75 | /** 76 | * @var integer number of operands required by the operator 77 | */ 78 | private $operandCount = 0; 79 | 80 | /** 81 | * SassScriptOperation constructor 82 | * 83 | * @param mixed string: operator symbol; array: operator token 84 | * @return SassScriptOperation 85 | */ 86 | public function __construct($operation) 87 | { 88 | if (is_string($operation)) { 89 | $operation = self::$operators[$operation]; 90 | } 91 | $this->operator = $operation[0]; 92 | if (isset($operation[1])) { 93 | $this->associativity = $operation[1]; 94 | $this->precedence = $operation[2]; 95 | $this->operandCount = (isset($operation[3]) ? $operation[3] : 0); 96 | } 97 | } 98 | 99 | /** 100 | * Getter function for properties 101 | * @param string $name name of property 102 | * @return mixed value of the property 103 | * @throws SassScriptOperationException if the property does not exist 104 | */ 105 | public function __get($name) 106 | { 107 | if (property_exists($this, $name)) { 108 | return $this->$name; 109 | } else { 110 | throw new SassScriptOperationException('Unknown property: ' . $name, SassScriptParser::$context->node); 111 | } 112 | } 113 | 114 | /** 115 | * Performs this operation. 116 | * @param array $operands operands for the operation. The operands are SassLiterals 117 | * @return SassLiteral the result of the operation 118 | * @throws SassScriptOperationException if the oprand count is incorrect or 119 | * the operation is undefined 120 | */ 121 | public function perform($operands) 122 | { 123 | if (count($operands) !== $this->operandCount) { 124 | throw new SassScriptOperationException('Incorrect operand count for ' . get_class($operands[0]) . '; expected ' . $this->operandCount . ', received ' . count($operands), SassScriptParser::$context->node); 125 | } 126 | 127 | if (!count($operands)) { 128 | return $operands; 129 | } 130 | 131 | // fix a bug of unknown origin 132 | foreach ($operands as $i => $op) { 133 | if (!is_object($op)) { 134 | $operands[] = null; 135 | unset ($operands[$i]); 136 | } 137 | } 138 | $operands = array_values($operands); 139 | 140 | if (count($operands) > 1 && $operands[1] === null) { 141 | $operation = 'op_unary_' . $this->operator; 142 | } else { 143 | $operation = 'op_' . $this->operator; 144 | if ($this->associativity == 'l') { 145 | $operands = array_reverse($operands); 146 | } 147 | } 148 | 149 | if (method_exists($operands[0], $operation)) { 150 | $op = clone $operands[0]; 151 | 152 | return $op->$operation(!empty($operands[1]) ? $operands[1] : null); 153 | } 154 | 155 | # avoid failures in case of null operands 156 | $count = count($operands); 157 | foreach ($operands as $i => $op) { 158 | if ($op === null) { 159 | $count--; 160 | } 161 | } 162 | 163 | if ($count) { 164 | throw new SassScriptOperationException('Undefined operation "' . $operation . '" for ' . get_class($operands[0]), SassScriptParser::$context->node); 165 | } 166 | } 167 | 168 | /** 169 | * Returns a value indicating if a token of this type can be matched at 170 | * the start of the subject string. 171 | * @param string $subject the subject string 172 | * @return mixed match at the start of the string or false if no match 173 | */ 174 | public static function isa($subject) 175 | { 176 | # begins with a "/x", almost always a path without quotes. 177 | if (preg_match('/^\/[^0-9\.\-\s]+/', $subject)) { 178 | return FALSE; 179 | } 180 | 181 | return (preg_match(self::MATCH, $subject, $matches) ? trim($matches[1]) : false); 182 | } 183 | 184 | /** 185 | * Converts the operation back into it's SASS representation 186 | */ 187 | public function __toString() 188 | { 189 | foreach (SassScriptOperation::$operators as $char => $operator) { 190 | if ($operator[0] == trim($this->operator)) { 191 | return $char; 192 | } 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /lib/phpsass/script/SassScriptParser.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.script 10 | */ 11 | 12 | require_once 'SassScriptLexer.php'; 13 | require_once 'SassScriptParserExceptions.php'; 14 | 15 | /** 16 | * SassScriptParser class. 17 | * Parses SassScript. SassScript is lexed into {@link http://en.wikipedia.org/wiki/Reverse_Polish_notation Reverse Polish notation} by the SassScriptLexer and 18 | * the calculated result returned. 19 | * @package PHamlP 20 | * @subpackage Sass.script 21 | */ 22 | class SassScriptParser 23 | { 24 | const MATCH_INTERPOLATION = '/(?lexer = new SassScriptLexer($this); 51 | self::$instance = $this; 52 | } 53 | 54 | /** 55 | * Replace interpolated SassScript contained in '#{}' with the parsed value. 56 | * @param string $string the text to interpolate 57 | * @param SassContext $context the context in which the string is interpolated 58 | * @return string the interpolated text 59 | */ 60 | public function interpolate($string, $context) 61 | { 62 | for ($i = 0, $n = preg_match_all(self::MATCH_INTERPOLATION, $string, $matches); $i < $n; $i++) { 63 | $var = $this->evaluate($matches[1][$i], $context); 64 | 65 | if ($var instanceOf SassString) { 66 | $var = $var->value; 67 | } else { 68 | $var = $var->toString(); 69 | } 70 | 71 | if (preg_match('/^unquote\((["\'])(.*)\1\)$/', $var, $match)) { 72 | $val = $match[2]; 73 | } elseif ($var == '""') { 74 | $val = ""; 75 | } elseif (preg_match('/^(["\'])(.*)\1$/', $var, $match)) { 76 | $val = $match[2]; 77 | } else { 78 | $val = $var; 79 | } 80 | $matches[1][$i] = $val; 81 | } 82 | 83 | return str_replace($matches[0], $matches[1], $string); 84 | } 85 | 86 | /** 87 | * Evaluate a SassScript. 88 | * @param string $expression expression to parse 89 | * @param SassContext $context the context in which the expression is evaluated 90 | * @param integer $environment the environment in which the expression is evaluated 91 | * @return SassLiteral parsed value 92 | */ 93 | public function evaluate($expression, $context, $environment = self::DEFAULT_ENV) 94 | { 95 | self::$context = $context; 96 | $operands = array(); 97 | 98 | $tokens = $this->parse($expression, $context, $environment); 99 | 100 | while (count($tokens)) { 101 | $token = array_shift($tokens); 102 | if ($token instanceof SassScriptFunction) { 103 | $perform = $token->perform(); 104 | array_push($operands, $perform); 105 | } elseif ($token instanceof SassLiteral) { 106 | if ($token instanceof SassString) { 107 | $token = new SassString($this->interpolate($token->toString(), self::$context)); 108 | } 109 | array_push($operands, $token); 110 | } else { 111 | $args = array(); 112 | for ($i = 0, $c = $token->operandCount; $i < $c; $i++) { 113 | $args[] = array_pop($operands); 114 | } 115 | array_push($operands, $token->perform($args)); 116 | } 117 | } 118 | 119 | return self::makeSingular($operands); 120 | } 121 | 122 | /** 123 | * Parse SassScript to a set of tokens in RPN 124 | * using the Shunting Yard Algorithm. 125 | * @param string $expression expression to parse 126 | * @param SassContext $context the context in which the expression is parsed 127 | * @param integer $environment the environment in which the expression is parsed 128 | * @return array tokens in RPN 129 | */ 130 | public function parse($expression, $context, $environment=self::DEFAULT_ENV) 131 | { 132 | $outputQueue = array(); 133 | $operatorStack = array(); 134 | $parenthesis = 0; 135 | 136 | $tokens = $this->lexer->lex($expression, $context); 137 | 138 | foreach ($tokens as $i=>$token) { 139 | // If two literals/expessions are seperated by whitespace use the concat operator 140 | if (empty($token)) { 141 | if (isset($tokens[$i+1])) { 142 | if ($i > 0 && (!$tokens[$i-1] instanceof SassScriptOperation || $tokens[$i-1]->operator === SassScriptOperation::$operators[')'][0]) && 143 | (!$tokens[$i+1] instanceof SassScriptOperation || $tokens[$i+1]->operator === SassScriptOperation::$operators['('][0])) { 144 | $token = new SassScriptOperation(SassScriptOperation::$defaultOperator, $context); 145 | } else { 146 | continue; 147 | } 148 | } 149 | } elseif ($token instanceof SassScriptVariable) { 150 | $token = $token->evaluate($context); 151 | $environment = self::DEFAULT_ENV; 152 | } 153 | 154 | // If the token is a number or function add it to the output queue. 155 | if ($token instanceof SassLiteral || $token instanceof SassScriptFunction) { 156 | if ($environment === self::CSS_PROPERTY && $token instanceof SassNumber && !$parenthesis) { 157 | $token->inExpression = false; 158 | } 159 | array_push($outputQueue, $token); 160 | } 161 | // If the token is an operation 162 | elseif ($token instanceof SassScriptOperation) { 163 | // If the token is a left parenthesis push it onto the stack. 164 | if ($token->operator == SassScriptOperation::$operators['('][0]) { 165 | array_push($operatorStack, $token); 166 | $parenthesis++; 167 | } 168 | // If the token is a right parenthesis: 169 | elseif ($token->operator == SassScriptOperation::$operators[')'][0]) { 170 | $parenthesis--; 171 | while ($c = count($operatorStack)) { 172 | // If the token at the top of the stack is a left parenthesis 173 | if ($operatorStack[$c - 1]->operator == SassScriptOperation::$operators['('][0]) { 174 | // Pop the left parenthesis from the stack, but not onto the output queue. 175 | array_pop($operatorStack); 176 | break; 177 | } 178 | // else pop the operator off the stack onto the output queue. 179 | array_push($outputQueue, array_pop($operatorStack)); 180 | } 181 | // If the stack runs out without finding a left parenthesis 182 | // there are mismatched parentheses. 183 | if ($c <= 0) { 184 | array_push($outputQueue, new SassString(')')); 185 | break; 186 | } 187 | } 188 | // the token is an operator, o1, so: 189 | else { 190 | // while there is an operator, o2, at the top of the stack 191 | while ($c = count($operatorStack)) { 192 | $operation = $operatorStack[$c - 1]; 193 | // if o2 is left parenthesis, or 194 | // the o1 has left associativty and greater precedence than o2, or 195 | // the o1 has right associativity and lower or equal precedence than o2 196 | if (($operation->operator == SassScriptOperation::$operators['('][0]) || 197 | ($token->associativity == 'l' && $token->precedence > $operation->precedence) || 198 | ($token->associativity == 'r' && $token->precedence <= $operation->precedence)) { 199 | break; // stop checking operators 200 | } 201 | //pop o2 off the stack and onto the output queue 202 | array_push($outputQueue, array_pop($operatorStack)); 203 | } 204 | // push o1 onto the stack 205 | array_push($operatorStack, $token); 206 | } 207 | } 208 | } 209 | 210 | // When there are no more tokens 211 | while ($c = count($operatorStack)) { // While there are operators on the stack: 212 | if ($operatorStack[$c - 1]->operator !== SassScriptOperation::$operators['('][0]) { 213 | array_push($outputQueue, array_pop($operatorStack)); 214 | } else { 215 | throw new SassScriptParserException('Unmatched parentheses', $context->node); 216 | } 217 | } 218 | 219 | return $outputQueue; 220 | } 221 | 222 | /** 223 | * Reduces a set down to a singular form 224 | */ 225 | public static function makeSingular($operands) 226 | { 227 | if (count($operands) == 1) { 228 | return $operands[0]; 229 | } 230 | 231 | $result = null; 232 | foreach ($operands as $i => $operand) { 233 | if (is_object($operand)) { 234 | if (!$result) { 235 | $result = $operand; 236 | continue; 237 | } 238 | if ($result instanceOf SassString) { 239 | $result = $result->op_concat($operand); 240 | } else { 241 | $result = $result->op_plus($operand); 242 | } 243 | } else { 244 | $string = new SassString(' '); 245 | if (!$result) { 246 | $result = $string; 247 | } else { 248 | $result = $result->op_plus($string); 249 | } 250 | } 251 | } 252 | 253 | return $result ? $result : array_shift($operands); 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /lib/phpsass/script/SassScriptParserExceptions.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.script 10 | */ 11 | 12 | require_once(dirname(__FILE__).'/../SassException.php'); 13 | 14 | /** 15 | * SassScriptParserException class. 16 | * @package PHamlP 17 | * @subpackage Sass.script 18 | */ 19 | class SassScriptParserException extends SassException {} 20 | 21 | /** 22 | * SassScriptLexerException class. 23 | * @package PHamlP 24 | * @subpackage Sass.script 25 | */ 26 | class SassScriptLexerException extends SassScriptParserException {} 27 | 28 | /** 29 | * SassScriptOperationException class. 30 | * @package PHamlP 31 | * @subpackage Sass.script 32 | */ 33 | class SassScriptOperationException extends SassScriptParserException {} 34 | 35 | /** 36 | * SassScriptFunctionException class. 37 | * @package PHamlP 38 | * @subpackage Sass.script 39 | */ 40 | class SassScriptFunctionException extends SassScriptParserException {} 41 | -------------------------------------------------------------------------------- /lib/phpsass/script/SassScriptVariable.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.script.literals 10 | */ 11 | 12 | /** 13 | * SassVariable class. 14 | * @package PHamlP 15 | * @subpackage Sass.script.literals 16 | */ 17 | class SassScriptVariable 18 | { 19 | /** 20 | * Regex for matching and extracting Variables 21 | */ 22 | const MATCH = '/^(?name = substr($value, 1); 36 | } 37 | 38 | /** 39 | * Returns the SassScript object for this variable. 40 | * @param SassContext $context context of the variable 41 | * @return SassLiteral the SassScript object for this variable 42 | */ 43 | public function evaluate($context) 44 | { 45 | return $context->getVariable($this->name); 46 | } 47 | 48 | /** 49 | * Returns a value indicating if a token of this type can be matched at 50 | * the start of the subject string. 51 | * @param string $subject the subject string 52 | * @return mixed match at the start of the string or false if no match 53 | */ 54 | public static function isa($subject) 55 | { 56 | // we need to do the check as preg_match returns a count of 1 if 57 | // subject == '!important'; the match being an empty match 58 | return (preg_match(self::MATCH, $subject, $matches) ? (empty($matches[0]) ? false : $matches[0]) : false); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/phpsass/script/literals/SassBoolean.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.script.literals 10 | */ 11 | 12 | require_once 'SassLiteral.php'; 13 | 14 | /** 15 | * SassBoolean class. 16 | * @package PHamlP 17 | * @subpackage Sass.script.literals 18 | */ 19 | class SassBoolean extends SassLiteral 20 | { 21 | /**@#+ 22 | * Regex for matching and extracting booleans 23 | */ 24 | const MATCH = '/^(true|false)\b/'; 25 | 26 | /** 27 | * SassBoolean constructor 28 | * @param string $value value of the boolean type 29 | * @throws SassBooleanException 30 | * @return SassBoolean 31 | */ 32 | public function __construct($value) 33 | { 34 | if (is_bool($value)) { 35 | $this->value = $value; 36 | } elseif ($value === 'true' || $value === 'false') { 37 | $this->value = ($value === 'true' ? true : false); 38 | } else { 39 | throw new SassBooleanException('Invalid SassBoolean', SassScriptParser::$context->node); 40 | } 41 | } 42 | 43 | /** 44 | * Returns the value of this boolean. 45 | * @return boolean the value of this boolean 46 | */ 47 | public function getValue() 48 | { 49 | return $this->value; 50 | } 51 | 52 | /** 53 | * Returns a string representation of the value. 54 | * @return string string representation of the value. 55 | */ 56 | public function toString() 57 | { 58 | return $this->getValue() ? 'true' : 'false'; 59 | } 60 | 61 | public function length() 62 | { 63 | return 1; 64 | } 65 | 66 | public function nth($i) 67 | { 68 | if ($i == 1 && isset($this->value)) { 69 | return new SassBoolean($this->value); 70 | } 71 | 72 | return new SassBoolean(false); 73 | } 74 | 75 | /** 76 | * Returns a value indicating if a token of this type can be matched at 77 | * the start of the subject string. 78 | * @param string $subject the subject string 79 | * @return mixed match at the start of the string or false if no match 80 | */ 81 | public static function isa($subject) 82 | { 83 | return (preg_match(self::MATCH, $subject, $matches) ? $matches[0] : false); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/phpsass/script/literals/SassList.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.script.literals 10 | */ 11 | 12 | require_once 'SassLiteral.php'; 13 | 14 | /** 15 | * SassBoolean class. 16 | * @package PHamlP 17 | * @subpackage Sass.script.literals 18 | */ 19 | class SassList extends SassLiteral 20 | { 21 | public $separator = ' '; 22 | 23 | /** 24 | * SassBoolean constructor 25 | * @param string $value value of the boolean type 26 | * @param string $separator 27 | * @return SassBoolean 28 | */ 29 | public function __construct($value, $separator = 'auto') 30 | { 31 | if (is_array($value)) { 32 | $this->value = $value; 33 | $this->separator = ($separator == 'auto' ? ', ' : $separator); 34 | } elseif ($value == '()') { 35 | $this->value = array(); 36 | $this->separator = ($separator == 'auto' ? ', ' : $separator); 37 | } elseif (list($list, $separator) = $this->_parse_list($value, $separator, true, SassScriptParser::$context)) { 38 | $this->value = $list; 39 | $this->separator = ($separator == ',' ? ', ' : ' '); 40 | } else { 41 | throw new SassListException('Invalid SassList', SassScriptParser::$context->node); 42 | } 43 | } 44 | 45 | public function nth($i) 46 | { 47 | $i = $i - 1; # SASS uses 1-offset arrays 48 | if (isset($this->value[$i])) { 49 | return $this->value[$i]; 50 | } 51 | 52 | return new SassBoolean(false); 53 | } 54 | 55 | public function length() 56 | { 57 | return count($this->value); 58 | } 59 | 60 | public function append($other, $separator = null) 61 | { 62 | if ($separator) { 63 | $this->separator = $separator; 64 | } 65 | if ($other instanceof SassList) { 66 | $this->value = array_merge($this->value, $other->value); 67 | } elseif ($other instanceof SassLiteral) { 68 | $this->value[] = $other; 69 | } else { 70 | throw new SassListException('Appendation can only occur with literals', SassScriptParser::$context->node); 71 | } 72 | } 73 | 74 | // New function index returns the list index of a value within a list. For example: index(1px solid red, solid) returns 2. When the value is not found false is returned. 75 | public function index($value) 76 | { 77 | for ($i = 0; $i < count($this->value); $i++) { 78 | if (trim((string) $value) == trim((string) $this->value[$i])) { 79 | return new SassNumber($i); 80 | } 81 | } 82 | 83 | return new SassBoolean(false); 84 | } 85 | 86 | /** 87 | * Returns the value of this boolean. 88 | * @return boolean the value of this boolean 89 | */ 90 | public function getValue() 91 | { 92 | $result = array(); 93 | foreach ($this->value as $k => $v) { 94 | if ($v instanceOf SassString) { 95 | $list = $this->_parse_list($v); 96 | if (count($list[0]) > 1) { 97 | if ($list[1] == $this->separator) { 98 | $result = array_merge($result, $list[0]); 99 | } else { 100 | $result[] = $v; 101 | } 102 | } else { 103 | $result[] = $v; 104 | } 105 | } else { 106 | $result[] = $v; 107 | } 108 | } 109 | $this->value = $result; 110 | 111 | return $this->value; 112 | } 113 | 114 | /** 115 | * Returns a string representation of the value. 116 | * @return string string representation of the value. 117 | */ 118 | public function toString() 119 | { 120 | $aliases = array( 121 | 'comma' => ',', 122 | 'space' => '', 123 | ); 124 | $this->separator = trim($this->separator); 125 | if (isset($aliases[$this->separator])) { 126 | $this->separator = $aliases[$this->separator]; 127 | } 128 | 129 | return implode($this->separator . ' ', $this->getValue()); 130 | } 131 | 132 | /** 133 | * Returns a value indicating if a token of this type can be matched at 134 | * the start of the subject string. 135 | * @param string $subject the subject string 136 | * @return mixed match at the start of the string or false if no match 137 | */ 138 | public static function isa($subject) 139 | { 140 | list($list, $separator) = self::_parse_list($subject, 'auto', false); 141 | 142 | return count($list) > 1 ? $subject : FALSE; 143 | } 144 | 145 | public static function _parse_list($list, $separator = 'auto', $lex = true, $context = null) 146 | { 147 | if ($lex) { 148 | $context = new SassContext($context); 149 | $list = SassScriptParser::$instance->evaluate($list, $context); 150 | $list = $list->toString(); 151 | } 152 | if ($separator == 'auto') { 153 | $separator = ','; 154 | $list = $list = self::_build_list($list, ','); 155 | if (count($list) < 2) { 156 | $separator = ' '; 157 | $list = self::_build_list($list, ' '); 158 | } 159 | } else { 160 | $list = self::_build_list($list, $separator); 161 | } 162 | 163 | if ($lex) { 164 | $context = new SassContext($context); 165 | foreach ($list as $k => $v) { 166 | $list[$k] = SassScriptParser::$instance->evaluate($v, $context); 167 | } 168 | } 169 | 170 | return array($list, $separator); 171 | } 172 | 173 | public static function _build_list($list, $separator = ',') 174 | { 175 | if (is_object($list)) { 176 | $list = $list->value; 177 | } 178 | 179 | if (is_array($list)) { 180 | $newlist = array(); 181 | foreach ($list as $listlet) { 182 | list($newlist, $separator) = array_merge($newlist, self::_parse_list($listlet, $separator, false)); 183 | } 184 | $list = implode(', ', $newlist); 185 | } 186 | 187 | $out = array(); 188 | $size = 0; 189 | $braces = 0; 190 | $quotes = false; 191 | $stack = ''; 192 | $listCount = strlen($list); 193 | for ($i = 0; $i < $listCount; $i++) { 194 | $char = substr($list, $i, 1); 195 | switch ($char) { 196 | case '"': 197 | case "'": 198 | if (!$quotes) { 199 | $quotes = $char; 200 | } elseif ($quotes && $quotes == $char) { 201 | $quotes = false; 202 | } 203 | $stack .= $char; 204 | break; 205 | case '(': 206 | $braces++; 207 | $stack .= $char; 208 | break; 209 | case ')': 210 | $braces--; 211 | $stack .= $char; 212 | break; 213 | case $separator: 214 | if ($braces === 0 && !$quotes) { 215 | $out[] = $stack; 216 | $stack = ''; 217 | $size++; 218 | break; 219 | } 220 | default: 221 | $stack .= $char; 222 | } 223 | } 224 | if (strlen($stack)) { 225 | if (($braces || $quotes) && count($out)) { 226 | $out[count($out) - 1] .= $stack; 227 | } else { 228 | $out[] = $stack; 229 | } 230 | } 231 | 232 | foreach ($out as $k => $v) { 233 | $out[$k] = trim($v, ', '); 234 | } 235 | 236 | return $out; 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /lib/phpsass/script/literals/SassLiteral.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.script.literals 10 | */ 11 | 12 | require_once 'SassLiteralExceptions.php'; 13 | 14 | /** 15 | * SassLiteral class. 16 | * Base class for all Sass literals. 17 | * Sass data types are extended from this class and these override the operation 18 | * methods to provide the appropriate semantics. 19 | * @package PHamlP 20 | * @subpackage Sass.script.literals 21 | */ 22 | abstract class SassLiteral 23 | { 24 | /** 25 | * @var array maps class names to data types 26 | */ 27 | public static $typeOf = array( 28 | 'SassBoolean' => 'bool', 29 | 'SassColour' => 'color', 30 | 'SassNumber' => 'number', 31 | 'SassString' => 'string', 32 | 'SassList' => 'list' 33 | ); 34 | 35 | /** 36 | * @var mixed value of the literal type 37 | */ 38 | public $value; 39 | 40 | /** 41 | * class constructor 42 | * @param string $value value of the literal type 43 | * @return SassLiteral 44 | */ 45 | public function __construct($value = null, $context) 46 | { 47 | $this->value = $value; 48 | $this->context = $context; 49 | } 50 | 51 | /** 52 | * Getter. 53 | * @param string $name name of property to get 54 | * @return mixed return value of getter function 55 | */ 56 | public function __get($name) 57 | { 58 | $getter = 'get' . ucfirst($name); 59 | if (method_exists($this, $getter)) { 60 | return $this->$getter(); 61 | } else { 62 | throw new SassLiteralException('No getter function for ' . $name, SassScriptParser::$context->node); 63 | } 64 | } 65 | 66 | public function __toString() 67 | { 68 | return $this->toString(); 69 | } 70 | 71 | /** 72 | * Returns the boolean representation of the value of this 73 | * @return boolean the boolean representation of the value of this 74 | */ 75 | public function toBoolean() 76 | { 77 | return (boolean) $this->value || $this->value === null; 78 | } 79 | 80 | /** 81 | * Returns the type of this 82 | * @return string the type of this 83 | */ 84 | public function getTypeOf() 85 | { 86 | return self::$typeOf[get_class($this)]; 87 | } 88 | 89 | /** 90 | * Returns the value of this 91 | * @return mixed the value of this 92 | */ 93 | public function getValue() 94 | { 95 | throw new SassLiteralException('Child classes must override this method', SassScriptParser::$context->node); 96 | } 97 | 98 | public function getChildren() 99 | { 100 | return array(); 101 | } 102 | 103 | /** 104 | * Adds a child object to this. 105 | * @param sassLiteral $sassLiteral the child object 106 | */ 107 | public function addChild($sassLiteral) 108 | { 109 | $this->children[] = $sassLiteral; 110 | } 111 | 112 | /** 113 | * SassScript '+' operation. 114 | * @param sassLiteral $other value to add 115 | * @return sassString the string values of this and other with no seperation 116 | */ 117 | public function op_plus($other) 118 | { 119 | return new SassString($this->toString().$other->toString()); 120 | } 121 | 122 | /** 123 | * SassScript '-' operation. 124 | * @param SassLiteral $other value to subtract 125 | * @return sassString the string values of this and other seperated by '-' 126 | */ 127 | public function op_minus($other) 128 | { 129 | return new SassString($this->toString().'-'.$other->toString()); 130 | } 131 | 132 | /** 133 | * SassScript '*' operation. 134 | * @param SassLiteral $other value to multiply by 135 | * @return sassString the string values of this and other seperated by '*' 136 | */ 137 | public function op_times($other) 138 | { 139 | return new SassString($this->toString().'*'.$other->toString()); 140 | } 141 | 142 | /** 143 | * SassScript '/' operation. 144 | * @param SassLiteral $other value to divide by 145 | * @return sassString the string values of this and other seperated by '/' 146 | */ 147 | public function op_div($other) 148 | { 149 | return new SassString($this->toString().' / '.$other->toString()); 150 | } 151 | 152 | /** 153 | * SassScript '%' operation. 154 | * @param SassLiteral $other value to take the modulus of 155 | * @return SassLiteral result 156 | * @throws Exception if modulo not supported for the data type 157 | */ 158 | public function op_modulo($other) 159 | { 160 | throw new SassLiteralException(get_class($this) . ' does not support Modulus', SassScriptParser::$context->node); 161 | } 162 | 163 | /** 164 | * Bitwise AND the value of other and this value 165 | * @param string $other value to bitwise AND with 166 | * @return string result 167 | * @throws Exception if bitwise AND not supported for the data type 168 | */ 169 | public function op_bw_and($other) 170 | { 171 | throw new SassLiteralException(get_class($this) . ' does not support Bitwise AND', SassScriptParser::$context->node); 172 | } 173 | 174 | /** 175 | * Bitwise OR the value of other and this value 176 | * @param SassNumber $other value to bitwise OR with 177 | * @return string result 178 | * @throws Exception if bitwise OR not supported for the data type 179 | */ 180 | public function op_bw_or($other) 181 | { 182 | throw new SassLiteralException(get_class($this) . ' does not support Bitwise OR', SassScriptParser::$context->node); 183 | } 184 | 185 | /** 186 | * Bitwise XOR the value of other and the value of this 187 | * @param SassNumber $other value to bitwise XOR with 188 | * @return string result 189 | * @throws Exception if bitwise XOR not supported for the data type 190 | */ 191 | public function op_bw_xor($other) 192 | { 193 | throw new SassLiteralException(get_class($this) . ' does not support Bitwise XOR', SassScriptParser::$context->node); 194 | } 195 | 196 | /** 197 | * Bitwise NOT the value of other and the value of this 198 | * @return string result 199 | * @throws Exception if bitwise NOT not supported for the data type 200 | */ 201 | public function op_bw_not() 202 | { 203 | throw new SassLiteralException(get_class($this) . ' does not support Bitwise NOT', SassScriptParser::$context->node); 204 | } 205 | 206 | /** 207 | * Shifts the value of this left by the number of bits given in value 208 | * @param SassNumber $other amount to shift left by 209 | * @return string result 210 | * @throws Exception if bitwise Shift Left not supported for the data type 211 | */ 212 | public function op_shiftl($other) 213 | { 214 | throw new SassLiteralException(get_class($this) . ' does not support Bitwise Shift Left', SassScriptParser::$context->node); 215 | } 216 | 217 | /** 218 | * Shifts the value of this right by the number of bits given in value 219 | * @param SassNumber $other amount to shift right by 220 | * @return string result 221 | * @throws Exception if bitwise Shift Right not supported for the data type 222 | */ 223 | public function op_shiftr($other) 224 | { 225 | throw new SassLiteralException(get_class($this) . ' does not support Bitwise Shift Right', SassScriptParser::$context->node); 226 | } 227 | 228 | /** 229 | * The SassScript and operation. 230 | * @param sassLiteral $other the value to and with this 231 | * @return SassLiteral other if this is boolean true, this if false 232 | */ 233 | public function op_and($other) 234 | { 235 | return ($this->toBoolean() ? $other : $this); 236 | } 237 | 238 | /** 239 | * The SassScript or operation. 240 | * @param sassLiteral $other the value to or with this 241 | * @return SassLiteral this if this is boolean true, other if false 242 | */ 243 | public function op_or($other) 244 | { 245 | return ($this->toBoolean() ? $this : $other); 246 | } 247 | 248 | public function op_assign($other) 249 | { 250 | return $other; 251 | } 252 | 253 | /** 254 | * The SassScript xor operation. 255 | * @param sassLiteral $other the value to xor with this 256 | * @return SassBoolean SassBoolean object with the value true if this or 257 | * other, but not both, are true, false if not 258 | */ 259 | public function op_xor($other) 260 | { 261 | return new SassBoolean($this->toBoolean() xor $other->toBoolean()); 262 | } 263 | 264 | /** 265 | * The SassScript not operation. 266 | * @return SassBoolean SassBoolean object with the value true if the 267 | * boolean of this is false or false if it is true 268 | */ 269 | public function op_not() 270 | { 271 | return new SassBoolean(!$this->toBoolean()); 272 | } 273 | 274 | /** 275 | * The SassScript > operation. 276 | * @param sassLiteral $other the value to compare to this 277 | * @return SassBoolean SassBoolean object with the value true if the values 278 | * of this is greater than the value of other, false if it is not 279 | */ 280 | public function op_gt($other) 281 | { 282 | return new SassBoolean($this->value > $other->value); 283 | } 284 | 285 | /** 286 | * The SassScript >= operation. 287 | * @param sassLiteral $other the value to compare to this 288 | * @return SassBoolean SassBoolean object with the value true if the values 289 | * of this is greater than or equal to the value of other, false if it is not 290 | */ 291 | public function op_gte($other) 292 | { 293 | return new SassBoolean($this->value >= $other->value); 294 | } 295 | 296 | /** 297 | * The SassScript < operation. 298 | * @param sassLiteral $other the value to compare to this 299 | * @return SassBoolean SassBoolean object with the value true if the values 300 | * of this is less than the value of other, false if it is not 301 | */ 302 | public function op_lt($other) 303 | { 304 | return new SassBoolean($this->value < $other->value); 305 | } 306 | 307 | /** 308 | * The SassScript <= operation. 309 | * @param sassLiteral $other the value to compare to this 310 | * @return SassBoolean SassBoolean object with the value true if the values 311 | * of this is less than or equal to the value of other, false if it is not 312 | */ 313 | public function op_lte($other) 314 | { 315 | return new SassBoolean($this->value <= $other->value); 316 | } 317 | 318 | /** 319 | * The SassScript == operation. 320 | * @param sassLiteral $other the value to compare to this 321 | * @return SassBoolean SassBoolean object with the value true if this and 322 | * other are equal, false if they are not 323 | */ 324 | public function op_eq($other) 325 | { 326 | return new SassBoolean($this == $other); 327 | } 328 | 329 | /** 330 | * The SassScript != operation. 331 | * @param sassLiteral $other the value to compare to this 332 | * @return SassBoolean SassBoolean object with the value true if this and 333 | * other are not equal, false if they are 334 | */ 335 | public function op_neq($other) 336 | { 337 | return new SassBoolean(!$this->op_eq($other)->toBoolean()); 338 | } 339 | 340 | /** 341 | * The SassScript default operation (e.g. $a $b, "foo" "bar"). 342 | * @param sassLiteral $other the value to concatenate with a space to this 343 | * @return sassString the string values of this and other seperated by " " 344 | */ 345 | public function op_concat($other) 346 | { 347 | return new SassString($this->toString().' '.$other->toString()); 348 | } 349 | 350 | /** 351 | * SassScript ',' operation. 352 | * @param sassLiteral $other the value to concatenate with a comma to this 353 | * @return sassString the string values of this and other seperated by "," 354 | */ 355 | public function op_comma($other) 356 | { 357 | return new SassString($this->toString().', '.$other->toString()); 358 | } 359 | 360 | /** 361 | * Asserts that the literal is the expected type 362 | * @param SassLiteral $other the literal to test 363 | * @param string $type expected type 364 | * @throws SassScriptFunctionException if value is not the expected type 365 | */ 366 | public static function assertType($literal, $type) 367 | { 368 | if (!$literal instanceof $type) { 369 | throw new SassScriptFunctionException(($literal instanceof SassLiteral ? get_class($literal) : 'literal') . ' must be a ' . $type, SassScriptParser::$context->node); 370 | } 371 | } 372 | 373 | /** 374 | * Asserts that the value of a literal is within the expected range 375 | * @param SassLiteral $literal the literal to test 376 | * @param float $min the minimum value 377 | * @param float $max the maximum value 378 | * @param string $units the units. 379 | * @throws SassScriptFunctionException if value is not the expected type 380 | */ 381 | public static function assertInRange($literal, $min, $max, $units = '') 382 | { 383 | if ($literal->value < $min || $literal->value > $max) { 384 | throw new SassScriptFunctionException($literal->typeOf . ' must be between ' . $min.$units . ' and ' . $max.$units . ' inclusive', SassScriptParser::$context->node); 385 | } 386 | } 387 | 388 | /** 389 | * Returns a string representation of the value. 390 | * @return string string representation of the value. 391 | */ 392 | abstract public function toString(); 393 | 394 | public function render() 395 | { 396 | return $this->toString(); 397 | } 398 | 399 | /** 400 | * Returns a value indicating if a token of this type can be matched at 401 | * the start of the subject string. 402 | * @param string $subject the subject string 403 | * @return mixed match at the start of the string or false if no match 404 | */ 405 | public static function isa($subject) 406 | { 407 | throw new SassLiteralException('Child classes must override this method'); 408 | } 409 | } 410 | -------------------------------------------------------------------------------- /lib/phpsass/script/literals/SassLiteralExceptions.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.script.literals 10 | */ 11 | 12 | require_once(dirname(__FILE__).'/../SassScriptParserExceptions.php'); 13 | 14 | /** 15 | * Sass literal exception. 16 | * @package PHamlP 17 | * @subpackage Sass.script.literals 18 | */ 19 | class SassLiteralException extends SassScriptParserException {} 20 | 21 | /** 22 | * SassBooleanException class. 23 | * @package PHamlP 24 | * @subpackage Sass.script.literals 25 | */ 26 | class SassBooleanException extends SassLiteralException {} 27 | 28 | /** 29 | * SassColourException class. 30 | * @package PHamlP 31 | * @subpackage Sass.script.literals 32 | */ 33 | class SassColourException extends SassLiteralException {} 34 | 35 | /** 36 | * SassListException class. 37 | * @package PHamlP 38 | * @subpackage Sass.script.literals 39 | */ 40 | class SassListException extends SassLiteralException {} 41 | 42 | /** 43 | * SassNumberException class. 44 | * @package PHamlP 45 | * @subpackage Sass.script.literals 46 | */ 47 | class SassNumberException extends SassLiteralException {} 48 | 49 | /** 50 | * SassStringException class. 51 | * @package PHamlP 52 | * @subpackage Sass.script.literals 53 | */ 54 | class SassStringException extends SassLiteralException {} 55 | -------------------------------------------------------------------------------- /lib/phpsass/script/literals/SassString.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.script.literals 10 | */ 11 | 12 | require_once 'SassLiteral.php'; 13 | 14 | /** 15 | * SassString class. 16 | * Provides operations and type testing for Sass strings. 17 | * @package PHamlP 18 | * @subpackage Sass.script.literals 19 | */ 20 | class SassString extends SassLiteral 21 | { 22 | const MATCH = '/^(((["\'])(.*?)(\3))|(-[a-zA-Z-]+[^\s]*?))/i'; 23 | const _MATCH = '/^(["\'])(.*?)(\1)?$/'; // Used to match strings such as "Times New Roman",serif 24 | const VALUE = 2; 25 | const QUOTE = 3; 26 | 27 | /** 28 | * @var string string quote type; double or single quotes, or unquoted. 29 | */ 30 | public $quote; 31 | 32 | /** 33 | * class constructor 34 | * @param string string 35 | * @return SassString 36 | */ 37 | public function __construct($value) 38 | { 39 | preg_match(self::_MATCH, $value, $matches); 40 | if ((isset($matches[self::QUOTE]))) { 41 | $this->quote = $matches[self::QUOTE]; 42 | $this->value = $matches[self::VALUE]; 43 | } else { 44 | $this->quote = ''; 45 | $this->value = $value; 46 | } 47 | } 48 | 49 | /** 50 | * String addition. 51 | * Concatenates this and other. 52 | * The resulting string will be quoted in the same way as this. 53 | * @param sassString string to add to this 54 | * @return sassString the string result 55 | */ 56 | public function op_plus($other) 57 | { 58 | $this->value .= $other->value; 59 | 60 | return $this; 61 | } 62 | 63 | /** 64 | * String multiplication. 65 | * this is repeated other times 66 | * @param sassNumber $other the number of times to repeat this 67 | * @return sassString the string result 68 | */ 69 | public function op_times($other) 70 | { 71 | if (!($other instanceof SassNumber) || !$other->isUnitless()) { 72 | throw new SassStringException('Value must be a unitless number', SassScriptParser::$context->node); 73 | } 74 | $this->value = str_repeat($this->value, $other->value); 75 | 76 | return $this; 77 | } 78 | 79 | /** 80 | * Equals - works better 81 | */ 82 | public function op_eq($other) 83 | { 84 | return new SassBoolean($this->value == $other->value || $this->toString() == $other->toString()); 85 | } 86 | 87 | /** 88 | * Evaluates the value as a boolean. 89 | */ 90 | public function toBoolean() 91 | { 92 | $value = strtolower(trim($this->value, ' "\'')); 93 | if (!$value || in_array($value, array('false', 'null', '0'))) { 94 | return FALSE; 95 | } 96 | 97 | return TRUE; 98 | } 99 | 100 | /** 101 | * Returns the value of this string. 102 | * @return string the string 103 | */ 104 | public function getValue() 105 | { 106 | return $this->value; 107 | } 108 | 109 | /** 110 | * Returns a string representation of the value. 111 | * @return string string representation of the value. 112 | */ 113 | public function toString() 114 | { 115 | if ($this->quote) { 116 | $value = $this->quote . $this->value . $this->quote; 117 | } else { 118 | $value = strlen(trim($this->value)) ? trim($this->value) : $this->value; 119 | } 120 | 121 | return $value; 122 | } 123 | 124 | public function toVar() 125 | { 126 | return SassScriptParser::$context->getVariable($this->value); 127 | } 128 | 129 | public function getTypeOf() 130 | { 131 | if (SassList::isa($this->toString())) { 132 | return 'list'; 133 | } 134 | 135 | return 'string'; 136 | } 137 | 138 | /** 139 | * Returns a value indicating if a token of this type can be matched at 140 | * the start of the subject string. 141 | * @param string the subject string 142 | * @return mixed match at the start of the string or false if no match 143 | */ 144 | public static function isa($subject) 145 | { 146 | return (preg_match(self::MATCH, $subject, $matches) ? $matches[0] : false); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /lib/phpsass/tree/SassCharsetNode.php: -------------------------------------------------------------------------------- 1 | source, $matches); 37 | 38 | if (empty($matches)) { 39 | return new SassBoolean('false'); 40 | } 41 | } 42 | 43 | /** 44 | * Parse this node. 45 | * Set passed arguments and any optional arguments not passed to their 46 | * defaults, then render the children of the return definition. 47 | * @param SassContext $pcontext the context in which this node is parsed 48 | * @return array the parsed node 49 | */ 50 | public function parse($pcontext) 51 | { 52 | return array($this); 53 | } 54 | 55 | public function render() { 56 | // print the original with a semi-colon if needed 57 | return $this->token->source 58 | . (substr($this->token->source, -1, 1) == ';' ? '' : ';') 59 | . "\n"; 60 | } 61 | 62 | /** 63 | * Contents a value indicating if the token represents this type of node. 64 | * @param object $token token 65 | * @return boolean true if the token represents this type of node, false if not 66 | */ 67 | public static function isa($token) 68 | { 69 | return $token->source[0] === self::IDENTIFIER; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/phpsass/tree/SassCommentNode.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.tree 10 | */ 11 | 12 | /** 13 | * SassCommentNode class. 14 | * Represents a CSS comment. 15 | * @package PHamlP 16 | * @subpackage Sass.tree 17 | */ 18 | class SassCommentNode extends SassNode 19 | { 20 | const NODE_IDENTIFIER = '/'; 21 | const MATCH = '%^/\*\s*?(.*?)\s*?(\*/)?$%s'; 22 | const COMMENT = 1; 23 | 24 | private $value; 25 | 26 | /** 27 | * SassCommentNode constructor. 28 | * @param object $token source token 29 | */ 30 | public function __construct($token) 31 | { 32 | parent::__construct($token); 33 | preg_match(self::MATCH, $token->source, $matches); 34 | $this->value = $matches[self::COMMENT]; 35 | } 36 | 37 | protected function getValue() 38 | { 39 | return $this->value; 40 | } 41 | 42 | /** 43 | * Parse this node. 44 | * @param mixed $context 45 | * @return array the parsed node - an empty array 46 | */ 47 | public function parse($context) 48 | { 49 | return array($this); 50 | } 51 | 52 | /** 53 | * Render this node. 54 | * @return string the rendered node 55 | */ 56 | public function render() 57 | { 58 | return $this->renderer->renderComment($this); 59 | } 60 | 61 | /** 62 | * Returns a value indicating if the token represents this type of node. 63 | * @param object $token token 64 | * @return boolean true if the token represents this type of node, false if not 65 | */ 66 | public static function isa($token) 67 | { 68 | return $token->source[0] === self::NODE_IDENTIFIER; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/phpsass/tree/SassContentNode.php: -------------------------------------------------------------------------------- 1 | source, $matches); 37 | 38 | if (empty($matches)) { 39 | return new SassBoolean('false'); 40 | } 41 | } 42 | 43 | /** 44 | * Parse this node. 45 | * Set passed arguments and any optional arguments not passed to their 46 | * defaults, then render the children of the return definition. 47 | * @param SassContext $pcontext the context in which this node is parsed 48 | * @return array the parsed node 49 | */ 50 | public function parse($pcontext) 51 | { 52 | $return = $this; 53 | $context = new SassContext($pcontext); 54 | 55 | $children = array(); 56 | foreach ($context->getContent() as $child) { 57 | $child->parent = $this->parent; 58 | $ctx = new SassContext($pcontext->parent); 59 | $ctx->variables = $pcontext->variables; 60 | $children = array_merge($children, $child->parse($ctx)); 61 | } 62 | 63 | return $children; 64 | } 65 | 66 | /** 67 | * Contents a value indicating if the token represents this type of node. 68 | * @param object $token token 69 | * @return boolean true if the token represents this type of node, false if not 70 | */ 71 | public static function isa($token) 72 | { 73 | return $token->source[0] === self::IDENTIFIER; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/phpsass/tree/SassContext.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.tree 10 | */ 11 | 12 | /** 13 | * SassContext class. 14 | * Defines the context that the parser is operating in and so allows variables 15 | * to be scoped. 16 | * A new context is created for Mixins and imported files. 17 | * @package PHamlP 18 | * @subpackage Sass.tree 19 | */ 20 | class SassContext 21 | { 22 | /** 23 | * @var SassContext enclosing context 24 | */ 25 | public $parent; 26 | 27 | /** 28 | * @var array mixins defined in this context 29 | */ 30 | public $mixins = array(); 31 | 32 | /** 33 | * @var array mixins defined in this context 34 | */ 35 | public $functions = array(); 36 | 37 | /** 38 | * @var array variables defined in this context 39 | */ 40 | public $variables = array(); 41 | 42 | /** 43 | * @var array tree representing any contextual content. 44 | */ 45 | public $content = array(); 46 | 47 | /** 48 | * @var SassNode the node being processed 49 | */ 50 | public $node; 51 | 52 | /** 53 | * SassContext constructor. 54 | * @param SassContext - the enclosing context 55 | * @return SassContext 56 | */ 57 | public function __construct($parent = null) 58 | { 59 | $this->parent = $parent; 60 | } 61 | 62 | /** 63 | * 64 | */ 65 | public function getContent() 66 | { 67 | if ($this->content) { 68 | return $this->content; 69 | } 70 | if (!empty($this->parent)) { 71 | return $this->parent->getContent(); 72 | } 73 | throw new SassContextException('@content requested but no content passed', $this->node); 74 | } 75 | 76 | /** 77 | * Adds a mixin 78 | * @param string $name name of mixin 79 | * @param mixed $mixin 80 | * @return SassMixinDefinitionNode the mixin 81 | */ 82 | public function addMixin($name, $mixin) 83 | { 84 | $this->mixins[$name] = $mixin; 85 | 86 | return $this; 87 | } 88 | 89 | /** 90 | * Returns a mixin 91 | * @param string $name name of mixin to return 92 | * @return SassMixinDefinitionNode the mixin 93 | * @throws SassContextException if mixin not defined in this context 94 | */ 95 | public function getMixin($name) 96 | { 97 | if (isset($this->mixins[$name])) { 98 | return $this->mixins[$name]; 99 | } elseif (!empty($this->parent)) { 100 | return $this->parent->getMixin($name); 101 | } 102 | throw new SassContextException('Undefined Mixin: ' . $name, $this->node); 103 | } 104 | 105 | /** 106 | * Adds a function 107 | * @param string $name name of function 108 | * @param mixed $function 109 | * @return SassFunctionDefinitionNode the function 110 | */ 111 | public function addFunction($name, $function) 112 | { 113 | $this->functions[$name] = $function; 114 | if (!empty($this->parent)) { 115 | $this->parent->addFunction($name, $function); 116 | } 117 | 118 | return $this; 119 | } 120 | 121 | /** 122 | * Returns a function 123 | * @param string $name name of function to return 124 | * @return SassFunctionDefinitionNode the mixin 125 | * @throws SassContextException if function not defined in this context 126 | */ 127 | public function getFunction($name) 128 | { 129 | if ($fn = $this->hasFunction($name)) { 130 | return $fn; 131 | } 132 | throw new SassContextException('Undefined Function: ' . $name, $this->node); 133 | } 134 | 135 | /** 136 | * Returns a boolean wether this function exists 137 | * @param string $name name of function to check for 138 | * @return boolean 139 | */ 140 | public function hasFunction($name) 141 | { 142 | if (isset($this->functions[$name])) { 143 | return $this->functions[$name]; 144 | } elseif (!empty($this->parent)) { 145 | return $this->parent->hasFunction($name); 146 | } 147 | 148 | return FALSE; 149 | } 150 | 151 | /** 152 | * Returns a variable defined in this context 153 | * @param string $name name of variable to return 154 | * @return string the variable 155 | * @throws SassContextException if variable not defined in this context 156 | */ 157 | public function getVariable($name) 158 | { 159 | $name = str_replace('-', '_', $name); 160 | if ($this->hasVariable($name)) { 161 | return $this->variables[$name]; 162 | } elseif (!empty($this->parent)) { 163 | return $this->parent->getVariable($name); 164 | } else { 165 | // Return false instead of throwing an exception. 166 | // throw new SassContextException('Undefined Variable: ' . $name, $this->node); 167 | return new SassBoolean('false'); 168 | } 169 | } 170 | 171 | /** 172 | * Returns a value indicating if the variable exists in this context 173 | * @param string $name name of variable to test 174 | * @return boolean true if the variable exists in this context, false if not 175 | */ 176 | public function hasVariable($name) 177 | { 178 | $name = str_replace('-', '_', $name); 179 | 180 | return isset($this->variables[$name]); 181 | } 182 | 183 | /** 184 | * Sets a variable to the given value 185 | * @param string $name name of variable 186 | * @param sassLiteral $value value of variable 187 | * @return SassContext 188 | */ 189 | public function setVariable($name, $value) 190 | { 191 | $name = str_replace('-', '_', $name); 192 | $this->variables[$name] = $value; 193 | 194 | return $this; 195 | } 196 | 197 | public function setVariables($vars) 198 | { 199 | foreach ($vars as $key => $value) { 200 | if ($value !== NULL) { 201 | $this->setVariable($key, $value); 202 | } 203 | } 204 | } 205 | 206 | /** 207 | * Makes variables and mixins from this context available in the parent context. 208 | * Note that if there are variables or mixins with the same name in the two 209 | * contexts they will be set to that defined in this context. 210 | */ 211 | public function merge() 212 | { 213 | $this->parent->variables = array_merge($this->parent->variables, $this->variables); 214 | $this->parent->mixins = array_merge($this->parent->mixins, $this->mixins); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /lib/phpsass/tree/SassDebugNode.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.tree 10 | */ 11 | 12 | /** 13 | * SassDebugNode class. 14 | * Represents a Sass @debug or @warn directive. 15 | * @package PHamlP 16 | * @subpackage Sass.tree 17 | */ 18 | class SassDebugNode extends SassNode 19 | { 20 | const IDENTIFIER = '@'; 21 | const MATCH = '/^@(?:debug|warn)\s+(.+?)\s*;?$/'; 22 | const MESSAGE = 1; 23 | 24 | /** 25 | * @var string the debug/warning message 26 | */ 27 | private $message; 28 | /** 29 | * @var array parameters for the message; 30 | * only used by internal warning messages 31 | */ 32 | private $params; 33 | /** 34 | * @var boolean true if this is a warning 35 | */ 36 | private $warning; 37 | 38 | /** 39 | * SassDebugNode. 40 | * @param object $token source token 41 | * @param mixed string: an internally generated warning message about the 42 | * source 43 | * boolean: the source token is a @debug or @warn directive containing the 44 | * message; True if this is a @warn directive 45 | * @param array $message parameters for the message 46 | * @return SassDebugNode 47 | */ 48 | public function __construct($token, $message = false) 49 | { 50 | parent::__construct($token); 51 | if (is_string($message)) { 52 | $this->message = $message; 53 | $this->warning = true; 54 | } else { 55 | preg_match(self::MATCH, $token->source, $matches); 56 | $this->message = $matches[self::MESSAGE]; 57 | $this->warning = $message; 58 | } 59 | } 60 | 61 | /** 62 | * Parse this node. 63 | * This raises an error. 64 | * @return array An empty array 65 | */ 66 | public function parse($context) 67 | { 68 | if (!$this->warning) { 69 | $result = $this->evaluate($this->message, $context)->toString(); 70 | 71 | $cb = SassParser::$instance->options['callbacks']['debug']; 72 | if ($cb) { 73 | call_user_func($cb, $result, $context); 74 | } else { 75 | set_error_handler(array($this, 'errorHandler')); 76 | trigger_error($result); 77 | restore_error_handler(); 78 | } 79 | } 80 | 81 | return array(); 82 | } 83 | 84 | /** 85 | * Error handler for degug and warning statements. 86 | * @param int $errno Error number 87 | * @param string $message Message 88 | */ 89 | public function errorHandler($errno, $message) 90 | { 91 | echo '

SASS '.($this->warning ? 'WARNING' : 'DEBUG').": $message

{$this->filename}::{$this->line}

Source: {$this->source}

"; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /lib/phpsass/tree/SassDirectiveNode.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.tree 10 | */ 11 | 12 | /** 13 | * SassDirectiveNode class. 14 | * Represents a CSS directive. 15 | * @package PHamlP 16 | * @subpackage Sass.tree 17 | */ 18 | class SassDirectiveNode extends SassNode 19 | { 20 | const NODE_IDENTIFIER = '@'; 21 | const MATCH = '/^(@[\w-]+)/'; 22 | 23 | const INTERPOLATION_MATCH = '/\$([\w-]+)/'; 24 | 25 | /** 26 | * SassDirectiveNode. 27 | * @param object $token source token 28 | * @return SassDirectiveNode 29 | */ 30 | public function __construct($token) 31 | { 32 | parent::__construct($token); 33 | } 34 | 35 | protected function getDirective() 36 | { 37 | return $this->token->source; 38 | } 39 | 40 | /** 41 | * Parse this node. 42 | * @param SassContext $context the context in which this node is parsed 43 | * @return array the parsed node 44 | */ 45 | public function parse($context) 46 | { 47 | $node = clone $this; 48 | $node->token->source = self::interpolate_nonstrict($this->token->source, $context); 49 | 50 | $node->children = $this->parseChildren($context); 51 | 52 | return array($node); 53 | } 54 | 55 | /** 56 | * Render this node. 57 | * @return string the rendered node 58 | */ 59 | public function render() 60 | { 61 | $properties = array(); 62 | foreach ($this->children as $child) { 63 | $properties[] = $child->render(); 64 | } // foreach 65 | 66 | return $this->renderer->renderDirective($this, $properties); 67 | } 68 | 69 | /** 70 | * @see parse 71 | */ 72 | public function __clone() 73 | { 74 | parent::__clone(); 75 | $this->token = clone $this->token; 76 | } 77 | 78 | /** 79 | * Returns a value indicating if the token represents this type of node. 80 | * @param object $token token 81 | * @return boolean true if the token represents this type of node, false if not 82 | */ 83 | public static function isa($token) 84 | { 85 | return $token->source[0] === self::NODE_IDENTIFIER; 86 | } 87 | 88 | /** 89 | * Returns the directive 90 | * @param object $token token 91 | * @return string the directive 92 | */ 93 | public static function extractDirective($token) 94 | { 95 | preg_match(self::MATCH, $token->source, $matches); 96 | 97 | return strtolower($matches[1]); 98 | } 99 | 100 | public static function interpolate_nonstrict($string, $context) 101 | { 102 | for ($i = 0, $n = preg_match_all(self::INTERPOLATION_MATCH, $string, $matches); $i < $n; $i++) { 103 | $var = SassScriptParser::$instance->evaluate($matches[0][$i], $context); 104 | 105 | if ($var instanceOf SassString) { 106 | $var = $var->value; 107 | } else { 108 | $var = $var->toString(); 109 | } 110 | 111 | if (preg_match('/^unquote\((["\'])(.*)\1\)$/', $var, $match)) { 112 | $val = $match[2]; 113 | } elseif ($var == '""') { 114 | $val = ""; 115 | } elseif (preg_match('/^(["\'])(.*)\1$/', $var, $match)) { 116 | $val = $match[2]; 117 | } else { 118 | $val = $var; 119 | } 120 | $matches[1][$i] = $val; 121 | } 122 | $matches[0][] = '#{'; 123 | $matches[0][] = '}'; 124 | $matches[1][] = ''; 125 | $matches[1][] = ''; 126 | 127 | return str_replace($matches[0], $matches[1], $string); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /lib/phpsass/tree/SassEachNode.php: -------------------------------------------------------------------------------- 1 | @each in
.
 7 |  *
 8 |  *  is comma+space separated
 9 |  *  is available to the rest of the script following evaluation
10 |  * and has the value that terminated the loop.
11 |  *
12 |  * @author  Pavol (Lopo) Hluchy 
13 |  * @copyright  Copyright (c) 2011 Lopo
14 |  * @license  http://www.gnu.org/licenses/gpl.html GNU General Public License Version 3
15 |  * @package  PHamlP
16 |  * @subpackage  Sass.tree
17 |  */
18 | 
19 | /**
20 |  * SassEachNode class.
21 |  * Represents a Sass @each loop.
22 |  * @package  PHamlP
23 |  * @subpackage  Sass.tree
24 |  */
25 | class SassEachNode extends SassNode
26 | {
27 |   const MATCH = '/@each\s+[!\$](.+?)in\s+(.+)$/i';
28 | 
29 |   const VARIABLE = 1;
30 |   const IN = 2;
31 | 
32 |   /**
33 |    * @var string variable name for the loop
34 |    */
35 |   private $variable;
36 |   /**
37 |    * @var string expression that provides the loop values
38 |    */
39 |   private $in;
40 | 
41 |   /**
42 |    * SassEachNode constructor.
43 |    * @param object $token source token
44 |    * @return SassEachNode
45 |    */
46 |   public function __construct($token)
47 |   {
48 |     parent::__construct($token);
49 |     if (!preg_match(self::MATCH, $token->source, $matches)) {
50 |       throw new SassEachNodeException('Invalid @each directive', $this);
51 |     } else {
52 |       $this->variable = trim($matches[self::VARIABLE]);
53 |       $this->in = $matches[self::IN];
54 |     }
55 |   }
56 | 
57 |   /**
58 |    * Parse this node.
59 |    * @param SassContext $context the context in which this node is parsed
60 |    * @return array parsed child nodes
61 |    */
62 |   public function parse($context)
63 |   {
64 |     $children = array();
65 | 
66 |     if ($this->variable && $this->in) {
67 |       $context = new SassContext($context);
68 | 
69 |       list($in, $sep) = SassList::_parse_list($this->in, 'auto', true, $context);
70 |       foreach ($in as $var) {
71 |         $context->setVariable($this->variable, $var);
72 |         $children = array_merge($children, $this->parseChildren($context));
73 |       }
74 |     }
75 |     $context->merge();
76 | 
77 |     return $children;
78 |   }
79 | }
80 | 


--------------------------------------------------------------------------------
/lib/phpsass/tree/SassElseNode.php:
--------------------------------------------------------------------------------
 1 | 
 6 |  * @copyright   Copyright (c) 2010 PBM Web Development
 7 |  * @license      http://phamlp.googlecode.com/files/license.txt
 8 |  * @package      PHamlP
 9 |  * @subpackage  Sass.tree
10 |  */
11 | 
12 | /**
13 |  * SassElseNode class.
14 |  * Represents Sass Else If and Else statements.
15 |  * Else If and Else statement nodes are chained below the If statement node.
16 |  * @package      PHamlP
17 |  * @subpackage  Sass.tree
18 |  */
19 | class SassElseNode extends SassIfNode
20 | {
21 |   /**
22 |    * SassElseNode constructor.
23 |    * @param object $token source token
24 |    * @return SassElseNode
25 |    */
26 |   public function __construct($token)
27 |   {
28 |     parent::__construct($token, false);
29 |   }
30 | }
31 | 


--------------------------------------------------------------------------------
/lib/phpsass/tree/SassExtendNode.php:
--------------------------------------------------------------------------------
 1 | 
 6 |  * @copyright   Copyright (c) 2010 PBM Web Development
 7 |  * @license      http://phamlp.googlecode.com/files/license.txt
 8 |  * @package      PHamlP
 9 |  * @subpackage  Sass.tree
10 |  */
11 | 
12 | /**
13 |  * SassExtendNode class.
14 |  * Represents a Sass @debug or @warn directive.
15 |  * @package      PHamlP
16 |  * @subpackage  Sass.tree
17 |  */
18 | class SassExtendNode extends SassNode
19 | {
20 |   const IDENTIFIER = '@';
21 |   const MATCH = '/^@extend\s+(.+)/i';
22 |   const VALUE = 1;
23 | 
24 |   /**
25 |    * @var string the directive
26 |    */
27 |   private $value;
28 | 
29 |   /**
30 |    * SassExtendNode.
31 |    * @param object $token source token
32 |    * @return SassExtendNode
33 |    */
34 |   public function __construct($token)
35 |   {
36 |     parent::__construct($token);
37 |     preg_match(self::MATCH, $token->source, $matches);
38 |     $this->value = $matches[self::VALUE];
39 |   }
40 | 
41 |   /**
42 |    * Parse this node.
43 |    * @return array An empty array
44 |    */
45 |   public function parse($context)
46 |   {
47 |     # resolve selectors in relation to variables
48 |     # allows extend inside nested loops.
49 |     $this->root->extend($this->value, $this->parent->resolveSelectors($context));
50 | 
51 |     return array();
52 |   }
53 | }
54 | 


--------------------------------------------------------------------------------
/lib/phpsass/tree/SassForNode.php:
--------------------------------------------------------------------------------
  1 | @for  from  to|through [ step ]
. 9 | * 10 | * can be less or greater than . 11 | * If the step clause is ommitted the = 1. 12 | * is available to the rest of the script following evaluation 13 | * and has the value that terminated the loop. 14 | * 15 | * @author Chris Yates 16 | * @copyright Copyright (c) 2010 PBM Web Development 17 | * @license http://phamlp.googlecode.com/files/license.txt 18 | * @package PHamlP 19 | * @subpackage Sass.tree 20 | */ 21 | 22 | /** 23 | * SassForNode class. 24 | * Represents a Sass @for loop. 25 | * @package PHamlP 26 | * @subpackage Sass.tree 27 | */ 28 | class SassForNode extends SassNode 29 | { 30 | const MATCH = '/@for\s+[!\$](\w+)\s+from\s+(.+?)\s+(through|to)\s+(.+?)(?:\s+step\s+(.+))?$/i'; 31 | 32 | const VARIABLE = 1; 33 | const FROM = 2; 34 | const INCLUSIVE = 3; 35 | const TO = 4; 36 | const STEP = 5; 37 | const IS_INCLUSIVE = 'through'; 38 | 39 | /** 40 | * @var string variable name for the loop 41 | */ 42 | private $variable; 43 | /** 44 | * @var string expression that provides the loop start value 45 | */ 46 | private $from; 47 | /** 48 | * @var string expression that provides the loop end value 49 | */ 50 | private $to; 51 | /** 52 | * @var boolean whether the loop end value is inclusive 53 | */ 54 | private $inclusive; 55 | /** 56 | * @var string expression that provides the amount by which the loop variable 57 | * changes on each iteration 58 | */ 59 | private $step; 60 | 61 | /** 62 | * SassForNode constructor. 63 | * @param object $token source token 64 | * @return SassForNode 65 | */ 66 | public function __construct($token) 67 | { 68 | parent::__construct($token); 69 | if (!preg_match(self::MATCH, $token->source, $matches)) { 70 | throw new SassForNodeException('Invalid @for directive', $this); 71 | } 72 | $this->variable = $matches[self::VARIABLE]; 73 | $this->from = $matches[self::FROM]; 74 | $this->to = $matches[self::TO]; 75 | $this->inclusive = ($matches[self::INCLUSIVE] === SassForNode::IS_INCLUSIVE); 76 | $this->step = (empty($matches[self::STEP]) ? 1 : $matches[self::STEP]); 77 | } 78 | 79 | /** 80 | * Parse this node. 81 | * @param SassContext $context the context in which this node is parsed 82 | * @return array parsed child nodes 83 | */ 84 | public function parse($context) 85 | { 86 | $children = array(); 87 | $from = (float) $this->evaluate($this->from, $context)->value; 88 | $to = (float) $this->evaluate($this->to, $context)->value; 89 | $step = (float) $this->evaluate($this->step, $context)->value * ($to > $from ? 1 : -1); 90 | 91 | if ($this->inclusive) { 92 | $to += ($from < $to ? 1 : -1); 93 | } 94 | 95 | $context = new SassContext($context); 96 | for ($i = $from; ($from < $to ? $i < $to : $i > $to); $i = $i + $step) { 97 | $context->setVariable($this->variable, new SassNumber($i)); 98 | $children = array_merge($children, $this->parseChildren($context)); 99 | } 100 | 101 | return $children; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /lib/phpsass/tree/SassFunctionDefinitionNode.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.tree 10 | */ 11 | 12 | /** 13 | * SassFunctionDefinitionNode class. 14 | * Represents a Function definition. 15 | * @package PHamlP 16 | * @subpackage Sass.tree 17 | */ 18 | class SassFunctionDefinitionNode extends SassNode 19 | { 20 | const NODE_IDENTIFIER = FALSE; 21 | const MATCH = '/^@function\s+([_\-\w]+)\s*(?:\((.*?)\))?\s*$/im'; 22 | const IDENTIFIER = 1; 23 | const NAME = 1; 24 | const ARGUMENTS = 2; 25 | 26 | /** 27 | * @var string name of the function 28 | */ 29 | private $name; 30 | /** 31 | * @var array arguments for the function as name=>value pairs were value is the 32 | * default value or null for required arguments 33 | */ 34 | private $args = array(); 35 | 36 | public $parent; 37 | 38 | /** 39 | * SassFunctionDefinitionNode constructor. 40 | * @param object $token source token 41 | * @return SassFunctionDefinitionNode 42 | */ 43 | public function __construct($token) 44 | { 45 | // if ($token->level !== 0) { 46 | // throw new SassFunctionDefinitionNodeException('Functions can only be defined at root level', $token); 47 | // } 48 | parent::__construct($token); 49 | preg_match(self::MATCH, $token->source, $matches); 50 | if (empty($matches)) { 51 | throw new SassFunctionDefinitionNodeException('Invalid Function', $token); 52 | } 53 | $this->name = $matches[self::NAME]; 54 | $this->name = preg_replace('/[^a-z0-9_]/', '_', strtolower($this->name)); 55 | if (isset($matches[self::ARGUMENTS])) { 56 | if (strlen(trim($matches[self::ARGUMENTS]))) { 57 | foreach (explode(',', $matches[self::ARGUMENTS]) as $arg) { 58 | $arg = explode(($matches[self::IDENTIFIER] === self::NODE_IDENTIFIER ? '=' : ':'), trim($arg)); 59 | $this->args[substr(trim($arg[0]), 1)] = (count($arg) == 2 ? trim($arg[1]) : null); 60 | } // foreach 61 | } 62 | } 63 | } 64 | 65 | /** 66 | * Parse this node. 67 | * Add this function to the current context. 68 | * @param SassContext $context the context in which this node is parsed 69 | * @return array the parsed node - an empty array 70 | */ 71 | public function parse($context) 72 | { 73 | $context->addFunction($this->name, $this); 74 | 75 | return array(); 76 | } 77 | 78 | /** 79 | * Returns the arguments with default values for this function 80 | * @return array the arguments with default values for this function 81 | */ 82 | public function getArgs() 83 | { 84 | return $this->args; 85 | } 86 | 87 | /** 88 | * Returns a value indicating if the token represents this type of node. 89 | * @param object $token token 90 | * @return boolean true if the token represents this type of node, false if not 91 | */ 92 | public static function isa($token) 93 | { 94 | return $token->source[0] === self::NODE_IDENTIFIER; 95 | } 96 | 97 | /** 98 | * Evalutes the function in the given context, with the provided arguments 99 | * @param SassContext - the parent context 100 | * @param array - the list of provided variables 101 | * @throws SassReturn - if the @return is fired then this is thrown to break early 102 | * @return SassBoolean(false) - if no @return was fired, return false 103 | */ 104 | public function execute($pcontext, $provided) 105 | { 106 | list($arguments, $context) = SassScriptFunction::fill_parameters($this->args, $provided, $pcontext, $this); 107 | $context->setVariables($arguments); 108 | 109 | $children = array(); 110 | try { 111 | foreach ($this->children as $child) { 112 | $child->parent = $this; 113 | $children = array_merge($children, $child->parse($context)); 114 | } 115 | } catch (SassReturn $e) { 116 | return $e->value; 117 | } 118 | 119 | return new SassBoolean('false'); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /lib/phpsass/tree/SassIfNode.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.tree 10 | */ 11 | 12 | /** 13 | * SassIfNode class. 14 | * Represents Sass If, Else If and Else statements. 15 | * Else If and Else statement nodes are chained below the If statement node. 16 | * @package PHamlP 17 | * @subpackage Sass.tree 18 | */ 19 | class SassIfNode extends SassNode 20 | { 21 | const MATCH_IF = '/^@if\s*(.+)$/i'; 22 | const MATCH_ELSE = '/@else(\s*if\s*(.+))?/i'; 23 | const IF_EXPRESSION = 1; 24 | const ELSE_IF = 1; 25 | const ELSE_EXPRESSION = 2; 26 | /** 27 | * @var SassIfNode the next else node. 28 | */ 29 | private $else; 30 | /** 31 | * @var string expression to evaluate 32 | */ 33 | private $expression; 34 | 35 | /** 36 | * SassIfNode constructor. 37 | * @param object $token source token 38 | * @param boolean $if true for an "if" node, false for an "else if | else" node 39 | * @return SassIfNode 40 | */ 41 | public function __construct($token, $if=true) 42 | { 43 | parent::__construct($token); 44 | if ($if) { 45 | preg_match(self::MATCH_IF, $token->source, $matches); 46 | $this->expression = $matches[SassIfNode::IF_EXPRESSION]; 47 | } else { 48 | preg_match(self::MATCH_ELSE, $token->source, $matches); 49 | $this->expression = (sizeof($matches)==1 ? null : $matches[SassIfNode::ELSE_EXPRESSION]); 50 | } 51 | } 52 | 53 | /** 54 | * Adds an "else" statement to this node. 55 | * @param SassIfNode "else" statement node to add 56 | * @return SassIfNode this node 57 | */ 58 | public function addElse($node) 59 | { 60 | if ($this->else === null) { 61 | $node->parent = $this; 62 | $node->root = $this->root; 63 | $this->else = $node; 64 | } else { 65 | $this->else->addElse($node); 66 | } 67 | 68 | return $this; 69 | } 70 | 71 | /** 72 | * Parse this node. 73 | * @param SassContext $context the context in which this node is parsed 74 | * @return array parsed child nodes 75 | */ 76 | public function parse($context) 77 | { 78 | if ($this->isElse() || $this->evaluate($this->expression, $context)->toBoolean()) { 79 | $children = $this->parseChildren($context); 80 | } elseif (!empty($this->else)) { 81 | $children = $this->else->parse($context); 82 | } else { 83 | $children = array(); 84 | } 85 | 86 | return $children; 87 | } 88 | 89 | /** 90 | * Returns a value indicating if this node is an "else" node. 91 | * @return true if this node is an "else" node, false if this node is an "if" 92 | * or "else if" node 93 | */ 94 | private function isElse() 95 | { 96 | return ($this->expression==''); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/phpsass/tree/SassImportNode.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.tree 10 | */ 11 | 12 | /** 13 | * SassImportNode class. 14 | * Represents a CSS Import. 15 | * @package PHamlP 16 | * @subpackage Sass.tree 17 | */ 18 | class SassImportNode extends SassNode 19 | { 20 | const IDENTIFIER = '@'; 21 | const MATCH = '/^@import\s+(.+)/i'; 22 | const MATCH_CSS = '/^((url)\((.+)\)|.+" \w+|http|.+\.css$)/im'; 23 | const FILES = 1; 24 | 25 | /** 26 | * @var array files to import 27 | */ 28 | private $files = array(); 29 | 30 | /** 31 | * SassImportNode. 32 | * @param object $token source token 33 | * @return SassImportNode 34 | */ 35 | public function __construct($token, $parent) 36 | { 37 | parent::__construct($token); 38 | $this->parent = $parent; 39 | preg_match(self::MATCH, $token->source, $matches); 40 | 41 | foreach (SassList::_build_list($matches[self::FILES]) as $file) { 42 | $this->files[] = trim($file, '"\'; '); 43 | } 44 | } 45 | 46 | /** 47 | * Parse this node. 48 | * If the node is a CSS import return the CSS import rule. 49 | * Else returns the rendered tree for the file. 50 | * @param SassContext $context the context in which this node is parsed 51 | * @return array the parsed node 52 | */ 53 | public function parse($context) 54 | { 55 | $imported = array(); 56 | foreach ($this->files as $file) { 57 | if (preg_match(self::MATCH_CSS, $file, $matches)) { 58 | if (isset($matches[2]) && $matches[2] == 'url') { 59 | $file = $matches[1]; 60 | } else { 61 | $file = "url('$file')"; 62 | } 63 | 64 | return array(new SassString("@import $file;"), new SassString("\n")); 65 | } 66 | $file = trim($file, '\'"'); 67 | $files = SassFile::get_file($file, $this->parser); 68 | $tree = array(); 69 | if ($files) { 70 | if ($this->token->level > 0) { 71 | $tree = $this->parent; 72 | while (get_class($tree) != 'SassRuleNode' && get_class($tree) != 'SassRootNode' && isset($tree->parent)) { 73 | $tree = $tree->parent; 74 | } 75 | $tree = clone $tree; 76 | $tree->children = array(); 77 | } else { 78 | $tree = new SassRootNode($this->parser); 79 | $tree->extend_parent = $this->parent; 80 | } 81 | 82 | foreach ($files as $subfile) { 83 | if (preg_match(self::MATCH_CSS, $subfile)) { 84 | $tree->addChild(new SassString("@import url('$subfile');")); 85 | } else { 86 | $this->parser->filename = $subfile; 87 | $subtree = SassFile::get_tree($subfile, $this->parser); 88 | foreach ($subtree->getChildren() as $child) { 89 | $tree->addChild($child); 90 | } 91 | } 92 | } 93 | } 94 | if (!empty($tree)) { 95 | # parent may be either SassRootNode (returns an object) or SassRuleNode (returns an array of nodes) 96 | # so we parse then try get the children. 97 | $parsed = $tree->parse($context); 98 | if (!is_array($parsed) && isset($parsed->children)) { 99 | $parsed = $parsed->children; 100 | } 101 | if (is_array($parsed)) { 102 | $imported = array_merge($imported, $parsed); 103 | } 104 | } 105 | } 106 | 107 | return $imported; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /lib/phpsass/tree/SassMediaNode.php: -------------------------------------------------------------------------------- 1 | source, $matches); 54 | $this->token = $token; 55 | $this->media = $matches[self::MEDIA]; 56 | } 57 | 58 | /** 59 | * Parse this node. 60 | * This raises an error. 61 | * @return array An empty array 62 | */ 63 | public function parse($context) 64 | { 65 | // If we are in a loop, function or mixin then the parent isn't what should 66 | // go inside the media node. Walk up the parent tree to find the rule node 67 | // to put inside the media node or the root node if the media node should be 68 | // at the root. 69 | $parent = $this->parent; 70 | while (!($parent instanceOf SassRuleNode) && !($parent instanceOf SassRootNode)) { 71 | $parent = $parent->parent; 72 | } 73 | 74 | // Make a copy of the token before parsing in case we are in a loop and it contains variables 75 | $token = clone $this->token; 76 | $token->source = SassDirectiveNode::interpolate_nonstrict($token->source, $context); 77 | 78 | $node = new SassRuleNode($token, $context); 79 | $node->root = $parent->root; 80 | 81 | $rule = clone $parent; 82 | $rule->root = $node->root; 83 | $rule->children = $this->children; 84 | 85 | $try = $rule->parse($context); 86 | if (is_array($try)) { 87 | $rule->children = $try; 88 | } 89 | // Tests were failing with this, but I'm not sure if we cover every case. 90 | //else if (is_object($try) && method_exists($try, 'render')) { 91 | // $rule = $try; 92 | //} 93 | 94 | $node->children = array(new SassString($rule->render($context))); 95 | 96 | return array($node); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/phpsass/tree/SassMixinDefinitionNode.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.tree 10 | */ 11 | 12 | /** 13 | * SassMixinDefinitionNode class. 14 | * Represents a Mixin definition. 15 | * @package PHamlP 16 | * @subpackage Sass.tree 17 | */ 18 | class SassMixinDefinitionNode extends SassNode 19 | { 20 | const NODE_IDENTIFIER = '='; 21 | const MATCH = '/^(=|@mixin\s+)([-\w]+)\s*(?:\((.*?)\))?\s*$/im'; 22 | const IDENTIFIER = 1; 23 | const NAME = 2; 24 | const ARGUMENTS = 3; 25 | 26 | /** 27 | * @var string name of the mixin 28 | */ 29 | private $name; 30 | /** 31 | * @var array arguments for the mixin as name=>value pairs were value is the 32 | * default value or null for required arguments 33 | */ 34 | private $args = array(); 35 | 36 | /** 37 | * SassMixinDefinitionNode constructor. 38 | * 39 | * @param object $token source token 40 | * 41 | * @throws SassMixinDefinitionNodeException 42 | * @return SassMixinDefinitionNode 43 | */ 44 | public function __construct($token) 45 | { 46 | preg_match(self::MATCH, $token->source, $matches); 47 | parent::__construct($token); 48 | if (empty($matches)) { 49 | throw new SassMixinDefinitionNodeException('Invalid Mixin', $this); 50 | } 51 | $this->name = $matches[self::NAME]; 52 | if (isset($matches[self::ARGUMENTS])) { 53 | $this->args = SassScriptFunction::extractArgs($matches[self::ARGUMENTS], true, new SassContext); 54 | } 55 | } 56 | 57 | /** 58 | * Parse this node. 59 | * Add this mixin to the current context. 60 | * @param SassContext $context the context in which this node is parsed 61 | * @return array the parsed node - an empty array 62 | */ 63 | public function parse($context) 64 | { 65 | $context->addMixin($this->name, $this); 66 | 67 | return array(); 68 | } 69 | 70 | /** 71 | * Returns the arguments with default values for this mixin 72 | * @return array the arguments with default values for this mixin 73 | */ 74 | public function getArgs() 75 | { 76 | return $this->args; 77 | } 78 | 79 | /** 80 | * Returns a value indicating if the token represents this type of node. 81 | * @param object $token token 82 | * @return boolean true if the token represents this type of node, false if not 83 | */ 84 | public static function isa($token) 85 | { 86 | return $token->source[0] === self::NODE_IDENTIFIER; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/phpsass/tree/SassMixinNode.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.tree 10 | */ 11 | 12 | /** 13 | * SassMixinNode class. 14 | * Represents a Mixin. 15 | * @package PHamlP 16 | * @subpackage Sass.tree 17 | */ 18 | class SassMixinNode extends SassNode 19 | { 20 | const NODE_IDENTIFIER = '+'; 21 | const MATCH = '/^(\+|@include\s+)([a-z0-9_-]+)\s*(?:\((.*?)\))?\s*$/i'; 22 | const IDENTIFIER = 1; 23 | const NAME = 2; 24 | const ARGS = 3; 25 | 26 | /** 27 | * @var string name of the mixin 28 | */ 29 | private $name; 30 | /** 31 | * @var array arguments for the mixin 32 | */ 33 | private $args = ''; 34 | 35 | /** 36 | * SassMixinDefinitionNode constructor. 37 | * @param object $token source token 38 | * @return SassMixinNode 39 | */ 40 | public function __construct($token) 41 | { 42 | parent::__construct($token); 43 | preg_match(self::MATCH, $token->source, $matches); 44 | 45 | if (!isset($matches[self::NAME])) { 46 | throw new SassMixinNodeException('Invalid mixin invocation: ($token->source)', $this); 47 | } 48 | $this->name = $matches[self::NAME]; 49 | if (isset($matches[self::ARGS]) && strlen($matches[self::ARGS])) { 50 | $this->args = $matches[self::ARGS]; 51 | } 52 | } 53 | 54 | /** 55 | * Parse this node. 56 | * Set passed arguments and any optional arguments not passed to their 57 | * defaults, then render the children of the mixin definition. 58 | * @param SassContext $pcontext the context in which this node is parsed 59 | * @return array the parsed node 60 | */ 61 | public function parse($pcontext) 62 | { 63 | $mixin = $pcontext->getMixin($this->name); 64 | $context = new SassContext($pcontext); 65 | $context->content = $this->children; 66 | $argc = count($this->args); 67 | $count = 0; 68 | 69 | $args = SassScriptFunction::extractArgs($this->args, false, $context); 70 | 71 | list($arguments) = SassScriptFunction::fill_parameters($mixin->args, $args, $context, $this); 72 | $context->setVariables($arguments); 73 | 74 | $children = array(); 75 | foreach ($mixin->children as $child) { 76 | /** @var $child SassNode */ 77 | $child->parent = $this; 78 | $children = array_merge($children, $child->parse($context)); 79 | } 80 | 81 | // $context->merge(); 82 | return $children; 83 | } 84 | 85 | /** 86 | * Returns a value indicating if the token represents this type of node. 87 | * @param object $token token 88 | * @return boolean true if the token represents this type of node, false if not 89 | */ 90 | public static function isa($token) 91 | { 92 | return $token->source[0] === self::NODE_IDENTIFIER; 93 | } 94 | 95 | /** 96 | * Resolves selectors. 97 | * Interpolates SassScript in selectors and resolves any parent references or 98 | * appends the parent selectors. 99 | * @param SassContext $context the context in which this node is parsed 100 | * @return array 101 | */ 102 | public function resolveSelectors($context){ 103 | return $this->parent->resolveSelectors($context); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/phpsass/tree/SassNode.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.tree 10 | */ 11 | 12 | #require_once 'SassContext.php'; 13 | #require_once 'SassCommentNode.php'; 14 | #require_once 'SassDebugNode.php'; 15 | #require_once 'SassDirectiveNode.php'; 16 | #require_once 'SassImportNode.php'; 17 | #require_once 'SassMixinNode.php'; 18 | #require_once 'SassMixinDefinitionNode.php'; 19 | #require_once 'SassPropertyNode.php'; 20 | #require_once 'SassRootNode.php'; 21 | #require_once 'SassRuleNode.php'; 22 | #require_once 'SassVariableNode.php'; 23 | #require_once 'SassExtendNode.php'; 24 | #require_once 'SassEachNode.php'; 25 | #require_once 'SassForNode.php'; 26 | #require_once 'SassIfNode.php'; 27 | #require_once 'SassElseNode.php'; 28 | #require_once 'SassWhileNode.php'; 29 | #require_once 'SassNodeExceptions.php'; 30 | #require_once 'SassFunctionDefinitionNode.php'; 31 | #require_once 'SassReturnNode.php'; 32 | #require_once 'SassContentNode.php'; 33 | #require_once 'SassWarnNode.php'; 34 | #require_once 'SassMediaNode.php'; 35 | 36 | /** 37 | * SassNode class. 38 | * Base class for all Sass nodes. 39 | * @package PHamlP 40 | * @subpackage Sass.tree 41 | */ 42 | class SassNode 43 | { 44 | /** 45 | * @var SassNode parent of this node 46 | */ 47 | public $parent; 48 | /** 49 | * @var SassNode root node 50 | */ 51 | public $root; 52 | /** 53 | * @var array children of this node 54 | */ 55 | public $children = array(); 56 | /** 57 | * @var object source token 58 | */ 59 | public $token; 60 | 61 | /** 62 | * Constructor. 63 | * @param object $token source token 64 | * @return SassNode 65 | */ 66 | public function __construct($token) 67 | { 68 | $this->token = $token; 69 | } 70 | 71 | /** 72 | * Getter. 73 | * 74 | * @param string $name name of property to get 75 | * 76 | * @throws SassNodeException 77 | * @return mixed return value of getter function 78 | */ 79 | public function __get($name) 80 | { 81 | $getter = 'get' . ucfirst($name); 82 | if (method_exists($this, $getter)) { 83 | return $this->$getter(); 84 | } 85 | throw new SassNodeException('No getter function for ' . $name, $this); 86 | } 87 | 88 | /** 89 | * Setter. 90 | * @param string $name name of property to set 91 | * @param mixed $value value of property 92 | * @throws SassNodeException 93 | * @return SassNode this node 94 | */ 95 | public function __set($name, $value) 96 | { 97 | $setter = 'set' . ucfirst($name); 98 | if (method_exists($this, $setter)) { 99 | $this->$setter($value); 100 | 101 | return $this; 102 | } 103 | throw new SassNodeException('No setter function for ' . $name, $this); 104 | } 105 | 106 | /** 107 | * Resets children when cloned 108 | * @see parse 109 | */ 110 | public function __clone() 111 | { 112 | $this->children = array(); 113 | } 114 | 115 | /** 116 | * Return a value indicating if this node has a parent 117 | * @return array the node's parent 118 | */ 119 | public function hasParent() 120 | { 121 | return !empty($this->parent); 122 | } 123 | 124 | /** 125 | * Returns the node's parent 126 | * @return array the node's parent 127 | */ 128 | public function getParent() 129 | { 130 | return $this->parent; 131 | } 132 | 133 | /** 134 | * Adds a child to this node. 135 | */ 136 | public function addChild($child) 137 | { 138 | /** @var $child SassNode */ 139 | if ($child instanceof SassElseNode) { 140 | if (!$this->getLastChild() instanceof SassIfNode) { 141 | throw new SassException('@else(if) directive must come after @(else)if', $child); 142 | } 143 | $this->getLastChild()->addElse($child); 144 | } else { 145 | $this->children[] = $child; 146 | $child->parent = $this; 147 | $child->setRoot($this->root); 148 | } 149 | } 150 | 151 | /** 152 | * Sets a root recursively. 153 | * @param SassNode $root the new root node 154 | */ 155 | public function setRoot($root){ 156 | $this->root = $root; 157 | foreach ($this->children as $child) { 158 | /** @var $child SassNode */ 159 | $child->setRoot($this->root); 160 | } 161 | } 162 | 163 | /** 164 | * Returns a value indicating if this node has children 165 | * @return boolean true if the node has children, false if not 166 | */ 167 | public function hasChildren() 168 | { 169 | return !empty($this->children); 170 | } 171 | 172 | /** 173 | * Returns the node's children 174 | * @return array the node's children 175 | */ 176 | public function getChildren() 177 | { 178 | return $this->children; 179 | } 180 | 181 | /** 182 | * Returns a value indicating if this node is a child of the passed node. 183 | * This just checks the levels of the nodes. If this node is at a greater 184 | * level than the passed node if is a child of it. 185 | * 186 | * @param SassNode $node 187 | * 188 | * @return boolean true if the node is a child of the passed node, false if not 189 | */ 190 | public function isChildOf($node) 191 | { 192 | return $this->getLevel() > $node->getLevel(); 193 | } 194 | 195 | /** 196 | * Returns the last child node of this node. 197 | * @return SassNode the last child node of this node 198 | */ 199 | public function getLastChild() 200 | { 201 | return $this->children[count($this->children) - 1]; 202 | } 203 | 204 | /** 205 | * Returns the level of this node. 206 | * @return integer the level of this node 207 | */ 208 | public function getLevel() 209 | { 210 | return $this->token->level; 211 | } 212 | 213 | /** 214 | * Returns the source for this node 215 | * @return string the source for this node 216 | */ 217 | public function getSource() 218 | { 219 | return $this->token->source; 220 | } 221 | 222 | /** 223 | * Returns the debug_info option setting for this node 224 | * @return boolean the debug_info option setting for this node 225 | */ 226 | public function getDebug_info() 227 | { 228 | return $this->getParser()->debug_info; 229 | } 230 | 231 | /** 232 | * Returns the line number for this node 233 | * @return string the line number for this node 234 | */ 235 | public function getLine() 236 | { 237 | return $this->token->line; 238 | } 239 | 240 | /** 241 | * Returns the line_numbers option setting for this node 242 | * @return boolean the line_numbers option setting for this node 243 | */ 244 | public function getLine_numbers() 245 | { 246 | return $this->getParser()->line_numbers; 247 | } 248 | 249 | /** 250 | * Returns the filename for this node 251 | * @return string the filename for this node 252 | */ 253 | public function getFilename() 254 | { 255 | return $this->token->filename; 256 | } 257 | 258 | /** 259 | * Returns the Sass parser. 260 | * @return SassParser the Sass parser 261 | */ 262 | public function getParser() 263 | { 264 | return $this->root->parser; 265 | } 266 | 267 | /** 268 | * Returns the property syntax being used. 269 | * @return string the property syntax being used 270 | */ 271 | public function getPropertySyntax() 272 | { 273 | return $this->root->getParser()->propertySyntax; 274 | } 275 | 276 | /** 277 | * Returns the SassScript parser. 278 | * @return SassScriptParser the SassScript parser 279 | */ 280 | public function getScript() 281 | { 282 | return $this->root->script; 283 | } 284 | 285 | /** 286 | * Returns the renderer. 287 | * @return SassRenderer the renderer 288 | */ 289 | public function getRenderer() 290 | { 291 | return $this->root->renderer; 292 | } 293 | 294 | /** 295 | * Returns the render style of the document tree. 296 | * @return string the render style of the document tree 297 | */ 298 | public function getStyle() 299 | { 300 | return $this->root->getParser()->style; 301 | } 302 | 303 | /** 304 | * Returns a value indicating whether this node is in a directive 305 | * 306 | * @param boolean true if the node is in a directive, false if not 307 | * 308 | * @return bool 309 | */ 310 | public function inDirective() 311 | { 312 | return $this->parent instanceof SassDirectiveNode || 313 | $this->parent instanceof SassDirectiveNode; 314 | } 315 | 316 | /** 317 | * Returns a value indicating whether this node is in a SassScript directive 318 | * 319 | * @param boolean true if this node is in a SassScript directive, false if not 320 | * 321 | * @return bool 322 | */ 323 | public function inSassScriptDirective() 324 | { 325 | return $this->parent instanceof SassEachNode || 326 | $this->parent->parent instanceof SassEachNode || 327 | $this->parent instanceof SassForNode || 328 | $this->parent->parent instanceof SassForNode || 329 | $this->parent instanceof SassIfNode || 330 | $this->parent->parent instanceof SassIfNode || 331 | $this->parent instanceof SassWhileNode || 332 | $this->parent->parent instanceof SassWhileNode; 333 | } 334 | 335 | /** 336 | * Evaluates a SassScript expression. 337 | * 338 | * @param string $expression expression to evaluate 339 | * @param SassContext $context the context in which the expression is evaluated 340 | * @param mixed $x 341 | * 342 | * @return SassLiteral value of parsed expression 343 | */ 344 | public function evaluate($expression, $context, $x=null) 345 | { 346 | $context->node = $this; 347 | 348 | return $this->script->evaluate($expression, $context, $x); 349 | } 350 | 351 | /** 352 | * Replace interpolated SassScript contained in '#{}' with the parsed value. 353 | * @param string $expression the text to interpolate 354 | * @param SassContext $context the context in which the string is interpolated 355 | * @return string the interpolated text 356 | */ 357 | public function interpolate($expression, $context) 358 | { 359 | $context->node = $this; 360 | 361 | return $this->getScript()->interpolate($expression, $context); 362 | } 363 | 364 | /** 365 | * Adds a warning to the node. 366 | * @param string $message warning message 367 | */ 368 | public function addWarning($message) 369 | { 370 | $warning = new SassDebugNode($this->token, $message); 371 | $this->addChild($warning); 372 | } 373 | 374 | /** 375 | * Parse the children of the node. 376 | * @param SassContext $context the context in which the children are parsed 377 | * @return array the parsed child nodes 378 | */ 379 | public function parseChildren($context) 380 | { 381 | $children = array(); 382 | foreach ($this->children as $child) { 383 | # child could be a SassLiteral /or/ SassNode 384 | if (method_exists($child, 'parse')) { 385 | $kid = $child->parse($context); 386 | } else { 387 | $kid = array($child); 388 | } 389 | $children = array_merge($children, $kid); 390 | } 391 | 392 | return $children; 393 | } 394 | 395 | /** 396 | * Returns a value indicating if the token represents this type of node. 397 | * 398 | * @param object $token token 399 | * 400 | * @throws SassNodeException 401 | * @return boolean true if the token represents this type of node, false if not 402 | */ 403 | public static function isa($token) 404 | { 405 | throw new SassNodeException('Child classes must override this method'); 406 | } 407 | 408 | public function printDebugTree($i = 0) 409 | { 410 | echo str_repeat(' ', $i*2).get_class($this)." ".$this->getSource()."\n"; 411 | $p = $this->getParent(); 412 | if ($p) echo str_repeat(' ', $i*2)." parent: ".get_class($p)."\n"; 413 | foreach ($this->getChildren() as $c) { 414 | /** @var $c SassNode */ 415 | $c->printDebugTree($i+1); 416 | } 417 | } 418 | } 419 | -------------------------------------------------------------------------------- /lib/phpsass/tree/SassNodeExceptions.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.tree 10 | */ 11 | 12 | require_once(dirname(__FILE__).'/../SassException.php'); 13 | 14 | /** 15 | * SassNodeException class. 16 | * @package PHamlP 17 | * @subpackage Sass.tree 18 | */ 19 | class SassNodeException extends SassException {} 20 | 21 | /** 22 | * SassContextException class. 23 | * @package PHamlP 24 | * @subpackage Sass.tree 25 | */ 26 | class SassContextException extends SassNodeException {} 27 | 28 | /** 29 | * SassCommentNodeException class. 30 | * @package PHamlP 31 | * @subpackage Sass.tree 32 | */ 33 | class SassCommentNodeException extends SassNodeException {} 34 | 35 | /** 36 | * SassDebugNodeException class. 37 | * @package PHamlP 38 | * @subpackage Sass.tree 39 | */ 40 | class SassDebugNodeException extends SassNodeException {} 41 | 42 | /** 43 | * SassDirectiveNodeException class. 44 | * @package PHamlP 45 | * @subpackage Sass.tree 46 | */ 47 | class SassDirectiveNodeException extends SassNodeException {} 48 | 49 | /** 50 | * SassEachNodeException class. 51 | * @package PHamlP 52 | * @subpackage Sass.tree 53 | */ 54 | class SassEachNodeException extends SassNodeException {} 55 | 56 | /** 57 | * SassExtendNodeException class. 58 | * @package PHamlP 59 | * @subpackage Sass.tree 60 | */ 61 | class SassExtendNodeException extends SassNodeException {} 62 | 63 | /** 64 | * SassForNodeException class. 65 | * @package PHamlP 66 | * @subpackage Sass.tree 67 | */ 68 | class SassForNodeException extends SassNodeException {} 69 | 70 | /** 71 | * SassFunctionDefinitionNodeException class. 72 | * @package PHamlP 73 | * @subpackage Sass.tree 74 | */ 75 | class SassFunctionDefinitionNodeException extends SassNodeException {} 76 | 77 | /** 78 | * SassIfNodeException class. 79 | * @package PHamlP 80 | * @subpackage Sass.tree 81 | */ 82 | class SassIfNodeException extends SassNodeException {} 83 | 84 | /** 85 | * SassImportNodeException class. 86 | * @package PHamlP 87 | * @subpackage Sass.tree 88 | */ 89 | class SassImportNodeException extends SassNodeException {} 90 | 91 | /** 92 | * SassMixinDefinitionNodeException class. 93 | * @package PHamlP 94 | * @subpackage Sass.tree 95 | */ 96 | class SassMixinDefinitionNodeException extends SassNodeException {} 97 | 98 | /** 99 | * SassMixinNodeException class. 100 | * @package PHamlP 101 | * @subpackage Sass.tree 102 | */ 103 | class SassMixinNodeException extends SassNodeException {} 104 | 105 | /** 106 | * SassPropertyNodeException class. 107 | * @package PHamlP 108 | * @subpackage Sass.tree 109 | */ 110 | class SassPropertyNodeException extends SassNodeException {} 111 | 112 | /** 113 | * SassRuleNodeException class. 114 | * @package PHamlP 115 | * @subpackage Sass.tree 116 | */ 117 | class SassRuleNodeException extends SassNodeException {} 118 | 119 | /** 120 | * SassVariableNodeException class. 121 | * @package PHamlP 122 | * @subpackage Sass.tree 123 | */ 124 | class SassVariableNodeException extends SassNodeException {} 125 | 126 | /** 127 | * SassWhileNodeException class. 128 | * @package PHamlP 129 | * @subpackage Sass.tree 130 | */ 131 | class SassWhileNodeException extends SassNodeException {} 132 | -------------------------------------------------------------------------------- /lib/phpsass/tree/SassPropertyNode.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.tree 10 | */ 11 | 12 | /** 13 | * SassPropertyNode class. 14 | * Represents a CSS property. 15 | * @package PHamlP 16 | * @subpackage Sass.tree 17 | */ 18 | class SassPropertyNode extends SassNode 19 | { 20 | const MATCH_PROPERTY_SCSS = '/^([^\s=:"(\\\\:)]*)\s*(?:(= )|:)([^\:].*?)?(\s*!important[^;]*)?;*$/'; 21 | const MATCH_PROPERTY_NEW = '/^([^\s=:"]+)\s*(?:(= )|:)([^\:].*?)?(\s*!important[^;]*)?;*$/'; 22 | const MATCH_PROPERTY_OLD = '/^:([^\s=:]+)(?:\s*(=)\s*|\s+|$)(.*)(\s*!important.*)?/'; 23 | const MATCH_PSUEDO_SELECTOR = '/^:*\w[-\w]+\(?/i'; 24 | const MATCH_INTERPOLATION = '/^#\{(.*?)\}/i'; 25 | const MATCH_PROPRIETARY_SELECTOR = '/^:?-(moz|webkit|o|ms)-/'; 26 | const NAME = 1; 27 | const SCRIPT = 2; 28 | const VALUE = 3; 29 | const IS_SCRIPT = '= '; 30 | 31 | public static $psuedoSelectors = array( 32 | 'root', 33 | 'nth-child(', 34 | 'nth-last-child(', 35 | 'nth-of-type(', 36 | 'nth-last-of-type(', 37 | 'first-child', 38 | 'last-child', 39 | 'first-of-type', 40 | 'last-of-type', 41 | 'only-child', 42 | 'only-of-type', 43 | 'empty', 44 | 'link', 45 | 'visited', 46 | 'active', 47 | 'hover', 48 | 'focus', 49 | 'target', 50 | 'lang(', 51 | 'enabled', 52 | 'disabled', 53 | 'checked', 54 | ':first-line', 55 | ':first-letter', 56 | ':before', 57 | ':after', 58 | // CSS 2.1 59 | 'first-line', 60 | 'first-letter', 61 | 'before', 62 | 'after', 63 | // CSS 3 64 | 'not(', 65 | ); 66 | 67 | /** 68 | * @var string property name 69 | */ 70 | public $name; 71 | /** 72 | * @var string property value or expression to evaluate 73 | */ 74 | public $value; 75 | 76 | /** 77 | * @var boolean, wether the property is important 78 | */ 79 | public $important; 80 | 81 | /** 82 | * SassPropertyNode constructor. 83 | * @param object $token source token 84 | * @param string $syntax property syntax 85 | * @return SassPropertyNode 86 | */ 87 | public function __construct($token, $syntax = 'new') 88 | { 89 | parent::__construct($token); 90 | $matches = self::match($token, $syntax); 91 | $this->name = @$matches[self::NAME]; 92 | if (!isset($matches[self::VALUE])) { 93 | $this->value = ''; 94 | } else { 95 | $this->value = $matches[self::VALUE]; 96 | if ($matches[self::SCRIPT] === self::IS_SCRIPT) { 97 | $this->addWarning('Setting CSS properties with "=" is deprecated; use "{name}: {value};"', 98 | array('{name}'=>$this->name, '{value}'=>$this->value) 99 | ); 100 | } 101 | } 102 | $this->important = trim(array_pop($matches)) == '!important'; 103 | } 104 | 105 | /** 106 | * Parse this node. 107 | * If the node is a property namespace return all parsed child nodes. If not 108 | * return the parsed version of this node. 109 | * @param SassContext $context the context in which this node is parsed 110 | * @return array the parsed node 111 | */ 112 | public function parse($context) 113 | { 114 | $return = array(); 115 | if ($this->value !== "") { 116 | $node = clone $this; 117 | $node->name = ($this->inNamespace() ? "{$this->namespace}-" : '') . $this->interpolate($this->name, $context); 118 | 119 | $result = $this->evaluate($this->interpolate($this->value, $context), $context, SassScriptParser::CSS_PROPERTY); 120 | 121 | $node->value = $result && is_object($result) ? $result->toString() : $this->value; 122 | $return[] = $node; 123 | } 124 | if ($this->children) { 125 | $return = array_merge($return, $this->parseChildren($context)); 126 | } 127 | 128 | return $return; 129 | } 130 | 131 | /** 132 | * Render this node. 133 | * @return string the rendered node 134 | */ 135 | public function render() 136 | { 137 | return $this->renderer->renderProperty($this); 138 | } 139 | 140 | /** 141 | * Returns a value indicating if this node is in a namespace 142 | * @return boolean true if this node is in a property namespace, false if not 143 | */ 144 | public function inNamespace() 145 | { 146 | $parent = $this->parent; 147 | do { 148 | if ($parent instanceof SassPropertyNode) { 149 | return true; 150 | } 151 | $parent = $parent->parent; 152 | } while (is_object($parent)); 153 | 154 | return false; 155 | } 156 | 157 | /** 158 | * Returns the namespace for this node 159 | * @return string the namespace for this node 160 | */ 161 | public function getNamespace() 162 | { 163 | $namespace = array(); 164 | $parent = $this->parent; 165 | do { 166 | if ($parent instanceof SassPropertyNode) { 167 | $namespace[] = $parent->name; 168 | } 169 | $parent = $parent->parent; 170 | } while (is_object($parent)); 171 | 172 | return join('-', array_reverse($namespace)); 173 | } 174 | 175 | /** 176 | * Returns the name of this property. 177 | * If the property is in a namespace the namespace is prepended 178 | * @return string the name of this property 179 | */ 180 | public function getName() 181 | { 182 | return $this->name; 183 | } 184 | 185 | /** 186 | * Returns the parsed value of this property. 187 | * @return string the parsed value of this property 188 | */ 189 | public function getValue() 190 | { 191 | return $this->value; 192 | } 193 | 194 | /** 195 | * Returns a value indicating if the token represents this type of node. 196 | * @param object $token token 197 | * @return boolean true if the token represents this type of node, false if not 198 | */ 199 | public static function isa($token) 200 | { 201 | if (!is_array($token)) { 202 | $syntax = 'old'; 203 | } else { 204 | $syntax = $token['syntax']; 205 | $token = $token['token']; 206 | } 207 | 208 | $matches = self::match($token, $syntax); 209 | 210 | if (!empty($matches)) { 211 | if (isset($matches[self::VALUE]) && self::isPseudoSelector($matches[self::VALUE])) { 212 | return false; 213 | } 214 | if ($token->level === 0) { 215 | # RL - if it's on the first level it's probably a false positive, not an error. 216 | # even if it is a genuine error, no need to kill the compiler about it. 217 | 218 | return false; 219 | // throw new SassPropertyNodeException('Properties can not be assigned at root level', $token); 220 | } else { 221 | return true; 222 | } 223 | } else { 224 | return false; 225 | } 226 | } 227 | 228 | /** 229 | * Returns the matches for this type of node. 230 | * @param object $token 231 | * @param string $syntax the property syntax being used 232 | * @return array matches 233 | */ 234 | public static function match($token, $syntax) 235 | { 236 | switch ($syntax) { 237 | case 'scss': 238 | preg_match(self::MATCH_PROPERTY_SCSS, $token->source, $matches); 239 | break; 240 | case 'new': 241 | preg_match(self::MATCH_PROPERTY_NEW, $token->source, $matches); 242 | break; 243 | case 'old': 244 | preg_match(self::MATCH_PROPERTY_OLD, $token->source, $matches); 245 | break; 246 | default: 247 | if (preg_match(self::MATCH_PROPERTY_NEW, $token->source, $matches) == 0) { 248 | preg_match(self::MATCH_PROPERTY_OLD, $token->source, $matches); 249 | } 250 | break; 251 | } 252 | 253 | return $matches; 254 | } 255 | 256 | /** 257 | * Returns a value indicating if the string starts with a pseudo selector. 258 | * This is used to reject pseudo selectors as property values as, for example, 259 | * "a:hover" and "text-decoration:underline" look the same to the property 260 | * match regex. 261 | * It will also match interpolation to allow for constructs such as 262 | * content:#{$pos} 263 | * @see isa() 264 | * @param string $string the string to test 265 | * @return bool true if the string starts with a pseudo selector, false if not 266 | */ 267 | public static function isPseudoSelector($string) 268 | { 269 | preg_match(self::MATCH_PSUEDO_SELECTOR, $string, $matches); 270 | 271 | return (isset($matches[0]) && in_array($matches[0], self::$psuedoSelectors)) || 272 | preg_match(self::MATCH_INTERPOLATION, $string) || 273 | preg_match(self::MATCH_PROPRIETARY_SELECTOR, $string); 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /lib/phpsass/tree/SassReturnNode.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.tree 10 | */ 11 | 12 | /** 13 | * SassReturnNode class. 14 | * Represents a Return. 15 | * @package PHamlP 16 | * @subpackage Sass.tree 17 | */ 18 | class SassReturnNode extends SassNode 19 | { 20 | const NODE_IDENTIFIER = '+'; 21 | const MATCH = '/^(@return\s+)(.*)$/i'; 22 | const IDENTIFIER = 1; 23 | const STATEMENT = 2; 24 | 25 | /** 26 | * @var mixed statement to execute and return 27 | */ 28 | private $statement; 29 | 30 | /** 31 | * SassReturnNode constructor. 32 | * @param object $token source token 33 | * @return SassReturnNode 34 | */ 35 | public function __construct($token) 36 | { 37 | parent::__construct($token); 38 | preg_match(self::MATCH, $token->source, $matches); 39 | 40 | if (empty($matches)) { 41 | return new SassBoolean('false'); 42 | } 43 | 44 | $this->statement = $matches[self::STATEMENT]; 45 | } 46 | 47 | /** 48 | * Parse this node. 49 | * Set passed arguments and any optional arguments not passed to their 50 | * defaults, then render the children of the return definition. 51 | * @param SassContext $pcontext the context in which this node is parsed 52 | * @throws SassReturn 53 | * @return array the parsed node 54 | */ 55 | public function parse($pcontext) 56 | { 57 | $return = $this; 58 | $context = new SassContext($pcontext); 59 | $statement = $this->statement; 60 | 61 | $parent = $this->parent->parent->parser; 62 | $script = $this->parent->parent->script; 63 | $lexer = $script->lexer; 64 | 65 | $result = $script->evaluate($statement, $context); 66 | 67 | throw new SassReturn($result); 68 | } 69 | 70 | /** 71 | * Returns a value indicating if the token represents this type of node. 72 | * @param object $token token 73 | * @return boolean true if the token represents this type of node, false if not 74 | */ 75 | public static function isa($token) 76 | { 77 | return $token->source[0] === self::NODE_IDENTIFIER; 78 | } 79 | } 80 | 81 | class SassReturn extends Exception 82 | { 83 | public function __construct($value) 84 | { 85 | $this->value = $value; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/phpsass/tree/SassRootNode.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.tree 10 | */ 11 | 12 | #require_once(dirname(__FILE__).'/../script/SassScriptParser.php'); 13 | #require_once(dirname(__FILE__).'/../renderers/SassRenderer.php'); 14 | 15 | /** 16 | * SassRootNode class. 17 | * Also the root node of a document. 18 | * @package PHamlP 19 | * @subpackage Sass.tree 20 | */ 21 | class SassRootNode extends SassNode 22 | { 23 | /** 24 | * @var SassScriptParser SassScript parser 25 | */ 26 | public $script; 27 | /** 28 | * @var SassRenderer the renderer for this node 29 | */ 30 | public $renderer; 31 | /** 32 | * @var SassParser 33 | */ 34 | public $parser; 35 | /** 36 | * @var array extenders for this tree in the form extendee=>extender 37 | */ 38 | public $extenders = array(); 39 | 40 | /** 41 | * Extend_parent - for resolving extends across imported files. 42 | */ 43 | public $extend_parent = null; 44 | 45 | /** 46 | * Root SassNode constructor. 47 | * @param SassParser $parser Sass parser 48 | * @return SassRootNode 49 | */ 50 | public function __construct($parser) 51 | { 52 | parent::__construct((object) array( 53 | 'source' => '', 54 | 'level' => -1, 55 | 'filename' => $parser->filename, 56 | 'line' => 0, 57 | )); 58 | $this->parser = $parser; 59 | $this->script = new SassScriptParser(); 60 | $this->renderer = SassRenderer::getRenderer($parser->style); 61 | $this->root = $this; 62 | } 63 | 64 | /** 65 | * Parses this node and its children into the render tree. 66 | * Dynamic nodes are evaluated, files imported, etc. 67 | * Only static nodes for rendering are in the resulting tree. 68 | * @param SassContext $context the context in which this node is parsed 69 | * @return SassNode root node of the render tree 70 | */ 71 | public function parse($context) 72 | { 73 | $node = clone $this; 74 | $node->children = $this->parseChildren($context); 75 | 76 | return $node; 77 | } 78 | 79 | /** 80 | * Render this node. 81 | * @param mixed $context 82 | * @return string the rendered node 83 | */ 84 | public function render($context = null) 85 | { 86 | $context = new SassContext($context); 87 | $node = $this->parse($context); 88 | $output = ''; 89 | foreach ($node->children as $child) { 90 | $output .= $child->render(); 91 | } // foreach 92 | 93 | return $output; 94 | } 95 | 96 | /** 97 | * @param $extendee 98 | * @param $selectors 99 | * 100 | * @return mixed|NULL 101 | */ 102 | public function extend($extendee, $selectors) 103 | { 104 | if ($this->extend_parent && method_exists($this->extend_parent, 'extend')) { 105 | return $this->extend_parent->extend($extendee, $selectors); 106 | } 107 | $this->extenders[$extendee] = (isset($this->extenders[$extendee]) 108 | ? array_merge($this->extenders[$extendee], $selectors) : $selectors); 109 | return NULL; 110 | } 111 | 112 | public function getExtenders() 113 | { 114 | if ($this->extend_parent && method_exists($this->extend_parent, 'getExtenders')) { 115 | return $this->extend_parent->getExtenders(); 116 | } 117 | 118 | return $this->extenders; 119 | } 120 | 121 | /** 122 | * Returns a value indicating if the line represents this type of node. 123 | * Child classes must override this method. 124 | * @throws SassNodeException if not overriden 125 | */ 126 | public static function isa($line) 127 | { 128 | throw new SassNodeException('Child classes must override this method'); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /lib/phpsass/tree/SassRuleNode.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.tree 10 | */ 11 | 12 | /** 13 | * SassRuleNode class. 14 | * Represents a CSS rule. 15 | * @package PHamlP 16 | * @subpackage Sass.tree 17 | */ 18 | class SassRuleNode extends SassNode 19 | { 20 | const MATCH = '/^(.+?)(?:\s*\{)?$/'; 21 | const SELECTOR = 1; 22 | const CONTINUED = ','; 23 | 24 | /** 25 | * @const string that is replaced with the parent node selector 26 | */ 27 | const PARENT_REFERENCE = '&'; 28 | 29 | /** 30 | * @var array selector(s) 31 | */ 32 | private $selectors = array(); 33 | 34 | /** 35 | * @var array parent selectors 36 | */ 37 | private $parentSelectors = array(); 38 | 39 | /** 40 | * @var boolean whether the node expects more selectors 41 | */ 42 | private $isContinued; 43 | 44 | /** 45 | * SassRuleNode constructor. 46 | * @param object $token source token 47 | * @return SassRuleNode 48 | */ 49 | public function __construct($token) 50 | { 51 | parent::__construct($token); 52 | preg_match(self::MATCH, $token->source, $matches); 53 | $this->addSelectors($matches[SassRuleNode::SELECTOR]); 54 | } 55 | 56 | /** 57 | * Adds selector(s) to the rule. 58 | * If the selectors are to continue for the rule the selector must end in a comma 59 | * @param string $selectors selector 60 | * @param boolean $explode 61 | */ 62 | public function addSelectors($selectors, $explode = true) 63 | { 64 | $this->isContinued = substr($selectors, -1) === self::CONTINUED; 65 | $this->selectors = array_merge($this->selectors, $explode ? $this->explode($selectors) : $selectors); 66 | } 67 | 68 | /** 69 | * Returns a value indicating if the selectors for this rule are to be continued. 70 | * 71 | * @return bool rue if the selectors for this rule are to be continued false if not 72 | */ 73 | public function getIsContinued() 74 | { 75 | return $this->isContinued; 76 | } 77 | 78 | /** 79 | * Parse this node and its children into static nodes. 80 | * @param SassContext $context the context in which this node is parsed 81 | * @return array the parsed node and its children 82 | */ 83 | public function parse($context) 84 | { 85 | $node = clone $this; 86 | $node->selectors = $this->resolveSelectors($context); 87 | $node->children = $this->parseChildren($context); 88 | 89 | return array($node); 90 | } 91 | 92 | /** 93 | * Render this node and its children to CSS. 94 | * @return string the rendered node 95 | */ 96 | public function render() 97 | { 98 | $this->extend(); 99 | $rules = ''; 100 | $properties = array(); 101 | 102 | foreach ($this->children as $child) { 103 | $child->parent = $this; 104 | if ($child instanceof SassRuleNode) { 105 | $rules .= $child->render(); 106 | } else { 107 | $properties[] = $child->render(); 108 | } 109 | } 110 | 111 | return $this->getRenderer()->renderRule($this, $properties, $rules); 112 | } 113 | 114 | /** 115 | * Extend this nodes selectors 116 | * $extendee is the subject of the @extend directive 117 | * $extender is the selector that contains the @extend directive 118 | * $selector a selector or selector sequence that is to be extended 119 | */ 120 | public function extend() 121 | { 122 | foreach ($this->root->getExtenders() as $extendee => $extenders) { 123 | if ($this->isPsuedo($extendee)) { 124 | $extendee = explode(':', $extendee); 125 | $pattern = preg_quote($extendee[0]).'((\.[-\w]+)*):'.preg_quote($extendee[1]); 126 | } else { 127 | $pattern = preg_quote($extendee); 128 | } 129 | 130 | foreach (preg_grep('/'.$pattern.'/', $this->selectors) as $selector) { 131 | foreach ($extenders as $extender) { 132 | # first if establishes that we are using a placeholder and the extendee begins with a tag 133 | if ($extendee{0} == '%' && $selector{0} != '%' && preg_match('/(^| )[a-zA-Z][^%]*' . preg_quote($extendee) . '([^a-z0-9_-]|$)/', $selector)) { 134 | # the second if establishes that the extender is a tag rather than a class/id 135 | $zero = ord(strtolower(substr($extender, 0, 1))); // cheaper than regex 136 | if ($zero >= 97 && $zero <= 122) { 137 | continue; 138 | } 139 | } 140 | if (is_array($extendee)) { 141 | $this->selectors[] = preg_replace('/(.*?)'.$pattern.'([^a-zA-Z0-9_-]|$)/', '$1' . $extender . '$2', $selector); 142 | } elseif ($this->isSequence($extender) || $this->isSequence($selector)) { 143 | $this->selectors = array_merge($this->selectors, $this->mergeSequence($extender, $extendee, $selector)); 144 | } else { 145 | $this->selectors[] = str_replace($extendee, $extender, $selector); 146 | } 147 | } 148 | } 149 | $this->selectors = array_unique($this->selectors); 150 | } 151 | } 152 | 153 | /** 154 | * Tests whether the selector is a psuedo selector 155 | * @param string $selector selector to test 156 | * @return boolean true if the selector is a psuedo selector, false if not 157 | */ 158 | private function isPsuedo($selector) 159 | { 160 | return strpos($selector, ':') !== false; 161 | } 162 | 163 | /** 164 | * Tests whether the selector is a sequence selector 165 | * @param string $selector selector to test 166 | * @return boolean true if the selector is a sequence selector, false if not 167 | */ 168 | private function isSequence($selector) 169 | { 170 | return strpos($selector, ' ') !== false; 171 | } 172 | 173 | public function isPlaceholder($selector) 174 | { 175 | return strpos($selector, '%') !== false && !preg_match("/^[\d]+%$/", $selector); 176 | } 177 | 178 | /** 179 | * Merges selector sequences 180 | * @param string $extender the extender selector 181 | * @param string $extendee 182 | * @param string $selector selector to extend 183 | * @return array the merged sequences 184 | */ 185 | private function mergeSequence($extender, $extendee, $selector) 186 | { 187 | // if it's a placeholder, be lazy. Needs tests. 188 | if ($extendee[0] == '%') { 189 | // need to stop things like a%foo accepting div { @extend %foo } 190 | return array(str_replace($extendee, $extender, $selector)); 191 | } 192 | 193 | $extender = explode(' ', $extender); 194 | $end = array_pop($extender); 195 | $selector = explode(' ', $selector); 196 | array_pop($selector); 197 | 198 | $common = array(); 199 | if (count($extender) && count($selector)) { 200 | while (trim($extender[0]) === trim($selector[0])) { 201 | $common[] = array_shift($selector); 202 | array_shift($extender); 203 | if (!count($extender)) { 204 | break; 205 | } 206 | } 207 | } 208 | 209 | $beginning = (!empty($common) ? join(' ', $common) . ' ' : ''); 210 | 211 | # Richard Lyon - 2011-10-25 - removes duplicates by uniquing and trimming. 212 | # regex removes whitespace from start and and end of string as well as removing 213 | # whitespace following whitespace. slightly quicker than a trim and simpler replace 214 | 215 | return array_unique(array( 216 | preg_replace('/(^\s+|(\s)\s+|\s+$)/', '$2', $beginning.join(' ', $selector).' '.join(' ', $extender). ' ' . $end), 217 | preg_replace('/(^\s+|(\s)\s+|\s+$)/', '$2', $beginning.join(' ', $extender).' '.join(' ', $selector). ' ' . $end) 218 | )); 219 | } 220 | 221 | /** 222 | * Returns the selectors 223 | * @return array selectors 224 | */ 225 | public function getSelectors() 226 | { 227 | return $this->selectors; 228 | } 229 | 230 | /** 231 | * Resolves selectors. 232 | * Interpolates SassScript in selectors and resolves any parent references or 233 | * appends the parent selectors. 234 | * @param SassContext $context the context in which this node is parsed 235 | * 236 | * Change: 7/Dec/11 - change to make selector ordering conform to Ruby compiler. 237 | * @return array 238 | */ 239 | public function resolveSelectors($context) 240 | { 241 | $resolvedSelectors = $normalSelectors = array(); 242 | $this->parentSelectors = $this->getParentSelectors($context); 243 | 244 | foreach ($this->selectors as $selector) { 245 | $selector = $this->interpolate($selector, $context); 246 | $selectors = SassList::_build_list($selector); 247 | 248 | foreach ($selectors as $selector_inner) { 249 | $selector_inner = trim($selector_inner, ' \'"'); // strip whitespace and quotes, just-in-case. 250 | if ($this->hasParentReference($selector_inner)) { 251 | $resolvedSelectors = array_merge($resolvedSelectors, $this->resolveParentReferences($selector_inner, $context)); 252 | } else { 253 | $normalSelectors[] = $selector_inner; 254 | } 255 | } 256 | } // foreach 257 | 258 | // merge with parent selectors 259 | if ($this->parentSelectors) { 260 | $return = array(); 261 | foreach ($this->parentSelectors as $parent) { 262 | foreach ($normalSelectors as $selector) { 263 | $spacer = (substr($selector, 0, 1) == '[') ? '' : ' '; 264 | 265 | $return[] = $parent . $spacer . $selector; 266 | } 267 | } 268 | $normalSelectors = $return; 269 | } 270 | 271 | return array_merge($normalSelectors, $resolvedSelectors); 272 | } 273 | 274 | /** 275 | * Returns the parent selector(s) for this node. 276 | * This in an empty array if there is no parent selector. 277 | * @param mixed $context 278 | * @return array the parent selector for this node 279 | */ 280 | protected function getParentSelectors($context) 281 | { 282 | $ancestor = $this->parent; 283 | while (!$ancestor instanceof SassRuleNode && $ancestor->hasParent()) { 284 | $ancestor = $ancestor->parent; 285 | } 286 | 287 | if ($ancestor instanceof SassRuleNode) { 288 | return $ancestor->resolveSelectors($context); 289 | } 290 | 291 | return array(); 292 | } 293 | 294 | /** 295 | * Returns the position of the first parent reference in the selector. 296 | * If there is no parent reference in the selector this function returns 297 | * boolean FALSE. 298 | * Note that the return value may be non-Boolean that evaluates to FALSE, 299 | * i.e. 0. The return value should be tested using the === operator. 300 | * @param string $selector selector to test 301 | * @return mixed integer: position of the the first parent reference, 302 | * boolean: false if there is no parent reference. 303 | */ 304 | private function parentReferencePos($selector) 305 | { 306 | $inString = ''; 307 | for ($i = 0, $l = strlen($selector); $i < $l; $i++) { 308 | $c = $selector[$i]; 309 | if ($c === self::PARENT_REFERENCE && empty($inString)) { 310 | return $i; 311 | } elseif (empty($inString) && ($c === '"' || $c === "'")) { 312 | $inString = $c; 313 | } elseif ($c === $inString) { 314 | $inString = ''; 315 | } 316 | } 317 | 318 | return false; 319 | } 320 | 321 | /** 322 | * Determines if there is a parent reference in the selector 323 | * @param string $selector selector 324 | * @return boolean true if there is a parent reference in the selector 325 | */ 326 | private function hasParentReference($selector) 327 | { 328 | return $this->parentReferencePos($selector) !== false; 329 | } 330 | 331 | /** 332 | * Resolves parent references in the selector 333 | * @param string $selector selector 334 | * @param mixed $context 335 | * @throws SassRuleNodeException 336 | * @return string selector with parent references resolved 337 | */ 338 | private function resolveParentReferences($selector, $context) 339 | { 340 | $resolvedReferences = array(); 341 | if (!count($this->parentSelectors)) { 342 | throw new SassRuleNodeException('Can not use parent selector (' . self::PARENT_REFERENCE . ') when no parent selectors', $this); 343 | } 344 | foreach ($this->getParentSelectors($context) as $parentSelector) { 345 | $resolvedReferences[] = str_replace(self::PARENT_REFERENCE, $parentSelector, $selector); 346 | } 347 | 348 | return $resolvedReferences; 349 | } 350 | 351 | /** 352 | * Explodes a string of selectors into an array. 353 | * We can't use PHP::explode as this will potentially explode attribute 354 | * matches in the selector, e.g. div[title="some,value"] and interpolations. 355 | * @param string $string selectors 356 | * @return array selectors 357 | */ 358 | private function explode($string) 359 | { 360 | $selectors = array(); 361 | $inString = false; 362 | $interpolate = false; 363 | $selector = ''; 364 | 365 | for ($i = 0, $l = strlen($string); $i < $l; $i++) { 366 | $c = $string[$i]; 367 | if ($c === self::CONTINUED && !$inString && !$interpolate) { 368 | $selectors[] = trim($selector); 369 | $selector = ''; 370 | } else { 371 | $selector .= $c; 372 | if ($c === '"' || $c === "'") { 373 | do { 374 | $_c = $string[++$i]; 375 | $selector .= $_c; 376 | } while ($_c !== $c && isset($string[$i+1])); 377 | } elseif ($c === '#' && $string[$i+1] === '{') { 378 | do { 379 | $c = $string[++$i]; 380 | $selector .= $c; 381 | } while ($c !== '}'); 382 | } 383 | } 384 | } 385 | 386 | if (!empty($selector)) { 387 | $selectors[] = trim($selector); 388 | } 389 | 390 | return $selectors; 391 | } 392 | } 393 | -------------------------------------------------------------------------------- /lib/phpsass/tree/SassVariableNode.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.tree 10 | */ 11 | 12 | /** 13 | * SassVariableNode class. 14 | * Represents a variable. 15 | * @package PHamlP 16 | * @subpackage Sass.tree 17 | */ 18 | class SassVariableNode extends SassNode 19 | { 20 | const MATCH = '/^([!$])([\w-]+)\s*:?\s*((\|\|)?=)?\s*(.+?)\s*(!default)?;?$/i'; 21 | const IDENTIFIER = 1; 22 | const NAME = 2; 23 | const SASS_ASSIGNMENT = 3; 24 | const SASS_DEFAULT = 4; 25 | const VALUE = 5; 26 | const SCSS_DEFAULT = 6; 27 | const SASS_IDENTIFIER = '!'; 28 | const SCSS_IDENTIFIER = '$'; 29 | 30 | /** 31 | * @var string name of the variable 32 | */ 33 | private $name; 34 | /** 35 | * @var string value of the variable or expression to evaluate 36 | */ 37 | private $value; 38 | /** 39 | * @var boolean whether the variable is optionally assigned 40 | */ 41 | private $isDefault; 42 | 43 | /** 44 | * SassVariableNode constructor. 45 | * @param object $token source token 46 | * @throws SassVariableNodeException 47 | * @return SassVariableNode 48 | */ 49 | public function __construct($token) 50 | { 51 | parent::__construct($token); 52 | preg_match(self::MATCH, $token->source, $matches); 53 | if (empty($matches[self::NAME]) || ($matches[self::VALUE] === '')) { 54 | throw new SassVariableNodeException('Invalid variable definition; name and expression required', $this); 55 | } 56 | 57 | $this->name = $matches[self::NAME]; 58 | $this->value = $matches[self::VALUE]; 59 | $this->isDefault = (!empty($matches[self::SASS_DEFAULT]) || !empty($matches[self::SCSS_DEFAULT])); 60 | 61 | // Warn about deprecated features 62 | if ($matches[self::IDENTIFIER] === self::SASS_IDENTIFIER) { 63 | $this->addWarning('Variables prefixed with "!" is deprecated; use "' . $this->name . '"'); 64 | } 65 | if (!empty($matches[SassVariableNode::SASS_ASSIGNMENT])) { 66 | $this->addWarning('Setting variables with "' . (!empty($matches[SassVariableNode::SASS_DEFAULT])?'||':'') . '=" is deprecated; use "$' . $this->name . ': ' . $this->value . (!empty($matches[SassVariableNode::SASS_DEFAULT]) ? ' !default' : '')); 67 | } 68 | } 69 | 70 | /** 71 | * Parse this node. 72 | * Sets the variable in the current context. 73 | * @param SassContext $context the context in which this node is parsed 74 | * @return array the parsed node - an empty array 75 | */ 76 | public function parse($context) 77 | { 78 | if (!$this->isDefault || !$context->hasVariable($this->name)) { 79 | $context->setVariable( 80 | $this->name, $this->evaluate($this->value, $context) 81 | ); 82 | } 83 | $this->parseChildren($context); // Parse any warnings 84 | 85 | return array(); 86 | } 87 | 88 | /** 89 | * Returns a value indicating if the token represents this type of node. 90 | * @param object $token token 91 | * @return boolean true if the token represents this type of node, false if not 92 | */ 93 | public static function isa($token) 94 | { 95 | return $token->source[0] === self::SASS_IDENTIFIER || $token->source[0] === self::SCSS_IDENTIFIER; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/phpsass/tree/SassWarnNode.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright none 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.tree 10 | */ 11 | 12 | /** 13 | * SassReturnNode class. 14 | * Represents a Return. 15 | * @package PHamlP 16 | * @subpackage Sass.tree 17 | */ 18 | class SassWarnNode extends SassNode 19 | { 20 | const NODE_IDENTIFIER = '+'; 21 | const MATCH = '/^(@warn\s+)(["\']?)(.*?)(["\']?)$/i'; 22 | const IDENTIFIER = 1; 23 | const STATEMENT = 3; 24 | 25 | /** 26 | * @var mixed statement to execute and return 27 | */ 28 | private $statement; 29 | 30 | /** 31 | * SassReturnNode constructor. 32 | * @param object $token source token 33 | * @return SassWarnNode|void 34 | */ 35 | public function __construct($token) 36 | { 37 | parent::__construct($token); 38 | preg_match(self::MATCH, $token->source, $matches); 39 | 40 | if (empty($matches)) { 41 | return new SassBoolean('false'); 42 | } 43 | 44 | $this->statement = $matches[self::STATEMENT]; 45 | } 46 | 47 | /** 48 | * Parse this node. 49 | * Set passed arguments and any optional arguments not passed to their 50 | * defaults, then render the children of the return definition. 51 | * @param SassContext $pcontext the context in which this node is parsed 52 | * @return array the parsed node 53 | */ 54 | public function parse($pcontext) 55 | { 56 | $context = new SassContext($pcontext); 57 | $statement = $this->statement; 58 | 59 | try { 60 | $statement = $this->evaluate($this->statement, $context)->toString(); 61 | } catch (Exception $e) {} 62 | 63 | if (SassParser::$instance->options['callbacks']['warn']) { 64 | call_user_func(SassParser::$instance->options['callbacks']['warn'], $statement, $context); 65 | } 66 | 67 | if (SassParser::$instance->getQuiet()) { 68 | return array(new SassString('')); 69 | } else { 70 | return array(new SassString('/* @warn: ' . str_replace('*/', '', $statement) . ' */')); 71 | } 72 | } 73 | 74 | /** 75 | * Returns a value indicating if the token represents this type of node. 76 | * @param object $token token 77 | * @return boolean true if the token represents this type of node, false if not 78 | */ 79 | public static function isa($token) 80 | { 81 | return $token->source[0] === self::NODE_IDENTIFIER; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/phpsass/tree/SassWhileNode.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright Copyright (c) 2010 PBM Web Development 7 | * @license http://phamlp.googlecode.com/files/license.txt 8 | * @package PHamlP 9 | * @subpackage Sass.tree 10 | */ 11 | 12 | /** 13 | * SassWhileNode class. 14 | * Represents a Sass @while loop and a Sass @do loop. 15 | * @package PHamlP 16 | * @subpackage Sass.tree 17 | */ 18 | class SassWhileNode extends SassNode 19 | { 20 | const MATCH = '/^@(do|while)\s+(.+)$/i'; 21 | const LOOP = 1; 22 | const EXPRESSION = 2; 23 | const IS_DO = 'do'; 24 | /** 25 | * @var boolean whether this is a do/while. 26 | * A do/while loop is guarenteed to run at least once. 27 | */ 28 | private $isDo; 29 | /** 30 | * @var string expression to evaluate 31 | */ 32 | private $expression; 33 | 34 | /** 35 | * SassWhileNode constructor. 36 | * @param object $token source token 37 | * @return SassWhileNode 38 | */ 39 | public function __construct($token) 40 | { 41 | parent::__construct($token); 42 | preg_match(self::MATCH, $token->source, $matches); 43 | $this->expression = $matches[self::EXPRESSION]; 44 | $this->isDo = ($matches[self::LOOP] === SassWhileNode::IS_DO); 45 | } 46 | 47 | /** 48 | * Parse this node. 49 | * @param SassContext $context the context in which this node is parsed 50 | * @return array the parsed child nodes 51 | */ 52 | public function parse($context) 53 | { 54 | $children = array(); 55 | if ($this->isDo) { 56 | do { 57 | $children = array_merge($children, $this->parseChildren($context)); 58 | } while ($this->evaluate($this->expression, $context)->toBoolean()); 59 | } else { 60 | while ($this->evaluate($this->expression, $context)->toBoolean()) { 61 | $children = array_merge($children, $this->parseChildren($context)); 62 | } 63 | } 64 | 65 | return $children; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /templates/admin-page.php: -------------------------------------------------------------------------------- 1 | 4 |
5 |

6 | 7 |
8 | 9 | 10 | 11 | get_colors( 'basic' ); 14 | foreach ( $loops as $handle => $nicename ): ?> 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | get_colors( 'advanced' ); 29 | foreach ( $loops as $handle => $nicename ): ?> 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 | 40 |

Show advanced options

41 | 42 |

43 | 44 | 45 |

46 |
47 |
-------------------------------------------------------------------------------- /templates/empty-scheme.php: -------------------------------------------------------------------------------- 1 | 4 |
5 |

6 |
7 | 14 | -------------------------------------------------------------------------------- /templates/updated.php: -------------------------------------------------------------------------------- 1 | 4 |
5 |

6 |
7 | 14 | --------------------------------------------------------------------------------