├── .gitignore ├── .npmignore ├── .sassdocrc ├── .travis.yml ├── CHANGELOG.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── SassyJSON.gemspec ├── dist └── _SassyJSON.scss ├── lib ├── JsonImporter.rb └── SassyJSON.rb ├── package.json ├── sache.json ├── stylesheets ├── SassyJSON.scss ├── decode │ ├── api │ │ └── _json.scss │ ├── decode.scss │ ├── helpers │ │ ├── all │ │ │ ├── _throw.scss │ │ │ └── _value.scss │ │ ├── color │ │ │ ├── _color.scss │ │ │ ├── _get-color-value.scss │ │ │ ├── _hex-to-dec.scss │ │ │ ├── _hex.scss │ │ │ ├── _hsl.scss │ │ │ └── _rgb.scss │ │ ├── map │ │ │ └── _consume.scss │ │ ├── number │ │ │ ├── _find-digits.scss │ │ │ ├── _find-exponent.scss │ │ │ ├── _find-integer.scss │ │ │ └── _pow.scss │ │ └── string │ │ │ ├── _find-ending-quote.scss │ │ │ ├── _length.scss │ │ │ └── _strip-token.scss │ └── types │ │ ├── _bool.scss │ │ ├── _list.scss │ │ ├── _map.scss │ │ ├── _null.scss │ │ ├── _number.scss │ │ └── _string.scss └── encode │ ├── api │ └── _json.scss │ ├── encode.scss │ ├── helpers │ └── _quote.scss │ ├── mixins │ └── _json.scss │ └── types │ ├── _bool.scss │ ├── _color.scss │ ├── _list.scss │ ├── _map.scss │ ├── _null.scss │ ├── _number.scss │ └── _string.scss └── test ├── decode ├── _index.scss ├── api │ └── _json.scss ├── fixtures │ └── test.json ├── helpers │ ├── all │ │ └── _value.scss │ ├── color │ │ ├── _color.scss │ │ ├── _get-color-value.scss │ │ ├── _hex-to-dec.scss │ │ ├── _hex.scss │ │ ├── _hsl.scss │ │ └── _rgb.scss │ ├── map │ │ └── _consume.scss │ ├── number │ │ ├── _find-digits.scss │ │ ├── _find-exponent.scss │ │ ├── _find-integer.scss │ │ └── _pow.scss │ └── string │ │ ├── _find-ending-quote.scss │ │ ├── _length.scss │ │ └── _strip-token.scss ├── test.scss └── types │ ├── _bool.scss │ ├── _list.scss │ ├── _map.scss │ ├── _null.scss │ ├── _number.scss │ └── _string.scss ├── encode ├── _index.scss ├── api │ └── _json.scss ├── helpers │ └── _quote.scss ├── test.scss └── types │ ├── _bool.scss │ ├── _color.scss │ ├── _list.scss │ ├── _map.scss │ ├── _null.scss │ ├── _number.scss │ └── _string.scss └── test.scss /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | tmp 3 | .sass-cache 4 | *.gem 5 | npm-debug.log 6 | .sassdoc 7 | sassdocify 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | tmp 3 | dist 4 | .* 5 | sache.json 6 | .gemspec -------------------------------------------------------------------------------- /.sassdocrc: -------------------------------------------------------------------------------- 1 | basePath: "https://github.com/KittyGiraudel/SassyJSON/tree/master/stylesheets" 2 | display: 3 | access: ["public"] 4 | alias: false 5 | watermark: true 6 | 7 | package: "./package.json" 8 | theme: "default" 9 | groups: 10 | undefined: "General" 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | branches: 3 | only: 4 | - master 5 | git: 6 | depth: 1 7 | node_js: 8 | - '0.10' 9 | env: 10 | - SASS_VERSION=3.3.4 11 | before_script: 12 | - gem update --system 13 | - gem install sass -v $SASS_VERSION 14 | - gem install scss-lint 15 | - npm install -g grunt-cli 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | * `1.1.8`: Solved an issue with tabs 3 | * `1.1.7`: Included cosmetic changes made after linting with [scss-lint](https://github.com/causes/scss-lint) 4 | * `1.1.6`: JSON files that are loaded via `@import "data.json?varname` can be pretty printed now. 5 | * `1.1.5`: Fixed `json-encode` mixin to keep `body::before` in the render tree. Making it possible to access via `getComputedStyle(document.body, ':before')` 6 | * `1.1.4`: Small fix for the `npm` release 7 | * `1.1.3`: fixed error in `_pow` introduced after updating to Sass 3.3.0.rc.3 8 | * `1.1.2`: Made it possible to import JSON files via `@import 'relative/path/to/file.json?variable-name'` 9 | * `1.1.1`: fixing a minor issue with the gem 10 | * `1.1.0`: adding the ability to import a JSON file 11 | * `1.0.11`: fixing an issue with scientific number parsing 12 | * `1.0.10`: improving number and string helpers 13 | * `1.0.9`: fixing a bug in `_find-exponent` 14 | * `1.0.8`: fixing a major issue in Ruby Gem 15 | * `1.0.7`: minor fixes and stable Ruby Gem 16 | * `1.0.6`: released a Ruby Gem 17 | * `1.0.5`: improved the encoding mixin 18 | * `1.0.4`: fixed an error in map parsing 19 | * `1.0.3`: slightly edited the mixin to dump JSON to CSS 20 | * `1.0.2`: fixed an issue with string parsing 21 | * `1.0.1`: fixed an issue with alpha color parsing 22 | * `1.0.0`: Stable API. `json-encode` and `json-decode` 23 | * `0.0.2`: added `json-decode` and test 24 | * `0.0.1`: initial commit 25 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | // Modules 4 | grunt.loadNpmTasks('grunt-contrib-sass'); 5 | grunt.loadNpmTasks('grunt-contrib-watch'); 6 | grunt.loadNpmTasks('grunt-contrib-concat'); 7 | grunt.loadNpmTasks('grunt-scsslint'); 8 | grunt.loadNpmTasks('bootcamp'); 9 | 10 | // Grunt Tasks 11 | grunt.initConfig({ 12 | dir : { 13 | src : 'stylesheets', 14 | dist : 'dist' 15 | }, 16 | pkg: grunt.file.readJSON('package.json'), 17 | 18 | concat: { 19 | options: { 20 | banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %> */\n', 21 | }, 22 | dist: { 23 | src: [ 24 | // Decoder 25 | '<%= dir.src %>/decode/helpers/all/_throw.scss', 26 | '<%= dir.src %>/decode/helpers/all/_value.scss', 27 | '<%= dir.src %>/decode/helpers/map/_consume.scss', 28 | '<%= dir.src %>/decode/helpers/string/_find-ending-quote.scss', 29 | '<%= dir.src %>/decode/helpers/string/_strip-token.scss', 30 | '<%= dir.src %>/decode/helpers/string/_length.scss', 31 | '<%= dir.src %>/decode/helpers/color/_color.scss', 32 | '<%= dir.src %>/decode/helpers/color/_get-color-value.scss', 33 | '<%= dir.src %>/decode/helpers/color/_hsl.scss', 34 | '<%= dir.src %>/decode/helpers/color/_rgb.scss', 35 | '<%= dir.src %>/decode/helpers/color/_hex.scss', 36 | '<%= dir.src %>/decode/helpers/color/_hex-to-dec.scss', 37 | '<%= dir.src %>/decode/helpers/number/_pow.scss', 38 | '<%= dir.src %>/decode/helpers/number/_find-integer.scss', 39 | '<%= dir.src %>/decode/helpers/number/_find-digits.scss', 40 | '<%= dir.src %>/decode/helpers/number/_find-exponent.scss', 41 | '<%= dir.src %>/decode/types/_string.scss', 42 | '<%= dir.src %>/decode/types/_bool.scss', 43 | '<%= dir.src %>/decode/types/_null.scss', 44 | '<%= dir.src %>/decode/types/_number.scss', 45 | '<%= dir.src %>/decode/types/_list.scss', 46 | '<%= dir.src %>/decode/types/_map.scss', 47 | '<%= dir.src %>/decode/api/_json.scss', 48 | 49 | // Encoder 50 | '<%= dir.src %>/encode/helpers/_quote.scss', 51 | '<%= dir.src %>/encode/types/_bool.scss', 52 | '<%= dir.src %>/encode/types/_color.scss', 53 | '<%= dir.src %>/encode/types/_list.scss', 54 | '<%= dir.src %>/encode/types/_map.scss', 55 | '<%= dir.src %>/encode/types/_number.scss', 56 | '<%= dir.src %>/encode/types/_string.scss', 57 | '<%= dir.src %>/encode/types/_null.scss', 58 | '<%= dir.src %>/encode/mixins/_json.scss', 59 | '<%= dir.src %>/encode/api/_json.scss' 60 | ], 61 | dest: '<%= dir.dist %>/_<%= pkg.name %>.scss', 62 | }, 63 | }, 64 | 65 | // Sass 66 | sass: { 67 | options: { 68 | style: 'expanded', 69 | require : './lib/JsonImporter.rb', 70 | loadPath: ['./node_modules/bootcamp/dist', './<%= dir.src %>'] 71 | }, 72 | all: { 73 | files: { 74 | './tmp/all.css': './test/test.scss' 75 | } 76 | }, 77 | decoder: { 78 | files: { 79 | './tmp/decode.css': './test/decode/test.scss' 80 | } 81 | }, 82 | encoder: { 83 | files: { 84 | './tmp/encode.css': './test/encode/test.scss' 85 | } 86 | } 87 | }, 88 | 89 | scsslint : { 90 | options : { 91 | excludeLinter : [ 92 | 'ColorKeyword', 93 | 'NameFormat', 94 | 'StringQuotes', 95 | 'SpaceAfterComma', 96 | 'Comment', 97 | 'PropertySpelling' 98 | ] 99 | }, 100 | all: { 101 | src : ['<%= dir.src %>/**/*'] 102 | }, 103 | decoder: { 104 | src : ['<%= dir.src %>/decoder/**/*'] 105 | }, 106 | encoder: { 107 | src : ['<%= dir.src %>/encoder/**/*'] 108 | } 109 | }, 110 | 111 | // Bootcamp 112 | bootcamp: { 113 | all: { 114 | files: { 115 | src: ['./tmp/all.css'] 116 | } 117 | }, 118 | decoder:{ 119 | files: { 120 | src: ['./tmp/decode.css'] 121 | } 122 | }, 123 | encoder:{ 124 | files: { 125 | src: ['./tmp/encode.css'] 126 | } 127 | } 128 | }, 129 | 130 | // Watch 131 | watch: { 132 | all: { 133 | files: [ 134 | './test/**/*.scss', 135 | './<%= dir.src %>/**/*.scss' 136 | ], 137 | tasks: ['test'] 138 | }, 139 | decoder: { 140 | files: [ 141 | './test/decode/**/*.scss', 142 | './<%= dir.src %>/**/*.scss' 143 | ], 144 | tasks: ['test:decoder'] 145 | }, 146 | encoder: { 147 | files: [ 148 | './test/encode/**/*.scss', 149 | './<%= dir.src %>/**/*.scss' 150 | ], 151 | tasks: ['test:encode'] 152 | } 153 | } 154 | }); 155 | 156 | // Tasks 157 | grunt.registerTask('test', function (arg){ 158 | arg = arg || "all"; 159 | 160 | var tasks = ['scsslint', 'sass', 'bootcamp'].map(function (item){ 161 | return item + ":" + arg; 162 | }); 163 | 164 | 165 | grunt.task.run(tasks); 166 | }); 167 | grunt.registerTask('dev', function (arg ){ 168 | arg = arg || "all"; 169 | 170 | var tasks = ['test', 'watch'].map(function (item){ 171 | return item + ":" + arg; 172 | }); 173 | 174 | grunt.task.run(tasks); 175 | }); 176 | grunt.registerTask('build', ['test:all', 'concat']); 177 | }; 178 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Kitty Giraudel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SassyJSON [![NPM version](https://badge.fury.io/js/sassyjson.png)](http://badge.fury.io/js/sassyjson) [![Gem Version](https://badge.fury.io/rb/SassyJSON.png)](http://badge.fury.io/rb/SassyJSON) 2 | 3 | **⚠️ SassyJSON was an experimental project. It is not meant to be used in production. If you need to transit JSON data from and to your Sass layer, have a look into [Eyeglass](https://github.com/sass-eyeglass/eyeglass) and [Sassport](https://github.com/davidkpiano/sassport).** 4 | 5 | SassyJSON is a Sass-powered API for JSON. It provides you the classic `json-encode` and `json-decode` directly from your Sass files. We'll leave you the only judges of the point of this. 6 | 7 | ## Install 8 | 9 | SassyJSON is available on [npm](https://npmjs.org/) or as a [Ruby Gem](http://rubygems.org/gems/SassyJSON). 10 | 11 | ### Git 12 | 13 | ``` git 14 | git clone https://github.com/KittyGiraudel/SassyJSON.git && cd SassyJSON 15 | ``` 16 | 17 | ### npm 18 | 19 | ``` bash 20 | npm install sassyjson --save-dev 21 | ``` 22 | 23 | ### Compass extension 24 | 25 | 1. `gem install SassyJSON` 26 | 2. Add `require 'SassyJSON'` to your `config.rb` 27 | 3. Import it in your stylesheets with `@import 'SassyJSON'` 28 | 29 | ### Sass 30 | 31 | If you only want to play around the code without cloning the repo or using npm, you can find a [single file](https://github.com/KittyGiraudel/SassyJSON/blob/master/dist/_SassyJSON.scss) containing the whole API in the [dist](https://github.com/KittyGiraudel/SassyJSON/tree/master/dist) folder. 32 | 33 | Also, SassyJSON is available at [Sassmeister](http://sassmeister.com/). 34 | 35 | ## Example 36 | 37 | ### Encoding Sass to JSON 38 | 39 | #### Sass 40 | 41 | ``` scss 42 | $map: ((a: (1 2 ( b : 1 )), b: ( #444444, false, ( a: 1, b: test ) ), c: (2 3 4 string))); 43 | 44 | @include json-encode($map); 45 | ``` 46 | 47 | #### CSS 48 | 49 | ``` css 50 | /*! json-encode: '{"a": [1, 2, {"b": 1}], "b": ["#444444", false, {"a": 1, "b": "test"}], "c": [2, 3, 4, "string"]}' */ 51 | 52 | body::before { 53 | display:block; 54 | width:0;height:0; 55 | overflow:hidden; 56 | content: '{"a": [1, 2, {"b": 1}], "b": ["#444444", false, {"a": 1, "b": "test"}], "c": [2, 3, 4, "string"]}'; 57 | } 58 | 59 | head { 60 | font-family: '{"a": [1, 2, {"b": 1}], "b": ["#444444", false, {"a": 1, "b": "test"}], "c": [2, 3, 4, "string"]}'; 61 | } 62 | 63 | @media -json-encode { 64 | json { 65 | json: '{"a": [1, 2, {"b": 1}], "b": ["#444444", false, {"a": 1, "b": "test"}], "c": [2, 3, 4, "string"]}'; 66 | } 67 | } 68 | ``` 69 | 70 | If you want to restrict the output to only one of the three drivers (comment, media query or regular output) you can pass a flag as the second parameter with one of the four following keywords: `all`, `comment`, `media` or `regular`. Default is `all`. 71 | 72 | ### Decoding JSON to Sass 73 | 74 | ``` scss 75 | $json-decode: json-decode('{"a": [1, 2, {"b": 1}], "b": ["#444444", false, {"a": 1, "b": "test"}], "c": [2, 3, 4, "string"]}'); 76 | // ("a": 1 2 ("b": 1), "b": #444444 false ("a": 1, "b": "test"), "c": 2 3 4 "string") 77 | ``` 78 | 79 | ## Importing and decoding a JSON file 80 | 81 | To importe and decode an external `.json` file directly into a Sass variable: 82 | 83 | ``` scss 84 | @import 'SassyJSON'; // Import SassyJSON first! 85 | @import 'relative/path/to/file.json?variable-name'; 86 | // Do something with $variable-name 87 | ``` 88 | 89 | **Important:** 90 | 91 | * the path to the JSON file is relative to importing file 92 | * the get parameter is the variable name to use it in Sass 93 | 94 | ## Requirements 95 | 96 | All you need is a clean version of Sass 3.3. Otherwise it's just pure Sass madness. 97 | 98 | ## Development 99 | 100 | ### You need 101 | 102 | * [NodeJS](http://nodejs.org) 103 | * [Ruby](https://www.ruby-lang.org/) 104 | * Sass 3.3 via `gem install sass --pre` 105 | * `grunt-cli` via `npm install -g grunt-cli` 106 | 107 | ### How to 108 | 109 | 1. Fork this repository 110 | 2. Run `npm install` 111 | 3. `grunt dev` 112 | 4. Make your changes + write tests 113 | 5. Commit + Pull request 114 | 115 | ## Credits 116 | 117 | * [Fabrice Weinberg](http://twitter.com/fweinb) 118 | * [Kitty Giraudel](http://twitter.com/KittyGiraudel) 119 | -------------------------------------------------------------------------------- /SassyJSON.gemspec: -------------------------------------------------------------------------------- 1 | require './lib/SassyJSON' 2 | 3 | Gem::Specification.new do |s| 4 | # Release Specific Information 5 | s.version = SassyJSON::VERSION 6 | s.date = SassyJSON::DATE 7 | 8 | # Gem Details 9 | s.name = "SassyJSON" 10 | s.rubyforge_project = "SassyJSON" 11 | s.description = %q{Sass API for JSON} 12 | s.summary = %q{SassyJSON is a Sass-powered API for JSON. It provides you the classic json-encode and json-decode directly from your Sass files.} 13 | s.authors = ["Kitty Giraudel", "Fabrice Weinberg"] 14 | s.email = ["kitty.giraudel@gmail.com", "fabrice@weinberg.me"] 15 | s.homepage = "https://github.com/KittyGiraudel/SassyJSON/" 16 | 17 | # README file 18 | s.files = ["README.md"] 19 | 20 | # CHANGELOG 21 | s.files += ["CHANGELOG.md"] 22 | 23 | # Library Files 24 | s.files += Dir.glob("lib/**/*.*") 25 | 26 | # Sass Files 27 | s.files += Dir.glob("stylesheets/**/*.*") 28 | 29 | # Add to require 30 | s.require_paths = ["lib", "stylesheets"] 31 | 32 | # Gem Bookkeeping 33 | s.required_rubygems_version = ">= 1.3.6" 34 | s.rubygems_version = %q{1.3.6} 35 | 36 | # Deps 37 | #s.add_runtime_dependency 'sass', '>= 3.3.0' 38 | end 39 | -------------------------------------------------------------------------------- /dist/_SassyJSON.scss: -------------------------------------------------------------------------------- 1 | /*! sassyjson - v1.1.8 - 2015-09-24 */ 2 | /// Logs an error at `$pointer` with `$string` message 3 | /// @access private 4 | /// @param {String} $string - error message 5 | /// @param {Number} $pointer - pointer position 6 | @function _throw($string, $pointer) { 7 | @error "ERROR::#{$pointer}::#{$string}"; 8 | } 9 | 10 | /// Delay parsing to type-specific function 11 | /// @access private 12 | /// according to found character 13 | /// @param {String} $source - JSON complete source 14 | /// @param {Number} $pointer - current pointer 15 | /// @throw Unexpected token $token. 16 | /// @throw Empty JSON string. 17 | /// @return {List} - (new pointer, parsed value) 18 | /// @require {function} _throw 19 | /// @require {function} _json-decode--map 20 | /// @require {function} _json-decode--list 21 | /// @require {function} _json-decode--true 22 | /// @require {function} _json-decode--false 23 | /// @require {function} _json-decode--string 24 | /// @require {function} _json-decode--null 25 | /// @require {function} _json-decode--number 26 | @function _json-decode--value($source, $pointer) { 27 | $length: str-length($source); 28 | 29 | @while $pointer <= $length { 30 | $token: str-slice($source, $pointer, $pointer); 31 | $pointer: $pointer + 1; 32 | 33 | @if $token == '{' { 34 | @return _json-decode--map($source, $pointer); 35 | } @else if $token == '[' { 36 | @return _json-decode--list($source, $pointer); 37 | } @else if $token == 't' { 38 | @return _json-decode--true($source, $pointer); 39 | } @else if $token == 'f' { 40 | @return _json-decode--false($source, $pointer); 41 | } @else if $token == '"' { 42 | @return _json-decode--string($source, $pointer); 43 | } @else if $token == 'n' { 44 | @return _json-decode--null($source, $pointer); 45 | } @else if index('1' '2' '3' '4' '5' '6' '7' '8' '9' '0' '-' '.', $token) { 46 | @return _json-decode--number($source, $pointer); 47 | } @else if $token == ' ' or $token == " " { 48 | // @continue; 49 | } @else { 50 | @return _throw("Unexpected token `#{$token}`.", $pointer); 51 | } 52 | } 53 | 54 | @return _throw("Empty JSON string.", $pointer); 55 | } 56 | 57 | /// Move pointer to position of token 58 | /// @access private 59 | /// @param {String} $source - JSON complete source 60 | /// @param {Number} $pointer - current pointer 61 | /// @param {String} $token - token to reach 62 | /// @throw Expected $token; found $char. 63 | /// @throw Expected $token but reached end of stream. 64 | /// @return {Number} - new pointer 65 | /// @require {function} _throw 66 | @function _consume($source, $pointer, $token) { 67 | $length: str-length($source); 68 | 69 | @while $pointer <= $length { 70 | $char: str-slice($source, $pointer, $pointer); 71 | $pointer: $pointer + 1; 72 | 73 | @if $char == $token { 74 | @return $pointer; 75 | } @else if $char == " " or $char == " " { 76 | // @continue; 77 | } @else { 78 | @return _throw("Expected `#{$token}`; found `#{$char}`.", $pointer); 79 | } 80 | } 81 | 82 | @return _throw("Expected `#{$token}` but reached end of stream.", $pointer); 83 | } 84 | 85 | /// Will find the first non escaped quote in a JSON String 86 | /// @access private 87 | /// @param {String} $string - to search in 88 | /// @return {Number} - position of the first non escaped quote 89 | @function _find-ending-quote($string) { 90 | $backslash: str-slice('\\', 1, 1); // Dirty hack to have a single backslash 91 | $escaped-chars: '"\/bfnrtu'; // Characters that can be escaped in JSON 92 | $hexadecimal-chars: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9' '0' 'a' 'b' 'c' 'd' 'e' 'f'; 93 | 94 | $pos: 1; 95 | $length: str-length($string); 96 | $backslash-found: false; 97 | 98 | @while $pos <= $length { 99 | $char: to-lower-case(str-slice($string, $pos, $pos)); 100 | 101 | // Previous char was a backslash 102 | @if $backslash-found { 103 | 104 | // Special case, the 'u' character 105 | @if $char == 'u' { 106 | // Next 4 characters must be hexadecimal 107 | @if not index($hexadecimal-chars, str-slice($string, $pos + 1, $pos + 1)) or 108 | not index($hexadecimal-chars, str-slice($string, $pos + 2, $pos + 2)) or 109 | not index($hexadecimal-chars, str-slice($string, $pos + 3, $pos + 3)) or 110 | not index($hexadecimal-chars, str-slice($string, $pos + 4, $pos + 4)) { 111 | @return 0; 112 | } 113 | 114 | $pos: $pos + 4; 115 | } @else if not str-index($escaped-chars, $char) { 116 | // Invalid character escaped 117 | @return 0; 118 | } 119 | 120 | $backslash-found: false; 121 | } @else if $char == $backslash { 122 | $backslash-found: true; 123 | } @else if $char == '"' { 124 | // Unescaped quote found 125 | @return $pos; 126 | } 127 | 128 | $pos: $pos + 1; 129 | } 130 | 131 | // No end of string found 132 | @return 0; 133 | } 134 | 135 | /// Strip special carriage return characters 136 | /// @access private 137 | /// @param {String} $string - string to parse 138 | /// @param {String} $search - char to strip 139 | /// @param {String} $replace ('') - new substring 140 | /// @return {String} - new string 141 | @function _strip-token($string, $search, $replace: '') { 142 | $index: str-index($string, $search); 143 | 144 | @if $index { 145 | @return str-slice($string, 1, $index - 1) + $replace + _strip-token(str-slice($string, $index + str-length($search)), $search, $replace); 146 | } 147 | 148 | @return $string; 149 | } 150 | 151 | /// Parses a JSON encoded string to see if it's a CSS length 152 | /// @access private 153 | /// @param {String} $string - JSON string 154 | /// @return {Number | String} - string or number, depending on the match 155 | /// @require {function} _json-decode--number 156 | @function _length($string) { 157 | @if type-of($string) == "number" { 158 | @return $string; 159 | } 160 | 161 | $strings: 'px' 'cm' 'mm' '%' 'ch' 'pica' 'in' 'em' 'rem' 'pt' 'pc' 'ex' 'vw' 'vh' 'vmin' 'vmax'; 162 | $units: 1px 1cm 1mm 1% 1ch 1pica 1in 1em 1rem 1pt 1pc 1ex 1vw 1vh 1vmin 1vmax; 163 | $number: ""; 164 | $unit: ""; 165 | 166 | @for $i from 1 through str-length($string) { 167 | $c: str-slice($string, $i, $i); 168 | @if $c == ' ' or $c == " " { 169 | @if $number != "" { 170 | @return $string; 171 | } 172 | } @else if index('0' '1' '2' '3' '4' '5' '6' '7' '8' '9' '-' '.', $c) { 173 | $number: $number + $c; 174 | } @else { 175 | @if $number == "" { 176 | @return $string; 177 | } 178 | $unit: $unit + $c; 179 | } 180 | } 181 | 182 | $number: nth(_json-decode--number($number, 2), 2); 183 | $index: index($strings, to-lower-case($unit)); 184 | 185 | @if $index and $index > 0 { 186 | @return $number * nth($units, $index); 187 | } 188 | 189 | @return $string; 190 | } 191 | 192 | /// Parses a JSON encoded string to see if it's a CSS color 193 | /// @access private 194 | /// @param {String} $string - JSON string 195 | /// @return {Color | String} - string or number, depending on the match 196 | /// @require {function} _from-hex 197 | /// @require {function} _from-rgb 198 | /// @require {function} _from-hsl 199 | @function _color($string) { 200 | @if type-of($string) == "color" { 201 | @return $string; 202 | } 203 | 204 | $string-lower: to-lower-case($string); 205 | $colors: transparent black silver gray white maroon red purple fuchsia green lime olive yellow navy blue teal aqua aliceblue antiquewhite aqua aquamarine azure beige bisque black blanchedalmond blue blueviolet brown burlywood cadetblue chartreuse chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan darkgoldenrod darkgray darkgreen darkgrey darkkhaki darkmagenta darkolivegreen darkorange darkorchid darkred darksalmon darkseagreen darkslateblue darkslategray darkslategrey darkturquoise darkviolet deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite forestgreen fuchsia gainsboro ghostwhite gold goldenrod gray green greenyellow grey honeydew hotpink indianred indigo ivory khaki lavender lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan lightgoldenrodyellow lightgray lightgreen lightgrey lightpink lightsalmon lightseagreen lightskyblue lightslategray lightslategrey lightsteelblue lightyellow lime limegreen linen magenta maroon mediumaquamarine mediumblue mediumorchid mediumpurple mediumseagreen mediumslateblue mediumspringgreen mediumturquoise mediumvioletred midnightblue mintcream mistyrose moccasin navajowhite navy oldlace olive olivedrab orange orangered orchid palegoldenrod palegreen paleturquoise palevioletred papayawhip peachpuff peru pink plum powderblue purple red rosybrown royalblue saddlebrown salmon sandybrown seagreen seashell sienna silver skyblue slateblue slategray slategrey snow springgreen steelblue tan teal thistle tomato turquoise violet wheat white whitesmoke yellow yellowgreen; 206 | $keywords: (); 207 | 208 | // Filling $keywords with stringified color keywords 209 | @each $color in $colors { 210 | $keywords: append($keywords, $color + ""); 211 | } 212 | 213 | // Deal with inherit keyword 214 | @if $string-lower == "inherit" { 215 | @return unquote($string); 216 | } 217 | 218 | @if index($keywords, $string-lower) { 219 | // Deal with color keywords 220 | @return nth($colors, index($keywords, $string-lower)); 221 | } @else if str-slice($string-lower, 1, 1) == '#' { 222 | // Deal with hexadecimal triplets 223 | @return _from-hex($string); 224 | } @else if str-slice($string-lower, 1, 3) == 'rgb' { 225 | // Deal with rgb(a) colors 226 | @return _from-rgb($string); 227 | } @else if str-slice($string-lower, 1, 3) == 'hsl' { 228 | // Deal with hsl(a) colors 229 | @return _from-hsl($string); 230 | } @else { 231 | // Return string 232 | @return $string; 233 | } 234 | } 235 | 236 | /// Cast a stringified number / stringified percentage into number type 237 | /// @access private 238 | /// @param {String} $string - JSON string 239 | /// @return {Number} - unitless number or percentage 240 | /// @require {function} _json-decode--number 241 | @function _get-color-value($string) { 242 | $first: str-slice($string, 1, 1); 243 | 244 | // Pad <1 values with a leading 0 245 | @if $first == '.' { 246 | $string: '0' + $string; 247 | } 248 | 249 | $last: str-slice($string, -1, -1); 250 | 251 | @return if( 252 | $last == '%', 253 | nth(_json-decode--number(str-slice($string, 1, -2), 2), 2) * 1%, 254 | nth(_json-decode--number($string, 2), 2) 255 | ); 256 | } 257 | 258 | /// Cast a JSON encoded string into a hsl(a) color 259 | /// @access private 260 | /// @param {String} $string - JSON string 261 | /// @return {Color | String} - string or hsl(a) color, depending on the match 262 | /// @require {function} _get-color-value 263 | @function _from-hsl($string) { 264 | $frags: (); 265 | $string-lower: to-lower-case($string); 266 | $is-alpha: str-slice($string-lower, 4, 4) == 'a'; 267 | $length: str-length($string); 268 | $start: str-index($string, "("); 269 | 270 | @for $i from $start through $length { 271 | $token: str-slice($string-lower, $i, $i); 272 | @if $token == ' ' or $token == " " { 273 | // @continue; 274 | } @else if $token == '(' or $token == ',' { 275 | $frags: append($frags, ""); 276 | } @else if $token == ')' { 277 | @if length($frags) != if($is-alpha, 4, 3) { @return $string; } // Parsing error 278 | $hue: _get-color-value(nth($frags, 1)); 279 | $saturation: _get-color-value(nth($frags, 2)); 280 | $lightness: _get-color-value(nth($frags, 3)); 281 | 282 | @if not $hue or not $saturation or not $lightness { 283 | @return $string; 284 | } 285 | 286 | @if $is-alpha { 287 | @if length($frags) != 4 { @return $string; } // No alpha channel found 288 | $alpha: _get-color-value(nth($frags, 4)); 289 | @if not $alpha { @return $string; } // Error parsing alpha channel 290 | @return hsla($hue, $saturation, $lightness, $alpha); 291 | } 292 | 293 | @return hsl($hue, $saturation, $lightness); 294 | } @else { 295 | $frags: set-nth($frags, length($frags), nth($frags, length($frags)) + $token); 296 | } 297 | } 298 | 299 | @return $string; 300 | } 301 | 302 | /// Cast a JSON encoded string into a rgb(a) color 303 | /// @access private 304 | /// @param {String} $string - JSON string 305 | /// @return {Color | String} - string or rgb(a) color depending on the match 306 | /// @require {function} _get-color-value 307 | @function _from-rgb($string) { 308 | $string-lower: to-lower-case($string); 309 | $frags: (); 310 | $is-alpha: str-slice($string-lower, 4, 4) == 'a'; 311 | $start: str-index($string, "("); 312 | $length: str-length($string); 313 | 314 | @for $i from $start through $length { 315 | $token: str-slice($string-lower, $i, $i); 316 | @if $token == ' ' or $token == " " { 317 | // @continue; 318 | } @else if $token == '(' or $token == ',' { 319 | $frags: append($frags, ""); 320 | } @else if $token == ')' { 321 | @if length($frags) != if($is-alpha, 4, 3) { @return $string; } // Parsing error 322 | $red: _get-color-value(nth($frags, 1)); 323 | $green: _get-color-value(nth($frags, 2)); 324 | $blue: _get-color-value(nth($frags, 3)); 325 | 326 | @if not $red or not $green or not $blue { 327 | @return $string; 328 | } 329 | 330 | @if $is-alpha { 331 | @if length($frags) != 4 { @return $string; } // No alpha channel found 332 | $alpha: _get-color-value(nth($frags, 4)); 333 | @if not $alpha { @return $string; } // Error parsing alpha channel 334 | @return rgba($red, $green, $blue, $alpha); 335 | } 336 | 337 | @return rgb($red, $green, $blue); 338 | } @else { 339 | $frags: set-nth($frags, length($frags), nth($frags, length($frags)) + $token); 340 | } 341 | } 342 | 343 | @return $string; 344 | } 345 | 346 | /// Cast a JSON encoded string into a hexadecimal color 347 | /// @access private 348 | /// @param {String} $string - JSON string 349 | /// @return {Color | String} - string or hex color depending on the match 350 | /// @require {function} _hex-to-dec 351 | @function _from-hex($string) { 352 | $string-lower: to-lower-case($string); 353 | $r: ""; $g: ""; $b: ""; 354 | $hex: "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "a" "b" "c" "d" "e" "f"; 355 | $length: str-length($string); 356 | $max: if($length == 4, 1, 2); 357 | 358 | // Check for length accuracy 359 | @if $length != 4 and $length != 7 { 360 | @return $string; 361 | } 362 | 363 | // Loop from the second character (omitting #) 364 | @for $i from 2 through $length { 365 | $c: str-slice($string-lower, $i, $i); 366 | 367 | // If wrong character, return 368 | @if index($hex, $c) == null { 369 | @return $string; 370 | } 371 | 372 | @if str-length($r) < $max { 373 | $r: $r + $c; 374 | } @else if str-length($g) < $max { 375 | $g: $g + $c; 376 | } @else if str-length($b) < $max { 377 | $b: $b + $c; 378 | } 379 | } 380 | 381 | @if $length == 4 { 382 | $r: $r + $r; 383 | $g: $g + $g; 384 | $b: $b + $b; 385 | } 386 | 387 | @return rgb(_hex-to-dec($r), _hex-to-dec($g), _hex-to-dec($b)); 388 | } 389 | 390 | /// Convert an hexadecimal number to a decimal number 391 | /// @access private 392 | /// @param {String} $string - hexadecimal value 393 | /// @return {Number} - decimal number 394 | @function _hex-to-dec($string) { 395 | $hex: "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "a" "b" "c" "d" "e" "f"; 396 | $string: to-lower-case($string); 397 | $length: str-length($string); 398 | 399 | $dec: 0; 400 | @for $i from 1 through $length { 401 | $factor: 1 + (15 * ($length - $i)); 402 | $index: index($hex, str-slice($string, $i, $i)); 403 | $dec: $dec + $factor * ($index - 1); 404 | } 405 | 406 | @return $dec; 407 | } 408 | 409 | /// Power function 410 | /// @access private 411 | /// @param {Number} $x - number 412 | /// @param {Number} $n - power 413 | /// @return {Number} 414 | @function _pow($x, $n) { 415 | @if $n == 0 { @return 1; } 416 | $ret: 1; 417 | @if $n >= 0 { 418 | @for $i from 1 through $n { 419 | $ret: $ret * $x; 420 | } 421 | } @else { 422 | @for $i from $n to 0 { 423 | $ret: $ret / $x; 424 | } 425 | } 426 | 427 | @return $ret; 428 | } 429 | 430 | /// Parses a JSON encoded number to find the integer part 431 | /// @access private 432 | /// @param {String} $source - JSON complete source 433 | /// @param {Number} $pointer - current pointer 434 | /// @throw Unexpected token $token. 435 | /// @return {List} (new pointer, parsed number) 436 | /// @require {function} _throw 437 | @function _find-integer($source, $pointer) { 438 | $source: to-lower-case($source); 439 | $length: str-length($source); 440 | $numbers: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9'; 441 | $result: 0; 442 | 443 | @while $pointer <= $length { 444 | $token: str-slice($source, $pointer, $pointer); 445 | $index: index($numbers, $token); 446 | 447 | @if $token == '-' { 448 | // do nothing 449 | } @else if $index { 450 | $result: $result * 10 + ($index - 1); 451 | } @else { 452 | @if index('e' '.' ' ' ',' ']' '}', $token) { 453 | @return $pointer, $result; 454 | } 455 | 456 | @return _throw("Unexpected token `" + $token + "`.", $pointer); 457 | } 458 | 459 | $pointer: $pointer + 1; 460 | } 461 | 462 | @return $pointer, $result; 463 | } 464 | 465 | /// Parses a JSON encoded number to find the digits 466 | /// @access private 467 | /// @param {String} $source - JSON complete source 468 | /// @param {Number} $pointer - current pointer 469 | /// @throw Unexpected token $token. 470 | /// @return {List} (new pointer, parsed number) 471 | /// @require {function} _throw 472 | @function _find-digits($source, $pointer) { 473 | $source: to-lower-case($source); 474 | $length: str-length($source); 475 | $numbers: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9'; 476 | $result: null; 477 | $runs: 1; 478 | 479 | @while $pointer <= $length { 480 | $token: str-slice($source, $pointer, $pointer); 481 | $index: index($numbers, $token); 482 | 483 | @if $token == '.' { 484 | // @continue; 485 | } @else if $index and $index > 0 { 486 | $runs: $runs * 10; 487 | $result: if($result == null, ($index - 1), $result * 10 + ($index - 1)); 488 | } @else { 489 | @if index('e' '.' ' ' ',' ']' '}', $token) { 490 | @return $pointer, if($result != null, $result / $runs, $result); 491 | } 492 | 493 | @return _throw("Unexpected token `#{$token}`.", $pointer); 494 | } 495 | 496 | $pointer: $pointer + 1; 497 | } 498 | 499 | @return $pointer, if($result != null, $result / $runs, $result); 500 | } 501 | 502 | /// Parses a JSON encoded number to find the exponent part 503 | /// @access private 504 | /// @param {String} $source - JSON complete source 505 | /// @param {Number} $pointer - current pointer 506 | /// @throw Unexpected token $token. 507 | /// @return {List} - (new pointer, parsed number) 508 | /// @require {function} _throw 509 | @function _find-exponent($source, $pointer) { 510 | $source: to-lower-case($source); 511 | $length: str-length($source); 512 | $numbers: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9'; 513 | $result: null; 514 | $minus: null; 515 | 516 | @while $pointer <= $length { 517 | $token: str-slice($source, $pointer, $pointer); 518 | $index: index($numbers, $token); 519 | 520 | @if $token == 'e' { 521 | // @continue; 522 | } @else if $token == '-' { 523 | @if $minus != null { 524 | @return _throw("Unexpected token `-`.", $pointer); 525 | } 526 | $minus: true; 527 | } @else if $token == '+' { 528 | @if $minus != null { 529 | @return _throw("Unexpected token `+`.", $pointer); 530 | } 531 | $minus: false; 532 | } @else if $index and $index > 0 { 533 | $result: if($result == null, ($index - 1), $result * 10 + ($index - 1)); 534 | } @else { 535 | @if index(' ' ',' ']' '}', $token) == null { 536 | @return _throw("Unexpected token `" + $token + "`.", $pointer); 537 | } 538 | 539 | @return $pointer, if($minus and $result != null, $result * -1, $result); 540 | } 541 | 542 | $pointer: $pointer + 1; 543 | } 544 | 545 | @return $pointer, if($minus and $result != null, $result * -1, $result); 546 | } 547 | 548 | /// Parses a JSON encoded string 549 | /// @param {String} $source - JSON complete source 550 | /// @param {Number} $pointer - current pointer 551 | /// @throw Unterminated string. 552 | /// @return {List} (new pointer, parsed string / color / length) 553 | /// @require {function} _throw 554 | /// @require {function} _find-ending-quote 555 | /// @require {function} _strip-token 556 | /// @require {function} _color 557 | /// @require {function} _length 558 | @function _json-decode--string($source, $pointer) { 559 | // Check for the end of the string 560 | $temp: str-slice($source, $pointer); 561 | $end: _find-ending-quote($temp); 562 | $string: ""; 563 | 564 | // If no end is found 565 | @if not $end or $end == 0 { 566 | @return _throw("Unterminated string.", $pointer); 567 | } @else if $end > 1 { 568 | // If string is not empty 569 | $string: str-slice($temp, 1, $end - 1); 570 | 571 | $cr: " 572 | "; 573 | $string: _strip-token($string, "\\\r", $cr); 574 | $string: _strip-token($string, "\\\n", $cr); 575 | $string: _strip-token($string, '\\\"', '"'); 576 | 577 | // Test whether the string could be a CSS length 578 | $string: _length($string); 579 | 580 | // Test whether the string could be a CSS color 581 | @if type-of($string) == "string" { 582 | $string: _color($string); 583 | } 584 | } 585 | 586 | @return ($pointer + $end, $string); 587 | } 588 | /// Parses a JSON encoded `true` 589 | /// @param {String} $source - JSON complete source 590 | /// @param {Number} $pointer - current pointer 591 | /// @throw Unexpected token `t`. 592 | /// @return {List} - (new pointer, true) 593 | /// @require {function} _throw 594 | @function _json-decode--true($source, $pointer) { 595 | $length: str-length($source); 596 | 597 | @if $length - $pointer < 2 598 | or str-slice($source, $pointer, $pointer) != 'r' 599 | or str-slice($source, $pointer + 1, $pointer + 1) != 'u' 600 | or str-slice($source, $pointer + 2, $pointer + 2) != 'e' { 601 | @return _throw("Unexpected token: `t`.", $pointer); 602 | } 603 | 604 | @return ($pointer + 3, true); 605 | } 606 | 607 | /// Parses a JSON encoded `false` 608 | /// @param {String} $source - JSON complete source 609 | /// @param {Number} $pointer - current pointer 610 | /// @throw Unexpected token `f`. 611 | /// @return {List} - (new pointer, false) 612 | /// @require {function} _throw 613 | @function _json-decode--false($source, $pointer) { 614 | $length: str-length($source); 615 | 616 | @if $length - $pointer < 3 617 | or str-slice($source, $pointer, $pointer) != 'a' 618 | or str-slice($source, $pointer + 1, $pointer + 1) != 'l' 619 | or str-slice($source, $pointer + 2, $pointer + 2) != 's' 620 | or str-slice($source, $pointer + 3, $pointer + 3) != 'e' { 621 | @return _throw("Unexpected token: `f`.", $pointer); 622 | } 623 | 624 | @return ($pointer + 4, false); 625 | } 626 | 627 | /// Parses a JSON encoded `null` 628 | /// @param {String} $source - JSON complete source 629 | /// @param {Number} $pointer - current pointer 630 | /// @throw "Unexpected token `n`." 631 | /// @return {List} (new pointer, null) 632 | /// @require {function} _throw 633 | @function _json-decode--null($source, $pointer) { 634 | $length: str-length($source); 635 | 636 | @if $length - $pointer < 2 637 | or str-slice($source, $pointer, $pointer) != 'u' 638 | or str-slice($source, $pointer + 1, $pointer + 1) != 'l' 639 | or str-slice($source, $pointer + 2, $pointer + 2) != 'l' { 640 | @return _throw("Unexpected token: `n`.", $pointer); 641 | } 642 | 643 | @return ($pointer + 3, null); 644 | } 645 | 646 | /// Parses a JSON encoded number 647 | /// @param {String} $source - JSON complete source 648 | /// @param {Number} $pointer - current pointer 649 | /// @throw "Unexpected token $token." 650 | /// @throw "Unexpected end of stream." 651 | /// @return {List} (new pointer, parsed number) 652 | /// @require {function} _throw 653 | /// @require {function} _find-integer 654 | /// @require {function} _find-digits 655 | /// @require {function} _find-exponent 656 | /// @require {function} _pow 657 | @function _json-decode--number($source, $pointer) { 658 | $pointer: $pointer - 1; // Move back pointer to begininng of number 659 | $allowed: '-' '0' '1' '2' '3' '4' '5' '6' '7' '8' '9'; // Allowed characted to start with 660 | $first: str-slice($source, $pointer, $pointer); // First character of the number 661 | $minus: $first == '-'; // Is it negative? 662 | 663 | // Early check for errors 664 | @if not index($allowed, $first) { 665 | @return _throw("Unexpected token `" + $first + "`.", $pointer); 666 | } 667 | 668 | // Find the integer part 669 | $find-integer: _find-integer($source, $pointer); 670 | $pointer: nth($find-integer, 1); 671 | $result: nth($find-integer, 2); 672 | @if not $result { // Error occured 673 | @return $find-integer; 674 | } 675 | 676 | // Find digits 677 | @if str-slice($source, $pointer, $pointer) == '.' { 678 | $find-digits: _find-digits($source, $pointer); 679 | $pointer: nth($find-digits, 1); 680 | $digits: nth($find-digits, 2); 681 | 682 | @if $digits == null { // Empty digits, throw error 683 | @return _throw("Unexpected end of stream.", $pointer); 684 | } @else if $digits == false { // Error occured, return it 685 | @return $find-digits; 686 | } 687 | 688 | $result: $result + $digits; 689 | } 690 | 691 | // Find exponent 692 | @if to-lower-case(str-slice($source, $pointer, $pointer)) == 'e' { 693 | $find-exponent: _find-exponent($source, $pointer); 694 | $pointer: nth($find-exponent, 1); 695 | $exponent: nth($find-exponent, 2); 696 | 697 | @if $exponent == null { // Empty exponent, throw error 698 | @return _throw("Unexpected end of stream.", $pointer); 699 | } @else if $exponent == false { // Error occured, return it 700 | @return $find-exponent; 701 | } 702 | 703 | $result: $result * _pow(10, $exponent); 704 | } 705 | 706 | @return ($pointer, if($minus, $result * -1, $result)); 707 | } 708 | 709 | /// Parses a JSON encoded array 710 | /// @param {String} $source - JSON complete source 711 | /// @param {Number} $pointer - current pointer 712 | /// @throw Unexpected comma in array literal. 713 | /// @throw Missing comma in array literal. 714 | /// @throw Unterminated array literal. 715 | /// @return {List} (new pointer, parsed list) 716 | /// @require {function} _throw 717 | /// @require {function} _json-decode--value 718 | @function _json-decode--list($source, $pointer) { 719 | $length: str-length($source); 720 | $list: (); 721 | $needs-comma: false; 722 | 723 | @if $pointer <= $length and str-slice($source, $pointer, $pointer) == "]" { 724 | @return ($pointer + 1, $list); 725 | } 726 | 727 | @while $pointer <= $length { 728 | $token: str-slice($source, $pointer, $pointer); 729 | 730 | @if $token == "]" { 731 | @if not $needs-comma and length($list) != 0 { 732 | @return _throw("Unexpected comma in array literal.", $pointer); 733 | } 734 | 735 | // Do it the Sass way and destruct a single item array to an element. 736 | @return ($pointer + 1, if(length($list) == 1, nth($list, 1), $list)); 737 | } @else if $token == " " or $token == " " { 738 | $pointer: $pointer + 1; 739 | } @else if $token == "," { 740 | @if not $needs-comma { 741 | @return _throw("Unexpected comma in array literal.", $pointer); 742 | } 743 | 744 | $needs-comma: false; 745 | $pointer: $pointer + 1; 746 | } @else { 747 | @if $needs-comma { 748 | @return _throw("Missing comma in array literal.", $pointer); 749 | } 750 | 751 | $read: _json-decode--value($source, $pointer); 752 | $pointer: nth($read, 1); 753 | $list: append($list, nth($read, 2)); 754 | $needs-comma: true; 755 | } 756 | } 757 | 758 | @return _throw("Unterminated array literal.", $pointer); 759 | } 760 | 761 | /// Parses a JSON encoded object 762 | /// @param {String} $source - JSON complete source 763 | /// @param {Number} $pointer - current pointer 764 | /// @throw Unexpected comma in object literal. 765 | /// @throw Unexpected token $token in object literal. 766 | /// @throw Missing comma in object literal. 767 | /// @throw Unterminated object literal. 768 | /// @throw Consuming token `:` failed. 769 | /// @return {List} (new pointer, map) 770 | /// @require {function} _throw 771 | /// @require {function} _consume 772 | /// @require {function} _json-decode--value 773 | /// @require {function} _json-decode--string 774 | @function _json-decode--map($source, $pointer) { 775 | $length: str-length($source); 776 | $map: (); 777 | $needs-comma: false; 778 | 779 | // Deal with empty map 780 | @if $pointer <= $length and str-slice($source, $pointer, $pointer) == "}" { 781 | @return ($pointer + 1, $map); 782 | } 783 | 784 | @while $pointer <= $length { 785 | $token: str-slice($source, $pointer, $pointer); 786 | $pointer: $pointer + 1; 787 | 788 | @if $token == "}" { 789 | @if not $needs-comma and length($map) != 0 { 790 | @return _throw("Unexpected comma in object literal.", $pointer); 791 | } 792 | @return ($pointer, $map); 793 | } @else if $token == " " or $token == " " { 794 | // @continue; 795 | } @else if $token == "," { 796 | @if not $needs-comma { 797 | @return _throw("Unexpected comma in object literal.", $pointer); 798 | } 799 | $needs-comma: false; 800 | } @else if $token == '"' { 801 | @if $needs-comma { 802 | @return _throw("Missing comma in object literal.", $pointer); 803 | } 804 | 805 | // Read key 806 | $read-key: _json-decode--string($source, $pointer); 807 | $pointer: nth($read-key, 1); 808 | $key: nth($read-key, 2); 809 | 810 | // Remove colon 811 | $pointer: _consume($source, $pointer, ":"); 812 | @if length($pointer) > 1 { // If consume has failed 813 | @return _throw("Consuming token `:` failed.", 0); 814 | } 815 | 816 | // Read value 817 | $read-value: _json-decode--value($source, $pointer); 818 | $pointer: nth($read-value, 1); 819 | $value: nth($read-value, 2); 820 | 821 | // Add pair to map 822 | $map: map-merge($map, ($key: $value)); 823 | $needs-comma: true; 824 | } @else { 825 | @return _throw("Unexpected token `" + $token + "` in object literal.", $pointer); 826 | } 827 | } 828 | 829 | @return _throw("Unterminated object literal.", $pointer); 830 | } 831 | 832 | /// Parse a JSON string 833 | /// @access public 834 | /// @param {String} $json - JSON string to parse 835 | /// @throw Input string may not be null. 836 | /// @return {*} 837 | /// @require {function} _json-decode--value 838 | @function json-decode($json) { 839 | $length: str-length($json); 840 | $pointer: 1; 841 | $value: null; 842 | 843 | @if $json == null { 844 | @return _throw("Input string may not be null.", $pointer); 845 | } 846 | 847 | @while $value != false // Stop if error 848 | and $pointer <= $length { 849 | $read: _json-decode--value($json, $pointer); 850 | $pointer: nth($read, 1); 851 | $value: nth($read, 2); 852 | } 853 | 854 | @return $value; 855 | } 856 | 857 | /// Proof quote a value 858 | /// @access private 859 | /// @param {*} $value - value to be quoted 860 | /// @return {String} - quoted value 861 | 862 | @function _proof-quote($value) { 863 | // $value: to-string($value); 864 | @return '"#{$value}"'; 865 | } 866 | 867 | /// Encode a bool to JSON 868 | /// @access private 869 | /// @param {Bool} $bool - bool to be encoded 870 | /// @return {Bool} - encoded bool 871 | @function _json-encode--bool($boolean) { 872 | @return $boolean; 873 | } 874 | 875 | /// Encode a color to JSON 876 | /// @access private 877 | /// @param {Color} $color - color to be encoded 878 | /// @return {String} - encoded color 879 | /// @require {function} _proof-quote 880 | @function _json-encode--color($color) { 881 | @return _proof-quote($color); 882 | } 883 | 884 | /// Encode a list to JSON 885 | /// @access private 886 | /// @param {List} $list - list to be encoded 887 | /// @return {String} - encoded list 888 | /// @require {function} json-encore 889 | @function _json-encode--list($list) { 890 | $str: ""; 891 | 892 | @each $item in $list { 893 | $str: $str + ', ' + json-encode($item); 894 | } 895 | 896 | @return '[' + str-slice($str, 3) + ']'; 897 | } 898 | 899 | /// Encode a map to JSON 900 | /// @access private 901 | /// @param {Map} $map - map to be encoded 902 | /// @return {String} - encoded map 903 | /// @require {function} _proof-quote 904 | /// @require {function} json-encode 905 | @function _json-encode--map($map) { 906 | $str: ""; 907 | 908 | @each $key, $value in $map { 909 | $str: $str + ', ' + _proof-quote($key) + ': ' + json-encode($value); 910 | } 911 | 912 | @return '{' + str-slice($str, 3) + '}'; 913 | } 914 | 915 | /// Encode a number to JSON 916 | /// @access private 917 | /// @param {Number} $number - number to be encoded 918 | /// @return {String} - encoded number 919 | /// @require {function} _proof-quote 920 | @function _json-encode--number($number) { 921 | @return if(unitless($number), $number, _proof-quote($number)); 922 | } 923 | 924 | /// Encode a string to JSON 925 | /// @access private 926 | /// @param {String} $string - string to be encoded 927 | /// @return {String} - encoded string 928 | /// @require {function} _proof-quote 929 | @function _json-encode--string($string) { 930 | @return _proof-quote($string); 931 | } 932 | 933 | /// Encode `null` to JSON 934 | /// @access private 935 | /// @param {Null} $null - `null` 936 | /// @return {String} 937 | @function _json-encode--null($null) { 938 | @return "null"; 939 | } 940 | 941 | /// JSON.stringify a value and pass it as a font-family of head element 942 | /// @access public 943 | /// @param {*} $value - value to be stringified 944 | /// @param {String} $flag (all) - output driver 945 | /// @require {function} json-encode 946 | @mixin json-encode($value, $flag: "all") { 947 | $flag: if(index("all" "regular" "media" "comment", $flag), $flag, "all"); 948 | $json: json-encode($value); 949 | 950 | // Persistent comment 951 | @if $flag == "comment" or $flag == "all" { 952 | /*! json-encode: #{$json} */ 953 | } 954 | // Regular property value pair 955 | @if $flag == "regular" or $flag == "all" { 956 | // All browsers except IE8- 957 | body::before { 958 | // This element must be in the render tree to get it via getComputedStyle(document.body, ':before'); 959 | content: json-encode($value); 960 | display: block; 961 | height: 0; 962 | overflow: hidden; 963 | width: 0; 964 | } 965 | 966 | // All browsers except Opera (Presto based) 967 | head { 968 | font-family: json-encode($value); 969 | } 970 | } 971 | 972 | // Falsy media query 973 | @if $flag == "media" or $flag == "all" { 974 | @media -json-encode { 975 | json { 976 | json: $json; 977 | } 978 | } 979 | } 980 | } 981 | 982 | /// Delay the encoding of ta literal to JSON to a type-specific method 983 | /// @access public 984 | /// @param {*} $value - value to be stringified 985 | /// @throw Unknown type for #{$value} (#{$type}). 986 | /// @return {String} - JSON encoded string 987 | /// @require {function} _json-encode--string 988 | /// @require {function} _json-encode--number 989 | /// @require {function} _json-encode--list 990 | /// @require {function} _json-encode--map 991 | /// @require {function} _json-encode--null 992 | /// @require {function} _json-encode--color 993 | /// @require {function} _json-encode--bool 994 | @function json-encode($value) { 995 | $type: type-of($value); 996 | 997 | @if function_exists('_json-encode--#{$type}') { 998 | @return call('_json-encode--#{$type}', $value); 999 | } 1000 | 1001 | @error "Unknown type for #{$value} (#{$type})."; 1002 | } 1003 | -------------------------------------------------------------------------------- /lib/JsonImporter.rb: -------------------------------------------------------------------------------- 1 | require 'sass' 2 | 3 | 4 | # Monkey Path Sass 5 | # Adapted from: https://github.com/chriseppstein/sass-css-importer 6 | class Sass::Engine 7 | alias initialize_without_json_importer initialize 8 | 9 | def initialize(template, options={}) 10 | initialize_without_json_importer(template, options) 11 | 12 | json_importer = self.options[:load_paths].find {|lp| lp.is_a?(Sass::Importers::JsonImporter) } 13 | 14 | unless json_importer 15 | root = File.dirname(options[:filename] || ".") 16 | self.options[:load_paths] << Sass::Importers::JsonImporter.new(root) 17 | end 18 | end 19 | end 20 | 21 | module Sass 22 | module Importers 23 | # The default importer, used for any strings found in the load path. 24 | # Simply loads Sass files from the filesystem using the default logic. 25 | class JsonImporter < Base 26 | 27 | attr_accessor :root 28 | 29 | # Creates a new filesystem importer that imports files relative to a given path. 30 | # 31 | # @param root [String] The root path. 32 | # This importer will import files relative to this path. 33 | def initialize(root) 34 | @root = File.expand_path(root) 35 | @same_name_warnings = Set.new 36 | end 37 | 38 | # Enable watching of json files in Sass 3.3+ 39 | def watched_directories 40 | [root] 41 | end 42 | 43 | # Enable watching of json files in Sass 3.3+ 44 | def watched_file?(file) 45 | file.start_with?(root+File::SEPARATOR) && File.extname(file) == ".json" 46 | end 47 | # @see Base#find_relative 48 | def find_relative(name, base, options) 49 | _find(File.dirname(base), name, options) 50 | end 51 | 52 | # @see Base#find 53 | def find(name, options) 54 | _find(@root, name, options) 55 | end 56 | 57 | # @see Base#mtime 58 | def mtime(name, options) 59 | name = strip_varname(name); 60 | file, _ = Sass::Util.destructure(find_real_file(@root, name, options)) 61 | File.mtime(file) if file 62 | rescue Errno::ENOENT 63 | nil 64 | end 65 | 66 | # @see Base#key 67 | def key(name, options) 68 | name = strip_varname(name); 69 | [self.class.name + ":" + File.dirname(File.expand_path(name)), 70 | File.basename(name)] 71 | end 72 | 73 | # @see Base#to_s 74 | def to_s 75 | @root 76 | end 77 | 78 | def hash 79 | @root.hash 80 | end 81 | 82 | def eql?(other) 83 | root.eql?(other.root) 84 | end 85 | 86 | protected 87 | 88 | # If a full uri is passed, this removes the root from it 89 | # otherwise returns the name unchanged 90 | def remove_root(name) 91 | if name.index(@root + "/") == 0 92 | name[(@root.length + 1)..-1] 93 | else 94 | name 95 | end 96 | end 97 | 98 | # A hash from file extensions to the syntaxes for those extensions. 99 | # The syntaxes must be `:sass` or `:scss`. 100 | # 101 | # This can be overridden by subclasses that want normal filesystem importing 102 | # with unusual extensions. 103 | # 104 | # @return [{String => Symbol}] 105 | def extensions 106 | {'json' => :scss} 107 | end 108 | 109 | # Given an `@import`ed path, returns an array of possible 110 | # on-disk filenames and their corresponding syntaxes for that path. 111 | # 112 | # @param name [String] The filename. 113 | # @return [Array(String, Symbol)] An array of pairs. 114 | # The first element of each pair is a filename to look for; 115 | # the second element is the syntax that file would be in (`:sass` or `:scss`). 116 | def possible_files(name) 117 | name = escape_glob_characters(name) 118 | dirname, basename, extname = split(name) 119 | sorted_exts = extensions.sort 120 | syntax = extensions[extname] 121 | 122 | if syntax 123 | ret = [["#{dirname}/{_,}#{basename}.#{extensions.invert[syntax]}", syntax]] 124 | else 125 | ret = sorted_exts.map {|ext, syn| ["#{dirname}/{_,}#{basename}.#{ext}", syn]} 126 | end 127 | 128 | # JRuby chokes when trying to import files from JARs when the path starts with './'. 129 | ret.map {|f, s| [f.sub(%r{^\./}, ''), s]} 130 | end 131 | 132 | def escape_glob_characters(name) 133 | name.gsub(/[\*\[\]\{\}\?]/) do |char| 134 | "\\#{char}" 135 | end 136 | end 137 | 138 | REDUNDANT_DIRECTORY = %r{#{Regexp.escape(File::SEPARATOR)}\.#{Regexp.escape(File::SEPARATOR)}} 139 | # Given a base directory and an `@import`ed name, 140 | # finds an existant file that matches the name. 141 | # 142 | # @param dir [String] The directory relative to which to search. 143 | # @param name [String] The filename to search for. 144 | # @return [(String, Symbol)] A filename-syntax pair. 145 | def find_real_file(dir, name, options) 146 | # on windows 'dir' can be in native File::ALT_SEPARATOR form 147 | dir = dir.gsub(File::ALT_SEPARATOR, File::SEPARATOR) unless File::ALT_SEPARATOR.nil? 148 | 149 | found = possible_files(remove_root(name)).map do |f, s| 150 | path = (dir == "." || Pathname.new(f).absolute?) ? f : "#{escape_glob_characters(dir)}/#{f}" 151 | Dir[path].map do |full_path| 152 | full_path.gsub!(REDUNDANT_DIRECTORY, File::SEPARATOR) 153 | [Pathname.new(full_path).cleanpath.to_s, s] 154 | end 155 | end 156 | found = Sass::Util.flatten(found, 1) 157 | return if found.empty? 158 | 159 | if found.size > 1 && !@same_name_warnings.include?(found.first.first) 160 | found.each {|(f, _)| @same_name_warnings << f} 161 | relative_to = Pathname.new(dir) 162 | if options[:_line] 163 | # If _line exists, we're here due to an actual import in an 164 | # import_node and we want to print a warning for a user writing an 165 | # ambiguous import. 166 | candidates = found.map {|(f, _)| " " + Pathname.new(f).relative_path_from(relative_to).to_s}.join("\n") 167 | Sass::Util.sass_warn < extension_path) 6 | 7 | # Version is a number. If a version contains alphas, it will be created as a prerelease version 8 | # Date is in the form of YYYY-MM-DD 9 | module SassyJSON 10 | VERSION = "1.1.8" 11 | DATE = "2014-06-01" 12 | end 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "SassyJSON", 3 | "name": "sassyjson", 4 | "version": "1.1.8", 5 | "description": "A Sass API for JSON.", 6 | "scripts": { 7 | "test": "grunt test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/KittyGiraudel/SassyJSON" 12 | }, 13 | "keywords": [ 14 | "sass", 15 | "scss", 16 | "json" 17 | ], 18 | "author": "Kitty Giraudel, Fabrice Weinberg", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/KittyGiraudel/SassyJSON/issues" 22 | }, 23 | "homepage": "https://github.com/KittyGiraudel/SassyJSON", 24 | "devDependencies": { 25 | "grunt-contrib-sass": "~0.8.0", 26 | "grunt": "~0.4.2", 27 | "bootcamp": "~1.1.4", 28 | "grunt-contrib-watch": "~0.5.3", 29 | "grunt-contrib-concat": "~0.3.0", 30 | "grunt-scsslint": "0.1.0" 31 | }, 32 | "dependencies": { 33 | "scsslint": "0.0.3" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sache.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SassyJSON", 3 | "description": "SassyJSON is a Sass-powered API for JSON. It provides you the classic json-encode and json-decode directly from your Sass files. We'll leave you the only judges of the point of this.", 4 | "tags": ["json", "sass"] 5 | } 6 | -------------------------------------------------------------------------------- /stylesheets/SassyJSON.scss: -------------------------------------------------------------------------------- 1 | // Encoder 2 | @import "encode/encode"; 3 | 4 | // Decoder 5 | @import "decode/decode"; 6 | -------------------------------------------------------------------------------- /stylesheets/decode/api/_json.scss: -------------------------------------------------------------------------------- 1 | /// Parse a JSON string 2 | /// @access public 3 | /// @param {String} $json - JSON string to parse 4 | /// @throw Input string may not be null. 5 | /// @return {*} 6 | /// @require {function} _json-decode--value 7 | @function json-decode($json) { 8 | $length: str-length($json); 9 | $pointer: 1; 10 | $value: null; 11 | 12 | @if $json == null { 13 | @return _throw("Input string may not be null.", $pointer); 14 | } 15 | 16 | @while $value != false // Stop if error 17 | and $pointer <= $length { 18 | $read: _json-decode--value($json, $pointer); 19 | $pointer: nth($read, 1); 20 | $value: nth($read, 2); 21 | } 22 | 23 | @return $value; 24 | } 25 | -------------------------------------------------------------------------------- /stylesheets/decode/decode.scss: -------------------------------------------------------------------------------- 1 | // Helpers 2 | @import "helpers/all/throw"; 3 | @import "helpers/all/value"; 4 | @import "helpers/map/consume"; 5 | @import "helpers/number/pow"; 6 | @import "helpers/number/find-digits"; 7 | @import "helpers/number/find-integer"; 8 | @import "helpers/number/find-exponent"; 9 | @import "helpers/color/color"; 10 | @import "helpers/color/get-color-value"; 11 | @import "helpers/color/rgb"; 12 | @import "helpers/color/hsl"; 13 | @import "helpers/color/hex"; 14 | @import "helpers/color/hex-to-dec"; 15 | @import "helpers/string/length"; 16 | @import "helpers/string/strip-token"; 17 | @import "helpers/string/find-ending-quote"; 18 | 19 | // Type specific decoding functions 20 | @import "types/bool"; 21 | @import "types/null"; 22 | @import "types/list"; 23 | @import "types/map"; 24 | @import "types/number"; 25 | @import "types/string"; 26 | 27 | // Public API 28 | @import "api/json"; 29 | -------------------------------------------------------------------------------- /stylesheets/decode/helpers/all/_throw.scss: -------------------------------------------------------------------------------- 1 | /// Logs an error at `$pointer` with `$string` message 2 | /// @access private 3 | /// @param {String} $string - error message 4 | /// @param {Number} $pointer - pointer position 5 | @function _throw($string, $pointer) { 6 | @error "ERROR::#{$pointer}::#{$string}"; 7 | } 8 | -------------------------------------------------------------------------------- /stylesheets/decode/helpers/all/_value.scss: -------------------------------------------------------------------------------- 1 | /// Delay parsing to type-specific function 2 | /// @access private 3 | /// according to found character 4 | /// @param {String} $source - JSON complete source 5 | /// @param {Number} $pointer - current pointer 6 | /// @throw Unexpected token $token. 7 | /// @throw Empty JSON string. 8 | /// @return {List} - (new pointer, parsed value) 9 | /// @require {function} _throw 10 | /// @require {function} _json-decode--map 11 | /// @require {function} _json-decode--list 12 | /// @require {function} _json-decode--true 13 | /// @require {function} _json-decode--false 14 | /// @require {function} _json-decode--string 15 | /// @require {function} _json-decode--null 16 | /// @require {function} _json-decode--number 17 | @function _json-decode--value($source, $pointer) { 18 | $length: str-length($source); 19 | 20 | @while $pointer <= $length { 21 | $token: str-slice($source, $pointer, $pointer); 22 | $pointer: $pointer + 1; 23 | 24 | @if $token == '{' { 25 | @return _json-decode--map($source, $pointer); 26 | } @else if $token == '[' { 27 | @return _json-decode--list($source, $pointer); 28 | } @else if $token == 't' { 29 | @return _json-decode--true($source, $pointer); 30 | } @else if $token == 'f' { 31 | @return _json-decode--false($source, $pointer); 32 | } @else if $token == '"' { 33 | @return _json-decode--string($source, $pointer); 34 | } @else if $token == 'n' { 35 | @return _json-decode--null($source, $pointer); 36 | } @else if index('1' '2' '3' '4' '5' '6' '7' '8' '9' '0' '-' '.', $token) { 37 | @return _json-decode--number($source, $pointer); 38 | } @else if $token == ' ' or $token == " " { 39 | // @continue; 40 | } @else { 41 | @return _throw("Unexpected token `#{$token}`.", $pointer); 42 | } 43 | } 44 | 45 | @return _throw("Empty JSON string.", $pointer); 46 | } 47 | -------------------------------------------------------------------------------- /stylesheets/decode/helpers/color/_color.scss: -------------------------------------------------------------------------------- 1 | /// Parses a JSON encoded string to see if it's a CSS color 2 | /// @access private 3 | /// @param {String} $string - JSON string 4 | /// @return {Color | String} - string or number, depending on the match 5 | /// @require {function} _from-hex 6 | /// @require {function} _from-rgb 7 | /// @require {function} _from-hsl 8 | @function _color($string) { 9 | @if type-of($string) == "color" { 10 | @return $string; 11 | } 12 | 13 | $string-lower: to-lower-case($string); 14 | $colors: transparent black silver gray white maroon red purple fuchsia green lime olive yellow navy blue teal aqua aliceblue antiquewhite aqua aquamarine azure beige bisque black blanchedalmond blue blueviolet brown burlywood cadetblue chartreuse chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan darkgoldenrod darkgray darkgreen darkgrey darkkhaki darkmagenta darkolivegreen darkorange darkorchid darkred darksalmon darkseagreen darkslateblue darkslategray darkslategrey darkturquoise darkviolet deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite forestgreen fuchsia gainsboro ghostwhite gold goldenrod gray green greenyellow grey honeydew hotpink indianred indigo ivory khaki lavender lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan lightgoldenrodyellow lightgray lightgreen lightgrey lightpink lightsalmon lightseagreen lightskyblue lightslategray lightslategrey lightsteelblue lightyellow lime limegreen linen magenta maroon mediumaquamarine mediumblue mediumorchid mediumpurple mediumseagreen mediumslateblue mediumspringgreen mediumturquoise mediumvioletred midnightblue mintcream mistyrose moccasin navajowhite navy oldlace olive olivedrab orange orangered orchid palegoldenrod palegreen paleturquoise palevioletred papayawhip peachpuff peru pink plum powderblue purple red rosybrown royalblue saddlebrown salmon sandybrown seagreen seashell sienna silver skyblue slateblue slategray slategrey snow springgreen steelblue tan teal thistle tomato turquoise violet wheat white whitesmoke yellow yellowgreen; 15 | $keywords: (); 16 | 17 | // Filling $keywords with stringified color keywords 18 | @each $color in $colors { 19 | $keywords: append($keywords, $color + ""); 20 | } 21 | 22 | // Deal with inherit keyword 23 | @if $string-lower == "inherit" { 24 | @return unquote($string); 25 | } 26 | 27 | @if index($keywords, $string-lower) { 28 | // Deal with color keywords 29 | @return nth($colors, index($keywords, $string-lower)); 30 | } @else if str-slice($string-lower, 1, 1) == '#' { 31 | // Deal with hexadecimal triplets 32 | @return _from-hex($string); 33 | } @else if str-slice($string-lower, 1, 3) == 'rgb' { 34 | // Deal with rgb(a) colors 35 | @return _from-rgb($string); 36 | } @else if str-slice($string-lower, 1, 3) == 'hsl' { 37 | // Deal with hsl(a) colors 38 | @return _from-hsl($string); 39 | } @else { 40 | // Return string 41 | @return $string; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /stylesheets/decode/helpers/color/_get-color-value.scss: -------------------------------------------------------------------------------- 1 | /// Cast a stringified number / stringified percentage into number type 2 | /// @access private 3 | /// @param {String} $string - JSON string 4 | /// @return {Number} - unitless number or percentage 5 | /// @require {function} _json-decode--number 6 | @function _get-color-value($string) { 7 | $first: str-slice($string, 1, 1); 8 | 9 | // Pad <1 values with a leading 0 10 | @if $first == '.' { 11 | $string: '0' + $string; 12 | } 13 | 14 | $last: str-slice($string, -1, -1); 15 | 16 | @return if( 17 | $last == '%', 18 | nth(_json-decode--number(str-slice($string, 1, -2), 2), 2) * 1%, 19 | nth(_json-decode--number($string, 2), 2) 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /stylesheets/decode/helpers/color/_hex-to-dec.scss: -------------------------------------------------------------------------------- 1 | /// Convert an hexadecimal number to a decimal number 2 | /// @access private 3 | /// @param {String} $string - hexadecimal value 4 | /// @return {Number} - decimal number 5 | @function _hex-to-dec($string) { 6 | $hex: "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "a" "b" "c" "d" "e" "f"; 7 | $string: to-lower-case($string); 8 | $length: str-length($string); 9 | 10 | $dec: 0; 11 | @for $i from 1 through $length { 12 | $factor: 1 + (15 * ($length - $i)); 13 | $index: index($hex, str-slice($string, $i, $i)); 14 | $dec: $dec + $factor * ($index - 1); 15 | } 16 | 17 | @return $dec; 18 | } 19 | -------------------------------------------------------------------------------- /stylesheets/decode/helpers/color/_hex.scss: -------------------------------------------------------------------------------- 1 | /// Cast a JSON encoded string into a hexadecimal color 2 | /// @access private 3 | /// @param {String} $string - JSON string 4 | /// @return {Color | String} - string or hex color depending on the match 5 | /// @require {function} _hex-to-dec 6 | @function _from-hex($string) { 7 | $string-lower: to-lower-case($string); 8 | $r: ""; $g: ""; $b: ""; 9 | $hex: "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "a" "b" "c" "d" "e" "f"; 10 | $length: str-length($string); 11 | $max: if($length == 4, 1, 2); 12 | 13 | // Check for length accuracy 14 | @if $length != 4 and $length != 7 { 15 | @return $string; 16 | } 17 | 18 | // Loop from the second character (omitting #) 19 | @for $i from 2 through $length { 20 | $c: str-slice($string-lower, $i, $i); 21 | 22 | // If wrong character, return 23 | @if index($hex, $c) == null { 24 | @return $string; 25 | } 26 | 27 | @if str-length($r) < $max { 28 | $r: $r + $c; 29 | } @else if str-length($g) < $max { 30 | $g: $g + $c; 31 | } @else if str-length($b) < $max { 32 | $b: $b + $c; 33 | } 34 | } 35 | 36 | @if $length == 4 { 37 | $r: $r + $r; 38 | $g: $g + $g; 39 | $b: $b + $b; 40 | } 41 | 42 | @return rgb(_hex-to-dec($r), _hex-to-dec($g), _hex-to-dec($b)); 43 | } 44 | -------------------------------------------------------------------------------- /stylesheets/decode/helpers/color/_hsl.scss: -------------------------------------------------------------------------------- 1 | /// Cast a JSON encoded string into a hsl(a) color 2 | /// @access private 3 | /// @param {String} $string - JSON string 4 | /// @return {Color | String} - string or hsl(a) color, depending on the match 5 | /// @require {function} _get-color-value 6 | @function _from-hsl($string) { 7 | $frags: (); 8 | $string-lower: to-lower-case($string); 9 | $is-alpha: str-slice($string-lower, 4, 4) == 'a'; 10 | $length: str-length($string); 11 | $start: str-index($string, "("); 12 | 13 | @for $i from $start through $length { 14 | $token: str-slice($string-lower, $i, $i); 15 | @if $token == ' ' or $token == " " { 16 | // @continue; 17 | } @else if $token == '(' or $token == ',' { 18 | $frags: append($frags, ""); 19 | } @else if $token == ')' { 20 | @if length($frags) != if($is-alpha, 4, 3) { @return $string; } // Parsing error 21 | $hue: _get-color-value(nth($frags, 1)); 22 | $saturation: _get-color-value(nth($frags, 2)); 23 | $lightness: _get-color-value(nth($frags, 3)); 24 | 25 | @if not $hue or not $saturation or not $lightness { 26 | @return $string; 27 | } 28 | 29 | @if $is-alpha { 30 | @if length($frags) != 4 { @return $string; } // No alpha channel found 31 | $alpha: _get-color-value(nth($frags, 4)); 32 | @if not $alpha { @return $string; } // Error parsing alpha channel 33 | @return hsla($hue, $saturation, $lightness, $alpha); 34 | } 35 | 36 | @return hsl($hue, $saturation, $lightness); 37 | } @else { 38 | $frags: set-nth($frags, length($frags), nth($frags, length($frags)) + $token); 39 | } 40 | } 41 | 42 | @return $string; 43 | } 44 | -------------------------------------------------------------------------------- /stylesheets/decode/helpers/color/_rgb.scss: -------------------------------------------------------------------------------- 1 | /// Cast a JSON encoded string into a rgb(a) color 2 | /// @access private 3 | /// @param {String} $string - JSON string 4 | /// @return {Color | String} - string or rgb(a) color depending on the match 5 | /// @require {function} _get-color-value 6 | @function _from-rgb($string) { 7 | $string-lower: to-lower-case($string); 8 | $frags: (); 9 | $is-alpha: str-slice($string-lower, 4, 4) == 'a'; 10 | $start: str-index($string, "("); 11 | $length: str-length($string); 12 | 13 | @for $i from $start through $length { 14 | $token: str-slice($string-lower, $i, $i); 15 | @if $token == ' ' or $token == " " { 16 | // @continue; 17 | } @else if $token == '(' or $token == ',' { 18 | $frags: append($frags, ""); 19 | } @else if $token == ')' { 20 | @if length($frags) != if($is-alpha, 4, 3) { @return $string; } // Parsing error 21 | $red: _get-color-value(nth($frags, 1)); 22 | $green: _get-color-value(nth($frags, 2)); 23 | $blue: _get-color-value(nth($frags, 3)); 24 | 25 | @if not $red or not $green or not $blue { 26 | @return $string; 27 | } 28 | 29 | @if $is-alpha { 30 | @if length($frags) != 4 { @return $string; } // No alpha channel found 31 | $alpha: _get-color-value(nth($frags, 4)); 32 | @if not $alpha { @return $string; } // Error parsing alpha channel 33 | @return rgba($red, $green, $blue, $alpha); 34 | } 35 | 36 | @return rgb($red, $green, $blue); 37 | } @else { 38 | $frags: set-nth($frags, length($frags), nth($frags, length($frags)) + $token); 39 | } 40 | } 41 | 42 | @return $string; 43 | } 44 | -------------------------------------------------------------------------------- /stylesheets/decode/helpers/map/_consume.scss: -------------------------------------------------------------------------------- 1 | /// Move pointer to position of token 2 | /// @access private 3 | /// @param {String} $source - JSON complete source 4 | /// @param {Number} $pointer - current pointer 5 | /// @param {String} $token - token to reach 6 | /// @throw Expected $token; found $char. 7 | /// @throw Expected $token but reached end of stream. 8 | /// @return {Number} - new pointer 9 | /// @require {function} _throw 10 | @function _consume($source, $pointer, $token) { 11 | $length: str-length($source); 12 | 13 | @while $pointer <= $length { 14 | $char: str-slice($source, $pointer, $pointer); 15 | $pointer: $pointer + 1; 16 | 17 | @if $char == $token { 18 | @return $pointer; 19 | } @else if $char == " " or $char == " " { 20 | // @continue; 21 | } @else { 22 | @return _throw("Expected `#{$token}`; found `#{$char}`.", $pointer); 23 | } 24 | } 25 | 26 | @return _throw("Expected `#{$token}` but reached end of stream.", $pointer); 27 | } 28 | -------------------------------------------------------------------------------- /stylesheets/decode/helpers/number/_find-digits.scss: -------------------------------------------------------------------------------- 1 | /// Parses a JSON encoded number to find the digits 2 | /// @access private 3 | /// @param {String} $source - JSON complete source 4 | /// @param {Number} $pointer - current pointer 5 | /// @throw Unexpected token $token. 6 | /// @return {List} (new pointer, parsed number) 7 | /// @require {function} _throw 8 | @function _find-digits($source, $pointer) { 9 | $source: to-lower-case($source); 10 | $length: str-length($source); 11 | $numbers: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9'; 12 | $result: null; 13 | $runs: 1; 14 | 15 | @while $pointer <= $length { 16 | $token: str-slice($source, $pointer, $pointer); 17 | $index: index($numbers, $token); 18 | 19 | @if $token == '.' { 20 | // @continue; 21 | } @else if $index and $index > 0 { 22 | $runs: $runs * 10; 23 | $result: if($result == null, ($index - 1), $result * 10 + ($index - 1)); 24 | } @else { 25 | @if index('e' '.' ' ' ',' ']' '}', $token) { 26 | @return $pointer, if($result != null, $result / $runs, $result); 27 | } 28 | 29 | @return _throw("Unexpected token `#{$token}`.", $pointer); 30 | } 31 | 32 | $pointer: $pointer + 1; 33 | } 34 | 35 | @return $pointer, if($result != null, $result / $runs, $result); 36 | } 37 | -------------------------------------------------------------------------------- /stylesheets/decode/helpers/number/_find-exponent.scss: -------------------------------------------------------------------------------- 1 | /// Parses a JSON encoded number to find the exponent part 2 | /// @access private 3 | /// @param {String} $source - JSON complete source 4 | /// @param {Number} $pointer - current pointer 5 | /// @throw Unexpected token $token. 6 | /// @return {List} - (new pointer, parsed number) 7 | /// @require {function} _throw 8 | @function _find-exponent($source, $pointer) { 9 | $source: to-lower-case($source); 10 | $length: str-length($source); 11 | $numbers: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9'; 12 | $result: null; 13 | $minus: null; 14 | 15 | @while $pointer <= $length { 16 | $token: str-slice($source, $pointer, $pointer); 17 | $index: index($numbers, $token); 18 | 19 | @if $token == 'e' { 20 | // @continue; 21 | } @else if $token == '-' { 22 | @if $minus != null { 23 | @return _throw("Unexpected token `-`.", $pointer); 24 | } 25 | $minus: true; 26 | } @else if $token == '+' { 27 | @if $minus != null { 28 | @return _throw("Unexpected token `+`.", $pointer); 29 | } 30 | $minus: false; 31 | } @else if $index and $index > 0 { 32 | $result: if($result == null, ($index - 1), $result * 10 + ($index - 1)); 33 | } @else { 34 | @if index(' ' ',' ']' '}', $token) == null { 35 | @return _throw("Unexpected token `" + $token + "`.", $pointer); 36 | } 37 | 38 | @return $pointer, if($minus and $result != null, $result * -1, $result); 39 | } 40 | 41 | $pointer: $pointer + 1; 42 | } 43 | 44 | @return $pointer, if($minus and $result != null, $result * -1, $result); 45 | } 46 | -------------------------------------------------------------------------------- /stylesheets/decode/helpers/number/_find-integer.scss: -------------------------------------------------------------------------------- 1 | /// Parses a JSON encoded number to find the integer part 2 | /// @access private 3 | /// @param {String} $source - JSON complete source 4 | /// @param {Number} $pointer - current pointer 5 | /// @throw Unexpected token $token. 6 | /// @return {List} (new pointer, parsed number) 7 | /// @require {function} _throw 8 | @function _find-integer($source, $pointer) { 9 | $source: to-lower-case($source); 10 | $length: str-length($source); 11 | $numbers: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9'; 12 | $result: 0; 13 | 14 | @while $pointer <= $length { 15 | $token: str-slice($source, $pointer, $pointer); 16 | $index: index($numbers, $token); 17 | 18 | @if $token == '-' { 19 | // do nothing 20 | } @else if $index { 21 | $result: $result * 10 + ($index - 1); 22 | } @else { 23 | @if index('e' '.' ' ' ',' ']' '}', $token) { 24 | @return $pointer, $result; 25 | } 26 | 27 | @return _throw("Unexpected token `" + $token + "`.", $pointer); 28 | } 29 | 30 | $pointer: $pointer + 1; 31 | } 32 | 33 | @return $pointer, $result; 34 | } 35 | -------------------------------------------------------------------------------- /stylesheets/decode/helpers/number/_pow.scss: -------------------------------------------------------------------------------- 1 | /// Power function 2 | /// @access private 3 | /// @param {Number} $x - number 4 | /// @param {Number} $n - power 5 | /// @return {Number} 6 | @function _pow($x, $n) { 7 | @if $n == 0 { @return 1; } 8 | $ret: 1; 9 | @if $n >= 0 { 10 | @for $i from 1 through $n { 11 | $ret: $ret * $x; 12 | } 13 | } @else { 14 | @for $i from $n to 0 { 15 | $ret: $ret / $x; 16 | } 17 | } 18 | 19 | @return $ret; 20 | } 21 | -------------------------------------------------------------------------------- /stylesheets/decode/helpers/string/_find-ending-quote.scss: -------------------------------------------------------------------------------- 1 | /// Will find the first non escaped quote in a JSON String 2 | /// @access private 3 | /// @param {String} $string - to search in 4 | /// @return {Number} - position of the first non escaped quote 5 | @function _find-ending-quote($string) { 6 | $backslash: str-slice('\\', 1, 1); // Dirty hack to have a single backslash 7 | $escaped-chars: '"\/bfnrtu'; // Characters that can be escaped in JSON 8 | $hexadecimal-chars: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9' '0' 'a' 'b' 'c' 'd' 'e' 'f'; 9 | 10 | $pos: 1; 11 | $length: str-length($string); 12 | $backslash-found: false; 13 | 14 | @while $pos <= $length { 15 | $char: to-lower-case(str-slice($string, $pos, $pos)); 16 | 17 | // Previous char was a backslash 18 | @if $backslash-found { 19 | 20 | // Special case, the 'u' character 21 | @if $char == 'u' { 22 | // Next 4 characters must be hexadecimal 23 | @if not index($hexadecimal-chars, str-slice($string, $pos + 1, $pos + 1)) or 24 | not index($hexadecimal-chars, str-slice($string, $pos + 2, $pos + 2)) or 25 | not index($hexadecimal-chars, str-slice($string, $pos + 3, $pos + 3)) or 26 | not index($hexadecimal-chars, str-slice($string, $pos + 4, $pos + 4)) { 27 | @return 0; 28 | } 29 | 30 | $pos: $pos + 4; 31 | } @else if not str-index($escaped-chars, $char) { 32 | // Invalid character escaped 33 | @return 0; 34 | } 35 | 36 | $backslash-found: false; 37 | } @else if $char == $backslash { 38 | $backslash-found: true; 39 | } @else if $char == '"' { 40 | // Unescaped quote found 41 | @return $pos; 42 | } 43 | 44 | $pos: $pos + 1; 45 | } 46 | 47 | // No end of string found 48 | @return 0; 49 | } 50 | -------------------------------------------------------------------------------- /stylesheets/decode/helpers/string/_length.scss: -------------------------------------------------------------------------------- 1 | /// Parses a JSON encoded string to see if it's a CSS length 2 | /// @access private 3 | /// @param {String} $string - JSON string 4 | /// @return {Number | String} - string or number, depending on the match 5 | /// @require {function} _json-decode--number 6 | @function _length($string) { 7 | @if type-of($string) == "number" { 8 | @return $string; 9 | } 10 | 11 | $strings: 'px' 'cm' 'mm' '%' 'ch' 'pica' 'in' 'em' 'rem' 'pt' 'pc' 'ex' 'vw' 'vh' 'vmin' 'vmax'; 12 | $units: 1px 1cm 1mm 1% 1ch 1pica 1in 1em 1rem 1pt 1pc 1ex 1vw 1vh 1vmin 1vmax; 13 | $number: ""; 14 | $unit: ""; 15 | 16 | @for $i from 1 through str-length($string) { 17 | $c: str-slice($string, $i, $i); 18 | @if $c == ' ' or $c == " " { 19 | @if $number != "" { 20 | @return $string; 21 | } 22 | } @else if index('0' '1' '2' '3' '4' '5' '6' '7' '8' '9' '-' '.', $c) { 23 | $number: $number + $c; 24 | } @else { 25 | @if $number == "" { 26 | @return $string; 27 | } 28 | $unit: $unit + $c; 29 | } 30 | } 31 | 32 | $number: nth(_json-decode--number($number, 2), 2); 33 | $index: index($strings, to-lower-case($unit)); 34 | 35 | @if $index and $index > 0 { 36 | @return $number * nth($units, $index); 37 | } 38 | 39 | @return $string; 40 | } 41 | -------------------------------------------------------------------------------- /stylesheets/decode/helpers/string/_strip-token.scss: -------------------------------------------------------------------------------- 1 | /// Strip special carriage return characters 2 | /// @access private 3 | /// @param {String} $string - string to parse 4 | /// @param {String} $search - char to strip 5 | /// @param {String} $replace ('') - new substring 6 | /// @return {String} - new string 7 | @function _strip-token($string, $search, $replace: '') { 8 | $index: str-index($string, $search); 9 | 10 | @if $index { 11 | @return str-slice($string, 1, $index - 1) + $replace + _strip-token(str-slice($string, $index + str-length($search)), $search, $replace); 12 | } 13 | 14 | @return $string; 15 | } 16 | -------------------------------------------------------------------------------- /stylesheets/decode/types/_bool.scss: -------------------------------------------------------------------------------- 1 | /// Parses a JSON encoded `true` 2 | /// @param {String} $source - JSON complete source 3 | /// @param {Number} $pointer - current pointer 4 | /// @throw Unexpected token `t`. 5 | /// @return {List} - (new pointer, true) 6 | /// @require {function} _throw 7 | @function _json-decode--true($source, $pointer) { 8 | $length: str-length($source); 9 | 10 | @if $length - $pointer < 2 11 | or str-slice($source, $pointer, $pointer) != 'r' 12 | or str-slice($source, $pointer + 1, $pointer + 1) != 'u' 13 | or str-slice($source, $pointer + 2, $pointer + 2) != 'e' { 14 | @return _throw("Unexpected token: `t`.", $pointer); 15 | } 16 | 17 | @return ($pointer + 3, true); 18 | } 19 | 20 | /// Parses a JSON encoded `false` 21 | /// @param {String} $source - JSON complete source 22 | /// @param {Number} $pointer - current pointer 23 | /// @throw Unexpected token `f`. 24 | /// @return {List} - (new pointer, false) 25 | /// @require {function} _throw 26 | @function _json-decode--false($source, $pointer) { 27 | $length: str-length($source); 28 | 29 | @if $length - $pointer < 3 30 | or str-slice($source, $pointer, $pointer) != 'a' 31 | or str-slice($source, $pointer + 1, $pointer + 1) != 'l' 32 | or str-slice($source, $pointer + 2, $pointer + 2) != 's' 33 | or str-slice($source, $pointer + 3, $pointer + 3) != 'e' { 34 | @return _throw("Unexpected token: `f`.", $pointer); 35 | } 36 | 37 | @return ($pointer + 4, false); 38 | } 39 | -------------------------------------------------------------------------------- /stylesheets/decode/types/_list.scss: -------------------------------------------------------------------------------- 1 | /// Parses a JSON encoded array 2 | /// @param {String} $source - JSON complete source 3 | /// @param {Number} $pointer - current pointer 4 | /// @throw Unexpected comma in array literal. 5 | /// @throw Missing comma in array literal. 6 | /// @throw Unterminated array literal. 7 | /// @return {List} (new pointer, parsed list) 8 | /// @require {function} _throw 9 | /// @require {function} _json-decode--value 10 | @function _json-decode--list($source, $pointer) { 11 | $length: str-length($source); 12 | $list: (); 13 | $needs-comma: false; 14 | 15 | @if $pointer <= $length and str-slice($source, $pointer, $pointer) == "]" { 16 | @return ($pointer + 1, $list); 17 | } 18 | 19 | @while $pointer <= $length { 20 | $token: str-slice($source, $pointer, $pointer); 21 | 22 | @if $token == "]" { 23 | @if not $needs-comma and length($list) != 0 { 24 | @return _throw("Unexpected comma in array literal.", $pointer); 25 | } 26 | 27 | // Do it the Sass way and destruct a single item array to an element. 28 | @return ($pointer + 1, if(length($list) == 1, nth($list, 1), $list)); 29 | } @else if $token == " " or $token == " " { 30 | $pointer: $pointer + 1; 31 | } @else if $token == "," { 32 | @if not $needs-comma { 33 | @return _throw("Unexpected comma in array literal.", $pointer); 34 | } 35 | 36 | $needs-comma: false; 37 | $pointer: $pointer + 1; 38 | } @else { 39 | @if $needs-comma { 40 | @return _throw("Missing comma in array literal.", $pointer); 41 | } 42 | 43 | $read: _json-decode--value($source, $pointer); 44 | $pointer: nth($read, 1); 45 | $list: append($list, nth($read, 2)); 46 | $needs-comma: true; 47 | } 48 | } 49 | 50 | @return _throw("Unterminated array literal.", $pointer); 51 | } 52 | -------------------------------------------------------------------------------- /stylesheets/decode/types/_map.scss: -------------------------------------------------------------------------------- 1 | /// Parses a JSON encoded object 2 | /// @param {String} $source - JSON complete source 3 | /// @param {Number} $pointer - current pointer 4 | /// @throw Unexpected comma in object literal. 5 | /// @throw Unexpected token $token in object literal. 6 | /// @throw Missing comma in object literal. 7 | /// @throw Unterminated object literal. 8 | /// @throw Consuming token `:` failed. 9 | /// @return {List} (new pointer, map) 10 | /// @require {function} _throw 11 | /// @require {function} _consume 12 | /// @require {function} _json-decode--value 13 | /// @require {function} _json-decode--string 14 | @function _json-decode--map($source, $pointer) { 15 | $length: str-length($source); 16 | $map: (); 17 | $needs-comma: false; 18 | 19 | // Deal with empty map 20 | @if $pointer <= $length and str-slice($source, $pointer, $pointer) == "}" { 21 | @return ($pointer + 1, $map); 22 | } 23 | 24 | @while $pointer <= $length { 25 | $token: str-slice($source, $pointer, $pointer); 26 | $pointer: $pointer + 1; 27 | 28 | @if $token == "}" { 29 | @if not $needs-comma and length($map) != 0 { 30 | @return _throw("Unexpected comma in object literal.", $pointer); 31 | } 32 | @return ($pointer, $map); 33 | } @else if $token == " " or $token == " " { 34 | // @continue; 35 | } @else if $token == "," { 36 | @if not $needs-comma { 37 | @return _throw("Unexpected comma in object literal.", $pointer); 38 | } 39 | $needs-comma: false; 40 | } @else if $token == '"' { 41 | @if $needs-comma { 42 | @return _throw("Missing comma in object literal.", $pointer); 43 | } 44 | 45 | // Read key 46 | $read-key: _json-decode--string($source, $pointer); 47 | $pointer: nth($read-key, 1); 48 | $key: nth($read-key, 2); 49 | 50 | // Remove colon 51 | $pointer: _consume($source, $pointer, ":"); 52 | @if length($pointer) > 1 { // If consume has failed 53 | @return _throw("Consuming token `:` failed.", 0); 54 | } 55 | 56 | // Read value 57 | $read-value: _json-decode--value($source, $pointer); 58 | $pointer: nth($read-value, 1); 59 | $value: nth($read-value, 2); 60 | 61 | // Add pair to map 62 | $map: map-merge($map, ($key: $value)); 63 | $needs-comma: true; 64 | } @else { 65 | @return _throw("Unexpected token `" + $token + "` in object literal.", $pointer); 66 | } 67 | } 68 | 69 | @return _throw("Unterminated object literal.", $pointer); 70 | } 71 | -------------------------------------------------------------------------------- /stylesheets/decode/types/_null.scss: -------------------------------------------------------------------------------- 1 | /// Parses a JSON encoded `null` 2 | /// @param {String} $source - JSON complete source 3 | /// @param {Number} $pointer - current pointer 4 | /// @throw "Unexpected token `n`." 5 | /// @return {List} (new pointer, null) 6 | /// @require {function} _throw 7 | @function _json-decode--null($source, $pointer) { 8 | $length: str-length($source); 9 | 10 | @if $length - $pointer < 2 11 | or str-slice($source, $pointer, $pointer) != 'u' 12 | or str-slice($source, $pointer + 1, $pointer + 1) != 'l' 13 | or str-slice($source, $pointer + 2, $pointer + 2) != 'l' { 14 | @return _throw("Unexpected token: `n`.", $pointer); 15 | } 16 | 17 | @return ($pointer + 3, null); 18 | } 19 | -------------------------------------------------------------------------------- /stylesheets/decode/types/_number.scss: -------------------------------------------------------------------------------- 1 | /// Parses a JSON encoded number 2 | /// @param {String} $source - JSON complete source 3 | /// @param {Number} $pointer - current pointer 4 | /// @throw "Unexpected token $token." 5 | /// @throw "Unexpected end of stream." 6 | /// @return {List} (new pointer, parsed number) 7 | /// @require {function} _throw 8 | /// @require {function} _find-integer 9 | /// @require {function} _find-digits 10 | /// @require {function} _find-exponent 11 | /// @require {function} _pow 12 | @function _json-decode--number($source, $pointer) { 13 | $pointer: $pointer - 1; // Move back pointer to begininng of number 14 | $allowed: '-' '0' '1' '2' '3' '4' '5' '6' '7' '8' '9'; // Allowed characted to start with 15 | $first: str-slice($source, $pointer, $pointer); // First character of the number 16 | $minus: $first == '-'; // Is it negative? 17 | 18 | // Early check for errors 19 | @if not index($allowed, $first) { 20 | @return _throw("Unexpected token `" + $first + "`.", $pointer); 21 | } 22 | 23 | // Find the integer part 24 | $find-integer: _find-integer($source, $pointer); 25 | $pointer: nth($find-integer, 1); 26 | $result: nth($find-integer, 2); 27 | @if not $result { // Error occured 28 | @return $find-integer; 29 | } 30 | 31 | // Find digits 32 | @if str-slice($source, $pointer, $pointer) == '.' { 33 | $find-digits: _find-digits($source, $pointer); 34 | $pointer: nth($find-digits, 1); 35 | $digits: nth($find-digits, 2); 36 | 37 | @if $digits == null { // Empty digits, throw error 38 | @return _throw("Unexpected end of stream.", $pointer); 39 | } @else if $digits == false { // Error occured, return it 40 | @return $find-digits; 41 | } 42 | 43 | $result: $result + $digits; 44 | } 45 | 46 | // Find exponent 47 | @if to-lower-case(str-slice($source, $pointer, $pointer)) == 'e' { 48 | $find-exponent: _find-exponent($source, $pointer); 49 | $pointer: nth($find-exponent, 1); 50 | $exponent: nth($find-exponent, 2); 51 | 52 | @if $exponent == null { // Empty exponent, throw error 53 | @return _throw("Unexpected end of stream.", $pointer); 54 | } @else if $exponent == false { // Error occured, return it 55 | @return $find-exponent; 56 | } 57 | 58 | $result: $result * _pow(10, $exponent); 59 | } 60 | 61 | @return ($pointer, if($minus, $result * -1, $result)); 62 | } 63 | -------------------------------------------------------------------------------- /stylesheets/decode/types/_string.scss: -------------------------------------------------------------------------------- 1 | /// Parses a JSON encoded string 2 | /// @param {String} $source - JSON complete source 3 | /// @param {Number} $pointer - current pointer 4 | /// @throw Unterminated string. 5 | /// @return {List} (new pointer, parsed string / color / length) 6 | /// @require {function} _throw 7 | /// @require {function} _find-ending-quote 8 | /// @require {function} _strip-token 9 | /// @require {function} _color 10 | /// @require {function} _length 11 | @function _json-decode--string($source, $pointer) { 12 | // Check for the end of the string 13 | $temp: str-slice($source, $pointer); 14 | $end: _find-ending-quote($temp); 15 | $string: ""; 16 | 17 | // If no end is found 18 | @if not $end or $end == 0 { 19 | @return _throw("Unterminated string.", $pointer); 20 | } @else if $end > 1 { 21 | // If string is not empty 22 | $string: str-slice($temp, 1, $end - 1); 23 | 24 | $cr: " 25 | "; 26 | $string: _strip-token($string, "\\\r", $cr); 27 | $string: _strip-token($string, "\\\n", $cr); 28 | $string: _strip-token($string, '\\\"', '"'); 29 | 30 | // Test whether the string could be a CSS length 31 | $string: _length($string); 32 | 33 | // Test whether the string could be a CSS color 34 | @if type-of($string) == "string" { 35 | $string: _color($string); 36 | } 37 | } 38 | 39 | @return ($pointer + $end, $string); 40 | } -------------------------------------------------------------------------------- /stylesheets/encode/api/_json.scss: -------------------------------------------------------------------------------- 1 | /// Delay the encoding of ta literal to JSON to a type-specific method 2 | /// @access public 3 | /// @param {*} $value - value to be stringified 4 | /// @throw Unknown type for #{$value} (#{$type}). 5 | /// @return {String} - JSON encoded string 6 | /// @require {function} _json-encode--string 7 | /// @require {function} _json-encode--number 8 | /// @require {function} _json-encode--list 9 | /// @require {function} _json-encode--map 10 | /// @require {function} _json-encode--null 11 | /// @require {function} _json-encode--color 12 | /// @require {function} _json-encode--bool 13 | @function json-encode($value) { 14 | $type: type-of($value); 15 | 16 | @if function_exists('_json-encode--#{$type}') { 17 | @return call('_json-encode--#{$type}', $value); 18 | } 19 | 20 | @error "Unknown type for #{$value} (#{$type})."; 21 | } 22 | -------------------------------------------------------------------------------- /stylesheets/encode/encode.scss: -------------------------------------------------------------------------------- 1 | // Helpers 2 | @import "helpers/quote"; 3 | 4 | // Type specific encoding functions 5 | @import "types/bool"; 6 | @import "types/color"; 7 | @import "types/list"; 8 | @import "types/map"; 9 | @import "types/number"; 10 | @import "types/string"; 11 | @import "types/null"; 12 | 13 | // Public API 14 | @import "api/json"; 15 | 16 | // Mixin to pass the string to the DOM 17 | @import "mixins/json"; 18 | -------------------------------------------------------------------------------- /stylesheets/encode/helpers/_quote.scss: -------------------------------------------------------------------------------- 1 | /// Proof quote a value 2 | /// @access private 3 | /// @param {*} $value - value to be quoted 4 | /// @return {String} - quoted value 5 | 6 | @function _proof-quote($value) { 7 | // $value: to-string($value); 8 | @return '"#{$value}"'; 9 | } 10 | -------------------------------------------------------------------------------- /stylesheets/encode/mixins/_json.scss: -------------------------------------------------------------------------------- 1 | /// JSON.stringify a value and pass it as a font-family of head element 2 | /// @access public 3 | /// @param {*} $value - value to be stringified 4 | /// @param {String} $flag (all) - output driver 5 | /// @require {function} json-encode 6 | @mixin json-encode($value, $flag: "all") { 7 | $flag: if(index("all" "regular" "media" "comment", $flag), $flag, "all"); 8 | $json: json-encode($value); 9 | 10 | // Persistent comment 11 | @if $flag == "comment" or $flag == "all" { 12 | /*! json-encode: #{$json} */ 13 | } 14 | // Regular property value pair 15 | @if $flag == "regular" or $flag == "all" { 16 | // All browsers except IE8- 17 | body::before { 18 | // This element must be in the render tree to get it via getComputedStyle(document.body, ':before'); 19 | content: json-encode($value); 20 | display: block; 21 | height: 0; 22 | overflow: hidden; 23 | width: 0; 24 | } 25 | 26 | // All browsers except Opera (Presto based) 27 | head { 28 | font-family: json-encode($value); 29 | } 30 | } 31 | 32 | // Falsy media query 33 | @if $flag == "media" or $flag == "all" { 34 | @media -json-encode { 35 | json { 36 | json: $json; 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /stylesheets/encode/types/_bool.scss: -------------------------------------------------------------------------------- 1 | /// Encode a bool to JSON 2 | /// @access private 3 | /// @param {Bool} $bool - bool to be encoded 4 | /// @return {Bool} - encoded bool 5 | @function _json-encode--bool($boolean) { 6 | @return $boolean; 7 | } 8 | -------------------------------------------------------------------------------- /stylesheets/encode/types/_color.scss: -------------------------------------------------------------------------------- 1 | /// Encode a color to JSON 2 | /// @access private 3 | /// @param {Color} $color - color to be encoded 4 | /// @return {String} - encoded color 5 | /// @require {function} _proof-quote 6 | @function _json-encode--color($color) { 7 | @return _proof-quote($color); 8 | } 9 | -------------------------------------------------------------------------------- /stylesheets/encode/types/_list.scss: -------------------------------------------------------------------------------- 1 | /// Encode a list to JSON 2 | /// @access private 3 | /// @param {List} $list - list to be encoded 4 | /// @return {String} - encoded list 5 | /// @require {function} json-encore 6 | @function _json-encode--list($list) { 7 | $str: ""; 8 | 9 | @each $item in $list { 10 | $str: $str + ', ' + json-encode($item); 11 | } 12 | 13 | @return '[' + str-slice($str, 3) + ']'; 14 | } 15 | -------------------------------------------------------------------------------- /stylesheets/encode/types/_map.scss: -------------------------------------------------------------------------------- 1 | /// Encode a map to JSON 2 | /// @access private 3 | /// @param {Map} $map - map to be encoded 4 | /// @return {String} - encoded map 5 | /// @require {function} _proof-quote 6 | /// @require {function} json-encode 7 | @function _json-encode--map($map) { 8 | $str: ""; 9 | 10 | @each $key, $value in $map { 11 | $str: $str + ', ' + _proof-quote($key) + ': ' + json-encode($value); 12 | } 13 | 14 | @return '{' + str-slice($str, 3) + '}'; 15 | } 16 | -------------------------------------------------------------------------------- /stylesheets/encode/types/_null.scss: -------------------------------------------------------------------------------- 1 | /// Encode `null` to JSON 2 | /// @access private 3 | /// @param {Null} $null - `null` 4 | /// @return {String} 5 | @function _json-encode--null($null) { 6 | @return "null"; 7 | } 8 | -------------------------------------------------------------------------------- /stylesheets/encode/types/_number.scss: -------------------------------------------------------------------------------- 1 | /// Encode a number to JSON 2 | /// @access private 3 | /// @param {Number} $number - number to be encoded 4 | /// @return {String} - encoded number 5 | /// @require {function} _proof-quote 6 | @function _json-encode--number($number) { 7 | @return if(unitless($number), $number, _proof-quote($number)); 8 | } 9 | -------------------------------------------------------------------------------- /stylesheets/encode/types/_string.scss: -------------------------------------------------------------------------------- 1 | /// Encode a string to JSON 2 | /// @access private 3 | /// @param {String} $string - string to be encoded 4 | /// @return {String} - encoded string 5 | /// @require {function} _proof-quote 6 | @function _json-encode--string($string) { 7 | @return _proof-quote($string); 8 | } 9 | -------------------------------------------------------------------------------- /test/decode/_index.scss: -------------------------------------------------------------------------------- 1 | //Helper 2 | @import "helpers/map/consume"; 3 | @import "helpers/all/value"; 4 | @import "helpers/number/pow"; 5 | @import "helpers/number/find-integer"; 6 | @import "helpers/number/find-exponent"; 7 | @import "helpers/number/find-digits"; 8 | @import "helpers/string/length"; 9 | @import "helpers/string/strip-token"; 10 | @import "helpers/string/find-ending-quote"; 11 | @import "helpers/color/get-color-value"; 12 | @import "helpers/color/color"; 13 | @import "helpers/color/hex"; 14 | @import "helpers/color/rgb"; 15 | @import "helpers/color/hsl"; 16 | @import "helpers/color/hex-to-dec"; 17 | 18 | //Types 19 | @import "types/string"; 20 | @import "types/number"; 21 | @import "types/bool"; 22 | @import "types/list"; 23 | @import "types/map"; 24 | @import "types/null"; 25 | 26 | //API 27 | @import "api/json"; 28 | -------------------------------------------------------------------------------- /test/decode/api/_json.scss: -------------------------------------------------------------------------------- 1 | // Relative to test/test.scss 2 | @import "decode/fixtures/test.json?testvar"; 3 | 4 | @include describe("The json-decode should handle any JSON input") { 5 | 6 | @include it("should work for valid JSON") { 7 | $string: '{"a": [1, 2, {"b": 1}], "b": ["#444444", false, {"a": 1, "b": "test"}], "c": [2, 3, 4, "string"]}'; 8 | @include should(expect(json-encode(json-decode($string))), to(equal($string))); 9 | @include should(expect(json-decode('{"a":[1,2,3],"b":"#000000"}')), to(equal((a:(1 2 3), b: black)))); 10 | @include should(expect(json-decode('{"a":"green","b":"blue"}')), to(equal((a:green, b: blue)))); 11 | @include should(expect(json-decode('[1,2,3,4]')), to(equal((1 2 3 4)))); 12 | } 13 | 14 | // See fixtures 15 | @include it("should load json from files") { 16 | @include should(expect($testvar), to(equal(("test": 1)))); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/decode/fixtures/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "test":1 3 | } -------------------------------------------------------------------------------- /test/decode/helpers/all/_value.scss: -------------------------------------------------------------------------------- 1 | @include describe("The _json-decode--value function") { 2 | @include it("should return a two element list") { 3 | @include should(expect(length(_json-decode--value('"test"', 1))), to(equal(2))); 4 | @include should(expect(length(_json-decode--value('42', 1))), to(equal(2))); 5 | @include should(expect(length(_json-decode--value('true', 1))), to(equal(2))); 6 | @include should(expect(length(_json-decode--value('false', 1))), to(equal(2))); 7 | @include should(expect(length(_json-decode--value('[0, 1]', 1))), to(equal(2))); 8 | @include should(expect(length(_json-decode--value('{"a": 1}', 1))), to(equal(2))); 9 | @include should(expect(length(_json-decode--value('null', 1))), to(equal(2))); 10 | @include should(expect(length(_json-decode--value('"error"', 1))), to(equal(2))); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/decode/helpers/color/_color.scss: -------------------------------------------------------------------------------- 1 | @include describe("The _color function") { 2 | @include it("should decode CSS colors to color type") { 3 | @include should(expect(type-of(_color('#ff0'))), to(equal('color'))); 4 | @include should(expect(type-of(_color('#000000'))), to(equal('color'))); 5 | @include should(expect(type-of(_color('#ffffff'))), to(equal('color'))); 6 | @include should(expect(type-of(_color('#1a1a1a'))), to(equal('color'))); 7 | @include should(expect(type-of(_color('rgb(100, 100, 100)'))), to(equal('color'))); 8 | @include should(expect(type-of(_color('rgb(100%, 100%, 100%)'))), to(equal('color'))); 9 | @include should(expect(type-of(_color('rgba(100, 100, 100, 0.5)'))), to(equal('color'))); 10 | @include should(expect(type-of(_color('rgba(100, 100, 100, .5)'))), to(equal('color'))); 11 | @include should(expect(type-of(_color('rgba(100%, 100%, 100%, 0.5)'))), to(equal('color'))); 12 | @include should(expect(type-of(_color('hsl(100, 100, 100)'))), to(equal('color'))); 13 | @include should(expect(type-of(_color('hsl(100, 100%, 100%)'))), to(equal('color'))); 14 | @include should(expect(type-of(_color('hsla(100, 100, 100, 0.5)'))), to(equal('color'))); 15 | @include should(expect(type-of(_color('hsla(100, 100%, 100%, 0.5)'))), to(equal('color'))); 16 | 17 | $keywords: transparent black silver gray white maroon red purple fuchsia green lime olive yellow navy blue teal aqua aliceblue antiquewhite aqua aquamarine azure beige bisque black blanchedalmond blue blueviolet brown burlywood cadetblue chartreuse chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan darkgoldenrod darkgray darkgreen darkgrey darkkhaki darkmagenta darkolivegreen darkorange darkorchid darkred darksalmon darkseagreen darkslateblue darkslategray darkslategrey darkturquoise darkviolet deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite forestgreen fuchsia gainsboro ghostwhite gold goldenrod gray green greenyellow grey honeydew hotpink indianred indigo ivory khaki lavender lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan lightgoldenrodyellow lightgray lightgreen lightgrey lightpink lightsalmon lightseagreen lightskyblue lightslategray lightslategrey lightsteelblue lightyellow lime limegreen linen magenta maroon mediumaquamarine mediumblue mediumorchid mediumpurple mediumseagreen mediumslateblue mediumspringgreen mediumturquoise mediumvioletred midnightblue mintcream mistyrose moccasin navajowhite navy oldlace olive olivedrab orange orangered orchid palegoldenrod palegreen paleturquoise palevioletred papayawhip peachpuff peru pink plum powderblue purple red rosybrown royalblue saddlebrown salmon sandybrown seagreen seashell sienna silver skyblue slateblue slategray slategrey snow springgreen steelblue tan teal thistle tomato turquoise violet wheat white whitesmoke yellow yellowgreen; 18 | @each $color in $keywords { 19 | @include should(expect(type-of(_color($color))), to(equal('color'))); 20 | } 21 | } 22 | 23 | @include it("should decode CSS colors properly when caps are used") { 24 | @include should(expect(type-of(_color('BLACK'))), to(equal('color'))); 25 | @include should(expect(type-of(_color('transparent'))), to(equal('color'))); 26 | @include should(expect(type-of(_color('#FFFFFF'))), to(equal('color'))); 27 | @include should(expect(type-of(_color('RGB(100, 100, 100)'))), to(equal('color'))); 28 | } 29 | 30 | @include it("should decode properly when weird spaces are used") { 31 | @include should(expect(type-of(_color('rgb( 100 , 100 , 100 )'))), to(equal('color'))); 32 | @include should(expect(type-of(_color('hsl( 100 , 100 , 100 )'))), to(equal('color'))); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /test/decode/helpers/color/_get-color-value.scss: -------------------------------------------------------------------------------- 1 | @include describe("The _get-color-value function") { 2 | @include it("should convert stringified number into an actual number") { 3 | @include should(expect(_get-color-value('0')), to(equal(0))); 4 | @include should(expect(_get-color-value('100')), to(equal(100))); 5 | @include should(expect(_get-color-value('255')), to(equal(255))); 6 | @include should(expect(_get-color-value('0.5')), to(equal(0.5))); 7 | @include should(expect(_get-color-value('.5')), to(equal(0.5))); 8 | } 9 | 10 | @include it("should convert stringified percentage into an actual number") { 11 | @include should(expect(_get-color-value('0%')), to(equal(0))); 12 | @include should(expect(_get-color-value('100%')), to(equal(100))); 13 | @include should(expect(_get-color-value('255%')), to(equal(255))); 14 | @include should(expect(_get-color-value('0.5%')), to(equal(0.5))); 15 | @include should(expect(_get-color-value('.5%')), to(equal(0.5))); 16 | } 17 | 18 | @include it("should always convert to number type") { 19 | @include should(expect(type-of(_get-color-value('0'))), to(equal('number'))); 20 | @include should(expect(type-of(_get-color-value('100'))), to(equal('number'))); 21 | @include should(expect(type-of(_get-color-value('255'))), to(equal('number'))); 22 | @include should(expect(type-of(_get-color-value('0.5'))), to(equal('number'))); 23 | @include should(expect(type-of(_get-color-value('.5'))), to(equal('number'))); 24 | @include should(expect(type-of(_get-color-value('0%'))), to(equal('number'))); 25 | @include should(expect(type-of(_get-color-value('100%'))), to(equal('number'))); 26 | @include should(expect(type-of(_get-color-value('255%'))), to(equal('number'))); 27 | @include should(expect(type-of(_get-color-value('0.5%'))), to(equal('number'))); 28 | @include should(expect(type-of(_get-color-value('.5%'))), to(equal('number'))); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/decode/helpers/color/_hex-to-dec.scss: -------------------------------------------------------------------------------- 1 | @include describe("The _hex-to-dec function") { 2 | @include it("should convert a hexadecimal string into a decimal number") { 3 | @include should(expect(_hex-to-dec('00')), to(equal(0))); 4 | @include should(expect(_hex-to-dec('ff')), to(equal(255))); 5 | @include should(expect(_hex-to-dec('c8')), to(equal(200))); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/decode/helpers/color/_hex.scss: -------------------------------------------------------------------------------- 1 | @include describe("The _from-hex function") { 2 | @include it("should convert a stringified hexadecimal color into a color") { 3 | @include should(expect(type-of(_from-hex('#000'))), to(equal('color'))); 4 | @include should(expect(type-of(_from-hex('#000000'))), to(equal('color'))); 5 | @include should(expect(type-of(_from-hex('#eFa01d'))), to(equal('color'))); 6 | } 7 | 8 | @include it("should return a string if conversion cannot be done") { 9 | @include should(expect(type-of(_from-hex('#00'))), to(equal('string'))); 10 | @include should(expect(type-of(_from-hex('#00000'))), to(equal('string'))); 11 | @include should(expect(type-of(_from-hex('#00000g'))), to(equal('string'))); 12 | @include should(expect(type-of(_from-hex('00000g'))), to(equal('string'))); 13 | @include should(expect(type-of(_from-hex('000000'))), to(equal('string'))); 14 | @include should(expect(type-of(_from-hex('000'))), to(equal('string'))); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/decode/helpers/color/_hsl.scss: -------------------------------------------------------------------------------- 1 | @include describe("The _from-hsl function") { 2 | @include it("should convert a stringified hsl or hsla color into a color") { 3 | @include should(expect(type-of(_from-hsl('hsl(0,0,0)'))), to(equal('color'))); 4 | @include should(expect(type-of(_from-hsl('hsl(0, 0, 0)'))), to(equal('color'))); 5 | @include should(expect(type-of(_from-hsl('hsla(0, 0, 0, .5)'))), to(equal('color'))); 6 | @include should(expect(type-of(_from-hsl('hsla(0, 0, 0, 0.5)'))), to(equal('color'))); 7 | @include should(expect(type-of(_from-hsl('hsla(100%, 100%, 100%, 0.5)'))), to(equal('color'))); 8 | @include should(expect(type-of(_from-hsl('hsla(100%, 100%, 100%, .5)'))), to(equal('color'))); 9 | } 10 | 11 | @include it("should return a string if conversion cannot be done") { 12 | @include should(expect(type-of(_from-hsl('hsl(0, 0, 0, .5)'))), to(equal('string'))); 13 | @include should(expect(type-of(_from-hsl('hsla(0, 0, 0)'))), to(equal('string'))); 14 | @include should(expect(type-of(_from-hsl('hsla(0, 0, 0'))), to(equal('string'))); 15 | @include should(expect(type-of(_from-hsl('hsl(0, 0 0)'))), to(equal('string'))); 16 | @include should(expect(type-of(_from-hsl('hsl(0 0 0)'))), to(equal('string'))); 17 | @include should(expect(type-of(_from-hsl('hsl(0 0, 0)'))), to(equal('string'))); 18 | @include should(expect(type-of(_from-hsl('hsl(0, 0, 0,)'))), to(equal('string'))); 19 | @include should(expect(type-of(_from-hsl('hsl(,0, 0, 0,)'))), to(equal('string'))); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/decode/helpers/color/_rgb.scss: -------------------------------------------------------------------------------- 1 | @include describe("The _from-rgb function") { 2 | @include it("should convert a stringified rgb or rgba color into a color") { 3 | @include should(expect(type-of(_from-rgb('rgb(0,0,0)'))), to(equal('color'))); 4 | @include should(expect(type-of(_from-rgb('rgb(0, 0, 0)'))), to(equal('color'))); 5 | @include should(expect(type-of(_from-rgb('rgba(0, 0, 0, .5)'))), to(equal('color'))); 6 | @include should(expect(type-of(_from-rgb('rgba(0, 0, 0, 0.5)'))), to(equal('color'))); 7 | @include should(expect(type-of(_from-rgb('rgba(100%, 100%, 100%, 0.5)'))), to(equal('color'))); 8 | @include should(expect(type-of(_from-rgb('rgba(100%, 100%, 100%, .5)'))), to(equal('color'))); 9 | } 10 | 11 | @include it("should return a string if conversion cannot be done") { 12 | @include should(expect(type-of(_from-rgb('rgb(0, 0, 0, .5)'))), to(equal('string'))); 13 | @include should(expect(type-of(_from-rgb('rgba(0, 0, 0)'))), to(equal('string'))); 14 | @include should(expect(type-of(_from-rgb('rgba(0, 0, 0'))), to(equal('string'))); 15 | @include should(expect(type-of(_from-rgb('rgb(0, 0 0)'))), to(equal('string'))); 16 | @include should(expect(type-of(_from-rgb('rgb(0 0 0)'))), to(equal('string'))); 17 | @include should(expect(type-of(_from-rgb('rgb(0 0, 0)'))), to(equal('string'))); 18 | @include should(expect(type-of(_from-rgb('rgb(0, 0, 0,)'))), to(equal('string'))); 19 | @include should(expect(type-of(_from-rgb('rgb(,0, 0, 0,)'))), to(equal('string'))); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/decode/helpers/map/_consume.scss: -------------------------------------------------------------------------------- 1 | @include describe("The _consume function") { 2 | @include it("should return the index of the token plus one") { 3 | @include should(expect(_consume(" : whatever", 1, ":")), to(equal(4))); 4 | @include should(expect(_consume(" / test", 1, "/")), to(equal(7))); 5 | } 6 | 7 | @include it("should always return a number") { 8 | @include should(expect(type-of(_consume(" : whatever", 1, ":"))), to(equal('number'))); 9 | @include should(expect(type-of(_consume(" / test", 1, "/"))), to(equal('number'))); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/decode/helpers/number/_find-digits.scss: -------------------------------------------------------------------------------- 1 | @include describe("The _find-digits function") { 2 | @include it("should always return a two item long list") { 3 | @include should(expect(length(_find-digits('.5', 1))), to(equal(2))); 4 | @include should(expect(length(_find-digits('.20', 1))), to(equal(2))); 5 | @include should(expect(length(_find-digits('.101', 1))), to(equal(2))); 6 | @include should(expect(length(_find-digits('.1337', 1))), to(equal(2))); 7 | } 8 | 9 | @include it("should return the number found after the dot as a second argument") { 10 | @include should(expect(nth(_find-digits('.1', 1), 2)), to(equal(0.1))); 11 | @include should(expect(nth(_find-digits('.23', 1), 2)), to(equal(0.23))); 12 | @include should(expect(nth(_find-digits('.456', 1), 2)), to(equal(0.456))); 13 | @include should(expect(nth(_find-digits('.7890', 1), 2)), to(equal(0.7890))); 14 | } 15 | 16 | @include it("should return a number as second argument") { 17 | @include should(expect(type-of(nth(_find-digits('.1', 1), 2))), to(equal('number'))); 18 | @include should(expect(type-of(nth(_find-digits('.23', 1), 2))), to(equal('number'))); 19 | @include should(expect(type-of(nth(_find-digits('.456', 1), 2))), to(equal('number'))); 20 | @include should(expect(type-of(nth(_find-digits('.7890', 1), 2))), to(equal('number'))); 21 | } 22 | 23 | @include it("should return null if error") { 24 | @include should(expect(nth(_find-digits('.', 1), 2)), to(equal(null))); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/decode/helpers/number/_find-exponent.scss: -------------------------------------------------------------------------------- 1 | @include describe("The _find-exponent function") { 2 | @include it("should always return a two item long list") { 3 | @include should(expect(length(_find-exponent('e1', 1))), to(equal(2))); 4 | @include should(expect(length(_find-exponent('e2', 1))), to(equal(2))); 5 | @include should(expect(length(_find-exponent('e5', 1))), to(equal(2))); 6 | @include should(expect(length(_find-exponent('e10', 1))), to(equal(2))); 7 | } 8 | 9 | @include it("should return the exponent part of a stringified number as a second argument") { 10 | @include should(expect(nth(_find-exponent('e-1', 1), 2)), to(equal(-1))); 11 | @include should(expect(nth(_find-exponent('e0', 1), 2)), to(equal(0))); 12 | @include should(expect(nth(_find-exponent('e2', 1), 2)), to(equal(2))); 13 | @include should(expect(nth(_find-exponent('e-3', 1), 2)), to(equal(-3))); 14 | @include should(expect(nth(_find-exponent('e4', 1), 2)), to(equal(4))); 15 | @include should(expect(nth(_find-exponent('e-5', 1), 2)), to(equal(-5))); 16 | @include should(expect(nth(_find-exponent('e6', 1), 2)), to(equal(6))); 17 | @include should(expect(nth(_find-exponent('e-7', 1), 2)), to(equal(-7))); 18 | @include should(expect(nth(_find-exponent('e8', 1), 2)), to(equal(8))); 19 | @include should(expect(nth(_find-exponent('e-9', 1), 2)), to(equal(-9))); 20 | } 21 | 22 | @include it("should return a number as second argument") { 23 | @include should(expect(type-of(nth(_find-exponent('e-1', 1), 2))), to(equal('number'))); 24 | @include should(expect(type-of(nth(_find-exponent('e2', 1), 2))), to(equal('number'))); 25 | @include should(expect(type-of(nth(_find-exponent('e-3', 1), 2))), to(equal('number'))); 26 | @include should(expect(type-of(nth(_find-exponent('e4', 1), 2))), to(equal('number'))); 27 | @include should(expect(type-of(nth(_find-exponent('e-5', 1), 2))), to(equal('number'))); 28 | @include should(expect(type-of(nth(_find-exponent('e6', 1), 2))), to(equal('number'))); 29 | @include should(expect(type-of(nth(_find-exponent('e-7', 1), 2))), to(equal('number'))); 30 | @include should(expect(type-of(nth(_find-exponent('e8', 1), 2))), to(equal('number'))); 31 | @include should(expect(type-of(nth(_find-exponent('e-9', 1), 2))), to(equal('number'))); 32 | } 33 | 34 | @include it("should return null if error") { 35 | @include should(expect(nth(_find-exponent('e', 1), 2)), to(equal(null))); 36 | @include should(expect(nth(_find-exponent('e-', 1), 2)), to(equal(null))); 37 | @include should(expect(nth(_find-exponent('e+', 1), 2)), to(equal(null))); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/decode/helpers/number/_find-integer.scss: -------------------------------------------------------------------------------- 1 | @include describe("The _find-integer function") { 2 | @include it("should always return a two item long list") { 3 | @include should(expect(length(_find-integer('1', 1))), to(equal(2))); 4 | @include should(expect(length(_find-integer('10', 1))), to(equal(2))); 5 | @include should(expect(length(_find-integer('100', 1))), to(equal(2))); 6 | @include should(expect(length(_find-integer('-1000', 1))), to(equal(2))); 7 | } 8 | 9 | @include it("should return the positive integer part of a stringified number as a second argument") { 10 | @include should(expect(nth(_find-integer('0', 1), 2)), to(equal(0))); 11 | @include should(expect(nth(_find-integer('1', 1), 2)), to(equal(1))); 12 | @include should(expect(nth(_find-integer('1.5', 1), 2)), to(equal(1))); 13 | @include should(expect(nth(_find-integer('-1.5', 1), 2)), to(equal(1))); 14 | @include should(expect(nth(_find-integer('-1', 1), 2)), to(equal(1))); 15 | @include should(expect(nth(_find-integer('-1e1', 1), 2)), to(equal(1))); 16 | @include should(expect(nth(_find-integer('-1e+1', 1), 2)), to(equal(1))); 17 | @include should(expect(nth(_find-integer('1e-1', 1), 2)), to(equal(1))); 18 | @include should(expect(nth(_find-integer('1e+1', 1), 2)), to(equal(1))); 19 | } 20 | 21 | @include it("should return a number as second argument") { 22 | @include should(expect(type-of(nth(_find-integer('0', 1), 2))), to(equal('number'))); 23 | @include should(expect(type-of(nth(_find-integer('1', 1), 2))), to(equal('number'))); 24 | @include should(expect(type-of(nth(_find-integer('1.5', 1), 2))), to(equal('number'))); 25 | @include should(expect(type-of(nth(_find-integer('-1.5', 1), 2))), to(equal('number'))); 26 | @include should(expect(type-of(nth(_find-integer('-1', 1), 2))), to(equal('number'))); 27 | @include should(expect(type-of(nth(_find-integer('-1e1', 1), 2))), to(equal('number'))); 28 | @include should(expect(type-of(nth(_find-integer('-1e+1', 1), 2))), to(equal('number'))); 29 | @include should(expect(type-of(nth(_find-integer('1e-1', 1), 2))), to(equal('number'))); 30 | @include should(expect(type-of(nth(_find-integer('1e+1', 1), 2))), to(equal('number'))); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/decode/helpers/number/_pow.scss: -------------------------------------------------------------------------------- 1 | @include describe("The _pow function") { 2 | @include it("should return the power of first argument per second argument") { 3 | @include should(expect(_pow(10, 0)), to(equal(1))); 4 | @include should(expect(_pow(2, 2)), to(equal(4))); 5 | @include should(expect(_pow(2, 10)), to(equal(1024))); 6 | @include should(expect(_pow(10, 2)), to(equal(100))); 7 | @include should(expect(_pow(10, -2)), to(equal(0.01))); 8 | @include should(expect(_pow(-2, 10)), to(equal(1024))); 9 | } 10 | 11 | @include it("should always return a number") { 12 | @include should(expect(type-of(_pow(2, 2))), to(equal('number'))); 13 | @include should(expect(type-of(_pow(2, 10))), to(equal('number'))); 14 | @include should(expect(type-of(_pow(10, 2))), to(equal('number'))); 15 | @include should(expect(type-of(_pow(10, -2))), to(equal('number'))); 16 | @include should(expect(type-of(_pow(-2, 10))), to(equal('number'))); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/decode/helpers/string/_find-ending-quote.scss: -------------------------------------------------------------------------------- 1 | @include describe("The _find-ending-quote function finds the last unescaped quote") { 2 | 3 | @include it("do it with strange spacing") { 4 | @include should(expect(_find-ending-quote('tes t\\\" "')), to(equal(10))); 5 | @include should(expect(_find-ending-quote('tes t" "')), to(equal(8))); 6 | @include should(expect(_find-ending-quote('tes\"\\\"\" t" "')), to(equal(4))); 7 | } 8 | @include it("should return zero if no quote was found") { 9 | @include should(expect(_find-ending-quote('')), to(equal(0))); 10 | @include should(expect(_find-ending-quote('safasfdasdf')), to(equal(0))); 11 | @include should(expect(_find-ending-quote('safas\\"fdasdf')), to(equal(0))); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/decode/helpers/string/_length.scss: -------------------------------------------------------------------------------- 1 | @include describe("The _color function") { 2 | @include it("should decode CSS lengths to number type") { 3 | $test-1: _length('-1%'); 4 | $test-2: _length('1.5in'); 5 | $test-3: _length('-1px'); 6 | $test-4: _length('1.5em'); 7 | $test-5: _length('-1mm'); 8 | $test-6: _length('1.5cm'); 9 | $test-7: _length('-1pt'); 10 | $test-8: _length('1.5pc'); 11 | $test-9: _length('-1ex'); 12 | $test-10: _length('1.5ch'); 13 | $test-11: _length('-1vh'); 14 | $test-12: _length('1.5vw'); 15 | $test-13: _length('-1rem'); 16 | $test-14: _length('1.5vmin'); 17 | $test-15: _length('-1vmax'); 18 | $test-16: _length('1.5pica'); 19 | 20 | @include should(expect($test-1 ), to(equal(-1%))); 21 | @include should(expect($test-2 ), to(equal(1.5in))); 22 | @include should(expect($test-3 ), to(equal(-1px))); 23 | @include should(expect($test-4 ), to(equal(1.5em))); 24 | @include should(expect($test-5 ), to(equal(-1mm))); 25 | @include should(expect($test-6 ), to(equal(1.5cm))); 26 | @include should(expect($test-7 ), to(equal(-1pt))); 27 | @include should(expect($test-8 ), to(equal(1.5pc))); 28 | @include should(expect($test-9 ), to(equal(-1ex))); 29 | @include should(expect($test-10), to(equal(1.5ch))); 30 | @include should(expect($test-11), to(equal(-1vh))); 31 | @include should(expect($test-12), to(equal(1.5vw))); 32 | @include should(expect($test-13), to(equal(-1rem))); 33 | @include should(expect($test-14), to(equal(1.5vmin))); 34 | @include should(expect($test-15), to(equal(-1vmax))); 35 | @include should(expect($test-16), to(equal(1.5pica))); 36 | 37 | @include should(expect(type-of($test-1 )), to(equal('number'))); 38 | @include should(expect(type-of($test-2 )), to(equal('number'))); 39 | @include should(expect(type-of($test-3 )), to(equal('number'))); 40 | @include should(expect(type-of($test-4 )), to(equal('number'))); 41 | @include should(expect(type-of($test-5 )), to(equal('number'))); 42 | @include should(expect(type-of($test-6 )), to(equal('number'))); 43 | @include should(expect(type-of($test-7 )), to(equal('number'))); 44 | @include should(expect(type-of($test-8 )), to(equal('number'))); 45 | @include should(expect(type-of($test-9 )), to(equal('number'))); 46 | @include should(expect(type-of($test-10)), to(equal('number'))); 47 | @include should(expect(type-of($test-11)), to(equal('number'))); 48 | @include should(expect(type-of($test-12)), to(equal('number'))); 49 | @include should(expect(type-of($test-13)), to(equal('number'))); 50 | @include should(expect(type-of($test-14)), to(equal('number'))); 51 | @include should(expect(type-of($test-15)), to(equal('number'))); 52 | @include should(expect(type-of($test-16)), to(equal('number'))); 53 | } 54 | } -------------------------------------------------------------------------------- /test/decode/helpers/string/_strip-token.scss: -------------------------------------------------------------------------------- 1 | @include describe("The _strip-token should replace tokes with CRs") { 2 | $cr: " 3 | "; // Carriage return 4 | 5 | @include it("should work") { 6 | @include should(expect(_strip-token(' test \r test ', '\r', $cr)), to(equal(' test ' + $cr + ' test '))); 7 | @include should(expect(_strip-token(' test \n test ', '\n', $cr)), to(equal(' test ' + $cr + ' test '))); 8 | @debug _strip-token(' test \ n test ', '\n', $cr); 9 | @include should(expect(_strip-token(' test \ n test ', '\n', $cr)), to(equal(' test \ n test '))); 10 | @include should(expect(_strip-token(' abc \ n', '\n', $cr)), to(equal(' abc '))); 11 | @include should(expect(_strip-token(_strip-token(' test \r\n test ', '\r', $cr), '\n', $cr)), to(equal(' test ' + $cr + $cr + ' test '))); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/decode/test.scss: -------------------------------------------------------------------------------- 1 | @import "bootcamp"; 2 | @import "sassyjson"; 3 | 4 | @include runner-start; 5 | 6 | @import "index"; 7 | 8 | @include runner-end; 9 | -------------------------------------------------------------------------------- /test/decode/types/_bool.scss: -------------------------------------------------------------------------------- 1 | @include describe("The json-decode--bool function") { 2 | @include it("should work for true with _json-decode--true"){ 3 | @include should(expect(_json-decode--true('true', 2)), to(equal((5, true)))); 4 | } 5 | 6 | @include it("should work for false with _json-decode--false"){ 7 | @include should(expect(_json-decode--false('false', 2)), to(equal((6, false)))); 8 | } 9 | 10 | @include it("should decode to bool type") { 11 | @include should(expect(type-of(nth(_json-decode--true('true', 2), 2))), to(equal('bool'))); 12 | @include should(expect(type-of(nth(_json-decode--true('true', 2), 2))), to(equal('bool'))); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/decode/types/_list.scss: -------------------------------------------------------------------------------- 1 | @include describe("The json-decode--list function") { 2 | @include it("should properly decode arrays to lists") { 3 | @include should(expect(nth(_json-decode--list('[0, 1, 2, 3]', 2), 2)), to(equal(0 1 2 3))); 4 | @include should(expect(nth(_json-decode--list('["a", "b", "c", "d"]', 2), 2)), to(equal("a" "b" "c" "d"))); 5 | @include should(expect(nth(_json-decode--list('[true, false, false, true, true]', 2), 2)), to(equal(true false false true true))); 6 | @include should(expect(nth(_json-decode--list('["red", "#405060", "rgb(0, 0, 0)"]', 2), 2)), to(equal(red #405060 black))); 7 | } 8 | 9 | @include it("should properly decode multi-dimensional arrays to nested lists") { 10 | @include should(expect(nth(_json-decode--list('[0, 1, [2, 3, [4, 5], 6]]', 2), 2)), to(equal(0 1 (2 3 (4 5) 6)))); 11 | @include should(expect(nth(_json-decode--list('[["a", "b", ["c", "d"]], "e"]', 2), 2)), to(equal(("a" "b" ("c" "d")) "e"))); 12 | @include should(expect(nth(_json-decode--list('[true, false, [false, true], true]', 2), 2)), to(equal(true false (false true) true))); 13 | @include should(expect(nth(_json-decode--list('[[true], [false], [false, true], true]', 2), 2)), to(equal((true) (false) (false true) true))); 14 | } 15 | 16 | @include it("should properly decode arrays with weird spaces to lists") { 17 | @include should(expect(nth(_json-decode--list('[ 0 , 1 , 2,3 ]', 2), 2)), to(equal(0 1 2 3))); 18 | } 19 | 20 | @include it("should properly decode empty arrays") { 21 | //@include should(expect(nth(_json-decode--list('[]', 2), 2)), to(equal(()))); 22 | //@include should(expect(nth(_json-decode--list('[ ]', 2), 2)), to(equal(()))); 23 | } 24 | 25 | @include it("should decode to list type") { 26 | @include should(expect(type-of(nth(_json-decode--list('[0, 1, 2, 3]', 2), 2))), to(equal('list'))); 27 | @include should(expect(type-of(nth(_json-decode--list('["a", "b", "c", "d"]', 2), 2))), to(equal('list'))); 28 | @include should(expect(type-of(nth(_json-decode--list('[true, false, false, true, true]', 2), 2))), to(equal('list'))); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/decode/types/_map.scss: -------------------------------------------------------------------------------- 1 | @include describe("The json-decode--map function") { 2 | @include it("should properly decode objects to map type") { 3 | @include should(expect(nth(_json-decode--map('{"a": 1}', 2), 2)), to(equal(("a": 1)))); 4 | @include should(expect(nth(_json-decode--map('{"a": 1, "b": "string", "c": true}', 2), 2)), to(equal(("a": 1, "b": "string", "c": true)))); 5 | } 6 | @include it("should properly decode objects with weird spaces to map type") { 7 | @include should(expect(nth(_json-decode--map(' {"a":1,"b":2}', 3), 2)), to(equal(("a": 1, "b": 2)))); 8 | @include should(expect(nth(_json-decode--map('{ "a":1,"b":2}', 2), 2)), to(equal(("a": 1, "b": 2)))); 9 | @include should(expect(nth(_json-decode--map('{"a" :1,"b":2}', 2), 2)), to(equal(("a": 1, "b": 2)))); 10 | @include should(expect(nth(_json-decode--map('{"a": 1,"b":2}', 2), 2)), to(equal(("a": 1, "b": 2)))); 11 | @include should(expect(nth(_json-decode--map('{"a":1 ,"b":2}', 2), 2)), to(equal(("a": 1, "b": 2)))); 12 | @include should(expect(nth(_json-decode--map('{"a":1, "b":2}', 2), 2)), to(equal(("a": 1, "b": 2)))); 13 | @include should(expect(nth(_json-decode--map('{"a":1,"b" :2}', 2), 2)), to(equal(("a": 1, "b": 2)))); 14 | @include should(expect(nth(_json-decode--map('{"a":1,"b": 2}', 2), 2)), to(equal(("a": 1, "b": 2)))); 15 | @include should(expect(nth(_json-decode--map('{"a":1,"b":2 }', 2), 2)), to(equal(("a": 1, "b": 2)))); 16 | @include should(expect(nth(_json-decode--map('{"a":1,"b":2} ', 2), 2)), to(equal(("a": 1, "b": 2)))); 17 | } 18 | 19 | @include it("should decode to map type") { 20 | @include should(expect(type-of(nth(_json-decode--map('{"a": 1}', 2), 2))), to(equal('map'))); 21 | @include should(expect(type-of(nth(_json-decode--map('{"a": 1, "b": "string", "c": true}', 2), 2))), to(equal('map'))); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/decode/types/_null.scss: -------------------------------------------------------------------------------- 1 | @include describe("The json-decode--null function") { 2 | @include it("should properly decode null to null") { 3 | @include should(expect(nth(_json-decode--null('null', 2), 2)), to(equal(null))); 4 | } 5 | 6 | @include it("should decode to null type") { 7 | @include should(expect(type-of(nth(_json-decode--null('null', 2), 2))), to(equal('null'))); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/decode/types/_number.scss: -------------------------------------------------------------------------------- 1 | @include describe("The _json-decode--number function") { 2 | $test-1: nth(_json-decode--number('0', 2), 2); 3 | $test-2: nth(_json-decode--number('10', 2), 2); 4 | $test-3: nth(_json-decode--number('123456789', 2), 2); 5 | $test-4: nth(_json-decode--number('-0', 2), 2); 6 | $test-5: nth(_json-decode--number('-10', 2), 2); 7 | $test-6: nth(_json-decode--number('-123456789', 2), 2); 8 | $test-7: nth(_json-decode--number('0.001', 2), 2); 9 | $test-8: nth(_json-decode--number('-0.001', 2), 2); 10 | $test-9: nth(_json-decode--number('1.5', 2), 2); 11 | $test-10: nth(_json-decode--number('-1.5', 2), 2); 12 | $test-11: nth(_json-decode--number('1e0', 2), 2); 13 | $test-12: nth(_json-decode--number('1e1', 2), 2); 14 | $test-13: nth(_json-decode--number('1e2', 2), 2); 15 | $test-14: nth(_json-decode--number('1e+0', 2), 2); 16 | $test-15: nth(_json-decode--number('1e+1', 2), 2); 17 | $test-16: nth(_json-decode--number('1e+2', 2), 2); 18 | $test-17: nth(_json-decode--number('1E0', 2), 2); 19 | $test-18: nth(_json-decode--number('1E1', 2), 2); 20 | $test-19: nth(_json-decode--number('1E2', 2), 2); 21 | $test-20: nth(_json-decode--number('1E+0', 2), 2); 22 | $test-21: nth(_json-decode--number('1E+1', 2), 2); 23 | $test-22: nth(_json-decode--number('1E+2', 2), 2); 24 | $test-23: nth(_json-decode--number('1e-0', 2), 2); 25 | $test-24: nth(_json-decode--number('1e-1', 2), 2); 26 | $test-25: nth(_json-decode--number('1e-2', 2), 2); 27 | $test-26: nth(_json-decode--number('1E-0', 2), 2); 28 | $test-27: nth(_json-decode--number('1E-1', 2), 2); 29 | $test-28: nth(_json-decode--number('1E-2', 2), 2); 30 | 31 | @include it("should properly decode integers") { 32 | @include should(expect($test-1), to(equal(0))); 33 | @include should(expect($test-2), to(equal(10))); 34 | @include should(expect($test-3), to(equal(123456789))); 35 | } 36 | 37 | @include it("should properly decode negative numbers") { 38 | @include should(expect($test-4), to(equal(0))); 39 | @include should(expect($test-5), to(equal(-10))); 40 | @include should(expect($test-6), to(equal(-123456789))); 41 | } 42 | 43 | @include it("should properly decode numbers with digits") { 44 | @include should(expect($test-7), to(equal(0.001))); 45 | @include should(expect($test-8), to(equal(-0.001))); 46 | @include should(expect($test-9), to(equal(1.5))); 47 | @include should(expect($test-10), to(equal(-1.5))); 48 | } 49 | 50 | @include it("should properly decode numbers with exponents") { 51 | @include should(expect($test-11), to(equal(1))); 52 | @include should(expect($test-12), to(equal(10))); 53 | @include should(expect($test-13), to(equal(100))); 54 | @include should(expect($test-14), to(equal(1))); 55 | @include should(expect($test-15), to(equal(10))); 56 | @include should(expect($test-16), to(equal(100))); 57 | @include should(expect($test-17), to(equal(1))); 58 | @include should(expect($test-18), to(equal(10))); 59 | @include should(expect($test-19), to(equal(100))); 60 | @include should(expect($test-20), to(equal(1))); 61 | @include should(expect($test-21), to(equal(10))); 62 | @include should(expect($test-22), to(equal(100))); 63 | } 64 | 65 | @include it("should properly decode numbers with negative exponents") { 66 | @include should(expect($test-23), to(equal(1))); 67 | @include should(expect($test-24), to(equal(.1))); 68 | @include should(expect($test-25), to(equal(.01))); 69 | @include should(expect($test-26), to(equal(1))); 70 | @include should(expect($test-27), to(equal(.1))); 71 | @include should(expect($test-28), to(equal(.01))); 72 | } 73 | 74 | @include it("should decode to number type") { 75 | @include should(expect(type-of($test-1 )), to(equal('number'))); 76 | @include should(expect(type-of($test-2 )), to(equal('number'))); 77 | @include should(expect(type-of($test-3 )), to(equal('number'))); 78 | @include should(expect(type-of($test-4 )), to(equal('number'))); 79 | @include should(expect(type-of($test-5 )), to(equal('number'))); 80 | @include should(expect(type-of($test-6 )), to(equal('number'))); 81 | @include should(expect(type-of($test-7 )), to(equal('number'))); 82 | @include should(expect(type-of($test-8 )), to(equal('number'))); 83 | @include should(expect(type-of($test-9 )), to(equal('number'))); 84 | @include should(expect(type-of($test-10)), to(equal('number'))); 85 | @include should(expect(type-of($test-11)), to(equal('number'))); 86 | @include should(expect(type-of($test-12)), to(equal('number'))); 87 | @include should(expect(type-of($test-13)), to(equal('number'))); 88 | @include should(expect(type-of($test-14)), to(equal('number'))); 89 | @include should(expect(type-of($test-15)), to(equal('number'))); 90 | @include should(expect(type-of($test-16)), to(equal('number'))); 91 | @include should(expect(type-of($test-17)), to(equal('number'))); 92 | @include should(expect(type-of($test-18)), to(equal('number'))); 93 | @include should(expect(type-of($test-19)), to(equal('number'))); 94 | @include should(expect(type-of($test-20)), to(equal('number'))); 95 | @include should(expect(type-of($test-21)), to(equal('number'))); 96 | @include should(expect(type-of($test-22)), to(equal('number'))); 97 | @include should(expect(type-of($test-23)), to(equal('number'))); 98 | @include should(expect(type-of($test-24)), to(equal('number'))); 99 | @include should(expect(type-of($test-25)), to(equal('number'))); 100 | @include should(expect(type-of($test-26)), to(equal('number'))); 101 | @include should(expect(type-of($test-27)), to(equal('number'))); 102 | @include should(expect(type-of($test-28)), to(equal('number'))); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /test/decode/types/_string.scss: -------------------------------------------------------------------------------- 1 | @include describe("The json-decode--string function") { 2 | @include it("should properly decode strings") { 3 | @include should(expect(nth(_json-decode--string('""', 2), 2)), to(equal(""))); 4 | @include should(expect(nth(_json-decode--string('"1 px"', 2), 2)), to(equal("1 px"))); 5 | @include should(expect(nth(_json-decode--string('"test"', 2), 2)), to(equal("test"))); 6 | @include should(expect(nth(_json-decode--string('"test with inner spaces"', 2), 2)), to(equal("test with inner spaces"))); 7 | @include should(expect(nth(_json-decode--string('" test with outer spaces "', 2), 2)), to(equal(" test with outer spaces "))); 8 | } 9 | 10 | @include it("should decode to string type") { 11 | @include should(expect(type-of(nth(_json-decode--string('""', 2), 2))), to(equal('string'))); 12 | @include should(expect(type-of(nth(_json-decode--string('"1 px"', 2), 2))), to(equal('string'))); 13 | @include should(expect(type-of(nth(_json-decode--string('"test"', 2), 2))), to(equal('string'))); 14 | @include should(expect(type-of(nth(_json-decode--string('"test with inner spaces"', 2), 2))), to(equal('string'))); 15 | @include should(expect(type-of(nth(_json-decode--string('" test with outer spaces "', 2), 2))), to(equal('string'))); 16 | } 17 | 18 | @include it("should work with escaped quotes in string") { 19 | @include should(expect(nth(_json-decode--string('"test\\"test"', 2), 2)), to(equal('test\\"test'))); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/encode/_index.scss: -------------------------------------------------------------------------------- 1 | // Helpers 2 | @import "helpers/quote"; 3 | 4 | // Types 5 | @import "types/string"; 6 | @import "types/number"; 7 | @import "types/color"; 8 | @import "types/bool"; 9 | @import "types/list"; 10 | @import "types/map"; 11 | @import "types/null"; 12 | 13 | // API 14 | @import "api/json"; 15 | -------------------------------------------------------------------------------- /test/encode/api/_json.scss: -------------------------------------------------------------------------------- 1 | @include describe("The json-encode function") { 2 | @include it("should return the same as json-encode--bool for a boolean") { 3 | @include should(expect(json-encode(true)), to(equal(_json-encode--bool(true)))); 4 | } 5 | @include it("should return the same as json-encode--string for a string") { 6 | @include should(expect(json-encode("test")), to(equal(_json-encode--string("test")))); 7 | } 8 | @include it("should return the same as json-encode--color for a color") { 9 | @include should(expect(json-encode(#000)), to(equal(_json-encode--color("#000")))); 10 | } 11 | @include it("should return the same as json-encode--number for a number") { 12 | @include should(expect(json-encode(1)), to(equal(_json-encode--number(1)))); 13 | } 14 | @include it("should return the same as json-encode--list for a list") { 15 | @include should(expect(json-encode(1 2 3)), to(equal(_json-encode--list((1 2 3))))); 16 | } 17 | @include it("should return the same as json-encode--map for a map") { 18 | @include should(expect(json-encode( (a: 1, b: 2, c: 3) )), to(equal(_json-encode--map((a: 1, b: 2, c: 3))))); 19 | } 20 | 21 | @include it("should handle some complicated map"){ 22 | $map-all: (a: (1 (a: (1 2 ( b : (1 2 3 4) )), b: ( #444444, false, ( a: 1, b: test ) ), c: (2 3 4 string)) ( b : 1 )), b: ( #444444, false, ( a: 1, b: test ) ), c: (2 (a: (1 2 ( b : 1 )), b: ( #444444, false, ( a: 1, b: test ) ), c: (2 3 4 string)) 4 string)); 23 | @include should(expect(json-encode($map-all)), to(equal('{"a": [1, {"a": [1, 2, {"b": [1, 2, 3, 4]}], "b": ["#444444", false, {"a": 1, "b": "test"}], "c": [2, 3, 4, "string"]}, {"b": 1}], "b": ["#444444", false, {"a": 1, "b": "test"}], "c": [2, {"a": [1, 2, {"b": 1}], "b": ["#444444", false, {"a": 1, "b": "test"}], "c": [2, 3, 4, "string"]}, 4, "string"]}'))); 24 | } 25 | @include it("should handle null correctly"){ 26 | $map-with-null: (a: null); 27 | @include should(expect(json-encode($map-with-null)), to(equal('{"a": null}'))); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/encode/helpers/_quote.scss: -------------------------------------------------------------------------------- 1 | @include describe("The _proof-quote function") { 2 | @include it("should return a quoted string") { 3 | @include should(expect(_proof-quote('test')), to(equal('"' + 'test' + '"'))); 4 | @include should(expect(_proof-quote(1)), to(equal('"' + '1' + '"'))); 5 | } 6 | } -------------------------------------------------------------------------------- /test/encode/test.scss: -------------------------------------------------------------------------------- 1 | @import "bootcamp"; 2 | @import "sassyjson"; 3 | 4 | @include runner-start; 5 | 6 | @import "index"; 7 | 8 | @include runner-end; 9 | -------------------------------------------------------------------------------- /test/encode/types/_bool.scss: -------------------------------------------------------------------------------- 1 | @include describe("The _json-encode--bool function") { 2 | @include it("should return the unchanged boolean") { 3 | @include should(expect(_json-encode--bool(true)), to(equal(true))); 4 | @include should(expect(_json-encode--bool(false)), to(equal(false))); 5 | } 6 | } -------------------------------------------------------------------------------- /test/encode/types/_color.scss: -------------------------------------------------------------------------------- 1 | @include describe("The _json-encode--color function") { 2 | @include it("should return a sass interpreted color wrapped in quotes") { 3 | @include should(expect(_json-encode--color(#000)), to(equal('"' + '#000' + '"'))); 4 | @include should(expect(_json-encode--color(rgba(255,255,225,1))), to(equal('"' + '#ffffe1' + '"'))); 5 | @include should(expect(_json-encode--color(rgba(255,255,225,.5))), to(equal('"' + 'rgba(255, 255, 225, 0.5)' + '"'))); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/encode/types/_list.scss: -------------------------------------------------------------------------------- 1 | @include describe("The _json-encode--list function") { 2 | @include it("should return a JSON like array string") { 3 | @include should(expect(_json-encode--list(1 2 3)), to(equal('[1, 2, 3]'))); 4 | @include should(expect(_json-encode--list(a b c)), to(equal('["a", "b", "c"]'))); 5 | @include should(expect(_json-encode--list(true false true)), to(equal("[true, false, true]"))); 6 | } 7 | 8 | @include it("should encode sass datatypes to strings") { 9 | @include should(expect(_json-encode--list(#000 green blue)), to(equal('["#000", "green", "blue"]'))); 10 | } 11 | 12 | @include it("should properly encode a list with commas to JSON") { 13 | @include should(expect(_json-encode--list((1, 2, 3))), to(equal('[1, 2, 3]'))); 14 | @include should(expect(_json-encode--list((a, b, c))), to(equal('["a", "b", "c"]'))); 15 | @include should(expect(_json-encode--list((true, false, true))), to(equal("[true, false, true]"))); 16 | @include should(expect(_json-encode--list((#333, #444, #555))), to(equal('["#333", "#444", "#555"]'))); 17 | } 18 | 19 | @include it("should properly encode nested lists to JSON") { 20 | @include should(expect(_json-encode--list((1 2 (3 4 5 (6 7) 8) 9))), to(equal('[1, 2, [3, 4, 5, [6, 7], 8], 9]'))); 21 | @include should(expect(_json-encode--list((a b (c d e (f g) h) i))), to(equal('["a", "b", ["c", "d", "e", ["f", "g"], "h"], "i"]'))); 22 | @include should(expect(_json-encode--list((true (false true (false true) true false)))), to(equal("[true, [false, true, [false, true], true, false]]"))); 23 | @include should(expect(_json-encode--list((#333 (#444 #555 #666) (#555 (#444 #333))))), to(equal('["#333", ["#444", "#555", "#666"], ["#555", ["#444", "#333"]]]'))); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/encode/types/_map.scss: -------------------------------------------------------------------------------- 1 | @include describe("The _json-encode--map function") { 2 | $map: (a: 1, b: 2, c: 3); 3 | $map-nested: (a: (b: 1 2 (c: test))); 4 | $map-full: (a: (1 2 ( b : 1 )), b: ( #444444, false, ( a: 1, b: test ) ), c: (2 3 4 string)); 5 | 6 | @include it("should return a JSON object string") { 7 | @include should(expect(_json-encode--map($map)), to(equal('{"a": 1, "b": 2, "c": 3}'))); 8 | } 9 | 10 | @include it("should return a JSON object string for nested maps") { 11 | @include should(expect(_json-encode--map($map-nested)), to(equal('{"a": {"b": [1, 2, {"c": "test"}]}}'))); 12 | } 13 | 14 | @include it("should interpolate sass datatypes") { 15 | @include should(expect(_json-encode--map($map-full)), to(equal('{"a": [1, 2, {"b": 1}], "b": ["#444444", false, {"a": 1, "b": "test"}], "c": [2, 3, 4, "string"]}'))); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/encode/types/_null.scss: -------------------------------------------------------------------------------- 1 | @include describe("The _json-encode--null function") { 2 | @include it("should return a string null for every input") { 3 | @include should(expect(_json-encode--null(true)), to(equal("null"))); 4 | @include should(expect(_json-encode--null(false)), to(equal("null"))); 5 | @include should(expect(_json-encode--null(1)), to(equal("null"))); 6 | @include should(expect(_json-encode--null(null)), to(equal("null"))); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/encode/types/_number.scss: -------------------------------------------------------------------------------- 1 | @include describe("The _json-encode--number function") { 2 | @include it("should return a number if input is unitless") { 3 | @include should(expect(_json-encode--number(42)), to(equal(42))); 4 | @include should(expect(_json-encode--number(13.37)), to(equal(13.37))); 5 | @include should(expect(_json-encode--number(-42)), to(equal(-42))); 6 | @include should(expect(_json-encode--number(-13.37)), to(equal(-13.37))); 7 | } 8 | 9 | @include it("should return encode a number to number type") { 10 | @include should(expect(type-of(_json-encode--number(42))), to(equal('number'))); 11 | @include should(expect(type-of(_json-encode--number(13.37))), to(equal('number'))); 12 | @include should(expect(type-of(_json-encode--number(-42))), to(equal('number'))); 13 | @include should(expect(type-of(_json-encode--number(-13.37))), to(equal('number'))); 14 | } 15 | 16 | @include it("should return a string if input has a unit") { 17 | $test-1: _json-encode--number(1%); 18 | $test-2: _json-encode--number(1in); 19 | $test-3: _json-encode--number(1px); 20 | $test-4: _json-encode--number(1em); 21 | $test-5: _json-encode--number(1mm); 22 | $test-6: _json-encode--number(1cm); 23 | $test-7: _json-encode--number(1pt); 24 | $test-8: _json-encode--number(1pc); 25 | $test-9: _json-encode--number(1ex); 26 | $test-10: _json-encode--number(1ch); 27 | $test-11: _json-encode--number(1vh); 28 | $test-12: _json-encode--number(1vw); 29 | $test-13: _json-encode--number(1rem); 30 | $test-14: _json-encode--number(1vmin); 31 | $test-15: _json-encode--number(1vmax); 32 | $test-16: _json-encode--number(1pica); 33 | 34 | @include should(expect($test-1 ), to(equal('"1%"'))); 35 | @include should(expect($test-2 ), to(equal('"1in"'))); 36 | @include should(expect($test-3 ), to(equal('"1px"'))); 37 | @include should(expect($test-4 ), to(equal('"1em"'))); 38 | @include should(expect($test-5 ), to(equal('"1mm"'))); 39 | @include should(expect($test-6 ), to(equal('"1cm"'))); 40 | @include should(expect($test-7 ), to(equal('"1pt"'))); 41 | @include should(expect($test-8 ), to(equal('"1pc"'))); 42 | @include should(expect($test-9 ), to(equal('"1ex"'))); 43 | @include should(expect($test-10), to(equal('"1ch"'))); 44 | @include should(expect($test-11), to(equal('"1vh"'))); 45 | @include should(expect($test-12), to(equal('"1vw"'))); 46 | @include should(expect($test-13), to(equal('"1rem"'))); 47 | @include should(expect($test-14), to(equal('"1vmin"'))); 48 | @include should(expect($test-15), to(equal('"1vmax"'))); 49 | @include should(expect($test-16), to(equal('"1pica"'))); 50 | 51 | @include should(expect(type-of($test-1 )), to(equal("string"))); 52 | @include should(expect(type-of($test-2 )), to(equal("string"))); 53 | @include should(expect(type-of($test-3 )), to(equal("string"))); 54 | @include should(expect(type-of($test-4 )), to(equal("string"))); 55 | @include should(expect(type-of($test-5 )), to(equal("string"))); 56 | @include should(expect(type-of($test-6 )), to(equal("string"))); 57 | @include should(expect(type-of($test-7 )), to(equal("string"))); 58 | @include should(expect(type-of($test-8 )), to(equal("string"))); 59 | @include should(expect(type-of($test-9 )), to(equal("string"))); 60 | @include should(expect(type-of($test-10)), to(equal("string"))); 61 | @include should(expect(type-of($test-11)), to(equal("string"))); 62 | @include should(expect(type-of($test-12)), to(equal("string"))); 63 | @include should(expect(type-of($test-13)), to(equal("string"))); 64 | @include should(expect(type-of($test-14)), to(equal("string"))); 65 | @include should(expect(type-of($test-15)), to(equal("string"))); 66 | @include should(expect(type-of($test-16)), to(equal("string"))); 67 | } 68 | } -------------------------------------------------------------------------------- /test/encode/types/_string.scss: -------------------------------------------------------------------------------- 1 | @include describe("The _json-encode--string function") { 2 | @include it("should return a quoted string") { 3 | @include should(expect(_json-encode--string("test")), to(equal('"' + 'test' + '"'))); 4 | @include should(expect(_json-encode--string(test)), to(equal('"' + 'test' + '"'))); 5 | @include should(expect(_json-encode--string("1")), to(equal('"' + '1' + '"'))); 6 | @include should(expect(_json-encode--string("true")), to(equal('"' + 'true' + '"'))); 7 | @include should(expect(_json-encode--string("false")), to(equal('"' + 'false' + '"'))); 8 | } 9 | } -------------------------------------------------------------------------------- /test/test.scss: -------------------------------------------------------------------------------- 1 | @import "bootcamp"; 2 | @import "SassyJSON"; 3 | 4 | @include runner-start; 5 | 6 | // Encoder 7 | @import "encode/index"; 8 | 9 | // Decoder 10 | @import "decode/index"; 11 | 12 | @include runner-end; --------------------------------------------------------------------------------