Skip to content

Instantly share code, notes, and snippets.

@FWeinb
Created January 18, 2014 09:46
Show Gist options
  • Save FWeinb/8488301 to your computer and use it in GitHub Desktop.
Save FWeinb/8488301 to your computer and use it in GitHub Desktop.
Controllable mixin
// ----
// Sass (v3.3.0.rc.2)
// Compass (v1.0.0.alpha.17)
// ----
/*! sassyjson - v0.0.2 - 2014-01-17 */
// Logs an error at `$pointer` with `$string` message
// --------------------------------------------------------------------------------
// @param [string] $string: error message
// @param [number] $pointer: pointer position
// --------------------------------------------------------------------------------
// @return [list] (pointer, false)
@function _throw($string, $pointer) {
@warn "ERROR::#{$pointer}::#{$string}";
@return $pointer, false;
}
// Move pointer to position of token
// --------------------------------------------------------------------------------
// @param [string] $source: JSON complete source
// @param [number] $pointer: current pointer
// @param [string] $token: token to reach
// --------------------------------------------------------------------------------
// @throw "Expected $token; found {x}."
// @throw "Expected $token but reached end of stream."
// --------------------------------------------------------------------------------
// @return [number|false] new pointer
@function _consume($source, $pointer, $token) {
$length: str-length($source);
@while $pointer <= $length {
$char: str-slice($source, $pointer, $pointer);
$pointer: $pointer + 1;
@if $char == $token {
@return $pointer;
}
@else if $char == " " {
// @continue;
}
@else {
@return _throw("Expected `" + $token + "; ` found `" + $char + "`.", $pointer);
}
}
@return _throw("Expected `" + $token + "` but reached end of stream.", $pointer);
}
// Delay parsing to type-specific function
// according to found character
// --------------------------------------------------------------------------------
// @param [string] $source: JSON complete source
// @param [number] $pointer: current pointer
// --------------------------------------------------------------------------------
// @throw "Unexpected token $token."
// @throw "Empty JSON string."
// --------------------------------------------------------------------------------
// @return [list|false] (new pointer, parsed value)
@function _json-decode--value($source, $pointer) {
$length: str-length($source);
@while $pointer <= $length {
$token: str-slice($source, $pointer, $pointer);
$pointer: $pointer + 1;
@if $token == '{' {
@return _json-decode--map($source, $pointer);
}
@else if $token == '[' {
@return _json-decode--list($source, $pointer);
}
@else if $token == 't' {
@return _json-decode--true($source, $pointer);
}
@else if $token == 'f' {
@return _json-decode--false($source, $pointer);
}
@else if $token == '"' {
@return _json-decode--string($source, $pointer);
}
@else if $token == 'n' {
@return _json-decode--null($source, $pointer);
}
@else if index('1' '2' '3' '4' '5' '6' '7' '8' '9' '0' '-' '.', $token) {
@return _json-decode--number($source, $pointer);
}
@else if $token == ' ' {
// @continue;
}
@else {
@return _throw("Unexpected token `" + $token + "`.", $pointer);
}
}
@return _throw("Empty JSON string.", $pointer);
}
// Power function
// --------------------------------------------------------------------------------
// @param [number] $x: number
// @param [number] $n: power
// --------------------------------------------------------------------------------
// @return [number] $x ^ $n
@function _pow($x, $n) {
$ret: 1;
@if $n >= 0 {
@for $i from 1 through $n {
$ret: $ret * $x;
}
} @else {
@for $i from $n to 0 {
$ret: $ret / $x;
}
}
@return $ret;
}
// Parses a JSON encoded number to find the integer part
// --------------------------------------------------------------------------------
// @param [string] $source: JSON complete source
// @param [number] $pointer: current pointer
// --------------------------------------------------------------------------------
// @throw "Unexpected token $token."
// --------------------------------------------------------------------------------
// @return [list|false] (new pointer, parsed number)
@function _find-integer($source, $pointer) {
$length: str-length($source);
$strings: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9';
$numbers: 0 1 2 3 4 5 6 7 8 9;
$result: 0;
@while $pointer <= $length {
$token: to-lower-case(str-slice($source, $pointer, $pointer));
$index: index($strings, $token);
@if $token == '-' {
// do nothing
}
@else if $index {
$number: nth($numbers, $index);
$result: $result * 10 + $number;
}
@else {
@if index('e' '.' ',' ']' '}' ' ', $token) {
@return $pointer, $result;
}
@return _throw("Unexpected token `" + $token + "`.", $pointer);
}
$pointer: $pointer + 1;
}
@return $pointer, $result;
}
// Parses a JSON encoded number to find the digits
// --------------------------------------------------------------------------------
// @param [string] $source: JSON complete source
// @param [number] $pointer: current pointer
// --------------------------------------------------------------------------------
// @throw "Unexpected token $token."
// --------------------------------------------------------------------------------
// @return [list|false] (new pointer, parsed number)
@function _find-digits($source, $pointer) {
$length: str-length($source);
$strings: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9';
$numbers: 0 1 2 3 4 5 6 7 8 9;
$result: null;
$runs: 1;
@while $pointer <= $length {
$token: to-lower-case(str-slice($source, $pointer, $pointer));
$index: index($strings, $token);
@if $token == '.' {
// @continue;
}
@else if $index and $index > 0 {
$number: nth($numbers, $index);
$runs: $runs * 10;
$result: if($result == null, $number, $result * 10 + $number);
}
@else {
@if index('e' '.' ',' ']' '}' ' ', $token) {
@return $pointer, if($result != null, $result / $runs, $result);
}
@return _throw("Unexpected token `" + $token + "`.", $pointer);
}
$pointer: $pointer + 1;
}
@return $pointer, if($result != null, $result / $runs, $result);
}
// Parses a JSON encoded number to find the exponent part
// --------------------------------------------------------------------------------
// @param [string] $source: JSON complete source
// @param [number] $pointer: current pointer
// --------------------------------------------------------------------------------
// @throw "Unexpected token $token."
// --------------------------------------------------------------------------------
// @return [list|false] (new pointer, parsed number)
@function _find-exponent($source, $pointer) {
$length: str-length($source);
$strings: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9';
$numbers: 0 1 2 3 4 5 6 7 8 9;
$result: null;
$minus: false;
@while $pointer <= $length {
$token: to-lower-case(str-slice($source, $pointer, $pointer));
$index: index($strings, $token);
@if $token == 'e' {
// @continue;
}
@else if $token == '-' {
$minus: true;
}
@else if $token == '+' {
$minus: false;
}
@else if $index and $index > 0 {
$number: nth($numbers, $index);
$result: if($result == null, $number, $result * 10 + $number);
}
@else {
@if not index(" ", ", ", "]", "}", $token) {
@return _throw("Unexpected token `" + $token + "`.", $pointer);
}
@return $pointer, if($minus and $result != null, $result * -1, $result);
}
$pointer: $pointer + 1;
}
@return $pointer, if($minus and $result != null, $result * -1, $result);
}
// Parses a JSON encoded string
// --------------------------------------------------------------------------------
// @param [string] $source: JSON complete source
// @param [number] $pointer: current pointer
// --------------------------------------------------------------------------------
// @throw "Unterminated string."
// --------------------------------------------------------------------------------
// @return [list|false] (new pointer, parsed string)
@function _json-decode--string($source, $pointer) {
// Check for the end of the string
$temp: str-slice($source, $pointer);
$end: str-index($temp, '"');
$string: "";
// If no end is found
@if $end == 0 {
@return _throw("Unterminated string.", $pointer);
}
@else if $end > 1 {
$string: str-slice($temp, 1, $end - 1);
$string: _strip-token($string, "\r");
$string: _strip-token($string, "\n");
}
@return ($pointer + $end, $string);
}
// Parses a JSON encoded `true`
// --------------------------------------------------------------------------------
// @param [string] $source: JSON complete source
// @param [number] $pointer: current pointer
// --------------------------------------------------------------------------------
// @throw "Unexpected token `t`."
// --------------------------------------------------------------------------------
// @return [list|false] (new pointer, true)
@function _json-decode--true($source, $pointer) {
$length: str-length($source);
@if $length - $pointer < 2
or str-slice($source, $pointer, $pointer) != 'r'
or str-slice($source, $pointer + 1, $pointer + 1) != 'u'
or str-slice($source, $pointer + 2, $pointer + 2) != 'e' {
@return _throw("Unexpected token: `t`.", $pointer);
}
@return ($pointer + 3, true);
}
// Parses a JSON encoded `false`
// --------------------------------------------------------------------------------
// @param $source: JSON complete source
// @param $pointer: current pointer
// --------------------------------------------------------------------------------
// @throw "Unexpected token `f`."
// --------------------------------------------------------------------------------
// @return [list|false] (new pointer, false)
@function _json-decode--false($source, $pointer) {
$length: str-length($source);
@if $length - $pointer < 3
or str-slice($source, $pointer, $pointer) != 'a'
or str-slice($source, $pointer + 1, $pointer + 1) != 'l'
or str-slice($source, $pointer + 2, $pointer + 2) != 's'
or str-slice($source, $pointer + 3, $pointer + 3) != 'e' {
@return _throw("Unexpected token: `f`.", $pointer);
}
@return ($pointer + 4, false);
}
// Parses a JSON encoded `null`
// --------------------------------------------------------------------------------
// @param [string] $source: JSON complete source
// @param [number] $pointer: current pointer
// --------------------------------------------------------------------------------
// @throw "Unexpected token `n`."
// --------------------------------------------------------------------------------
// @return [list|false] (new pointer, null)
@function _json-decode--null($source, $pointer) {
$length: str-length($source);
@if $length - $pointer < 2
or str-slice($source, $pointer, $pointer) != 'u'
or str-slice($source, $pointer + 1, $pointer + 1) != 'l'
or str-slice($source, $pointer + 2, $pointer + 2) != 'l' {
@return _throw("Unexpected token: `n`.", $pointer);
}
@return ($pointer + 3, null);
}
// Parses a JSON encoded number
// --------------------------------------------------------------------------------
// @param [string] $source: JSON complete source
// @param [number] $pointer: current pointer
// --------------------------------------------------------------------------------
// @throw "Unexpected token $token."
// @throw "Unexpected end of stream."
// --------------------------------------------------------------------------------
// @return [list|false] (new pointer, parsed number)
@function _json-decode--number($source, $pointer) {
$pointer: $pointer - 1; // Move back pointer to begininng of number
$allowed: '-' '0' '1' '2' '3' '4' '5' '6' '7' '8' '9'; // Allowed characted to start with
$first: str-slice($source, $pointer, $pointer); // First character of the number
$minus: $first == '-'; // Is it negative?
// Early check for errors
@if not index($allowed, $first) {
@return _throw("Unexpected token `" + $first + "`.", $pointer);
}
// Find the integer part
$find-integer: _find-integer($source, $pointer);
$pointer: nth($find-integer, 1);
$result: nth($find-integer, 2);
@if not $result { // Error occured
@return $find-integer;
}
// Find digits
@if str-slice($source, $pointer, $pointer) == '.' {
$find-digits: _find-digits($source, $pointer);
$pointer: nth($find-digits, 1);
$digits: nth($find-digits, 2);
@if $digits == null { // Empty digits, throw error
@return _throw("Unexpected end of stream.", $pointer);
}
@else if $digits == false { // Error occured, return it
@return $find-digits;
}
$result: $result + $digits;
}
// Find exponent
@if to-lower-case(str-slice($source, $pointer, $pointer)) == 'e' {
$find-exponent: _find-exponent($source, $pointer);
$pointer: nth($find-exponent, 1);
$exponent: nth($find-exponent, 2);
@if $exponent == null { // Empty exponent, throw error
@return _throw("Unexpected end of stream.", $pointer);
}
@else if $exponent == false { // Error occured, return it
@return $find-exponent;
}
$result: $result * _pow(10, $exponent);
}
@return ($pointer, if($minus, $result * -1, $result));
}
// Parses a JSON encoded array
// --------------------------------------------------------------------------------
// @param [string] $source: JSON complete source
// @param [number] $pointer: current pointer
// --------------------------------------------------------------------------------
// @throw "Unexpected comma in array literal."
// @throw "Missing comma in array literal."
// @throw "Unterminated array literal."
// --------------------------------------------------------------------------------
// @return [list|false] (new pointer, parsed list)
@function _json-decode--list($source, $pointer) {
$length: str-length($source);
$list: ();
$needs-comma: false;
@if $pointer <= $length and str-slice($source, $pointer, $pointer) == "]" {
@return ($pointer + 1, $list);
}
@while $pointer <= $length {
$token: str-slice($source, $pointer, $pointer);
@if $token == "]" {
@if not $needs-comma and length($list) != 0 {
@return _throw("Unexpected comma in array literal.", $pointer);
}
// Do it the Sass way and destruct a single item array to an element.
@return ($pointer + 1, if(length($list) == 1, nth($list, 1), $list));
}
@else if $token == " " {
$pointer: $pointer + 1;
}
@else if $token == "," {
@if not $needs-comma {
@return _throw("Unexpected comma in array literal.", $pointer);
}
$needs-comma: false;
$pointer: $pointer + 1;
}
@else {
@if $needs-comma {
@return _throw("Missing comma in array literal.", $pointer);
}
$read: _json-decode--value($source, $pointer);
$pointer: nth($read, 1);
$list: append($list, nth($read, 2));
$needs-comma: true;
}
}
@return _throw("Unterminated array literal.", $pointer);
}
// Parses a JSON encoded object
// --------------------------------------------------------------------------------
// @param [string] $source: JSON complete source
// @param [number] $pointer: current pointer
// --------------------------------------------------------------------------------
// @throw "Unexpected comma in object literal."
// @throw "Unexpected token $token in object literal."
// @throw "Missing comma in object literal."
// @throw "Unterminated object literal."
// @throw "Consuming token `:` failed."
// --------------------------------------------------------------------------------
// @return [list|false] (new pointer, map)
@function _json-decode--map($source, $pointer) {
$length: str-length($source);
$map: ();
$needs-comma: false;
// Deal with empty map
@if $pointer <= $length and str-slice($source, $pointer, $pointer) == "}" {
@return ($pointer + 1, $map);
}
@while $pointer <= $length {
$token: str-slice($source, $pointer, $pointer);
$pointer: $pointer + 1;
@if $token == "}" {
@if not $needs-comma and length($map) != 0 {
@return _throw("Unexpected comma in object literal.", $pointer);
}
@return ($pointer + 1, $map);
}
@else if $token == " " {
// @continue;
}
@else if $token == "," {
@if not $needs-comma {
@return _throw("Unexpected comma in object literal.", $pointer);
}
$needs-comma: false;
}
@else if $token == '"' {
@if $needs-comma {
@return _throw("Missing comma in object literal.", $pointer);
}
// Read key
$read-key: _json-decode--string($source, $pointer);
$pointer: nth($read-key, 1);
$key: nth($read-key, 2);
// Remove colon
$pointer: _consume($source, $pointer, ":");
@if length($pointer) > 1 { // If consume has failed
@return _throw("Consuming token `:` failed.", 0);
}
// Read value
$read-value: _json-decode--value($source, $pointer);
$pointer: nth($read-value, 1);
$value: nth($read-value, 2);
// Add pair to map
$map: map-merge($map, ($key: $value));
$needs-comma: true;
}
@else {
@return _throw("Unexpected token `" + $token + "` in object literal.", $pointer);
}
}
@return _throw("Unterminated object literal.", $pointer);
}
// Parse a JSON string
// --------------------------------------------------------------------------------
// @param $json: JSON string to parse
// --------------------------------------------------------------------------------
// @throw "Input string may not be null"
// --------------------------------------------------------------------------------
// @return [literal|false]
@function json-decode($json) {
$length: str-length($json);
$pointer: 1;
$value: null;
@if $json == null {
@return _throw("Input string may not be null.", $pointer);
}
@while $value != false // Stop if error
and $pointer <= $length {
$read: _json-decode--value($json, $pointer);
$pointer: nth($read, 1);
$value: nth($read, 2);
}
@return $value;
}
// Proof quote a value
// --------------------------------------------------------------------------------
// @param $value: value to be quoted
// --------------------------------------------------------------------------------
// @return [string] quoted value
@function _proof-quote($value) {
@return '"' + $value + '"';
}
// Encode a bool to JSON
// --------------------------------------------------------------------------------
// @param $bool: bool to be encoded
// --------------------------------------------------------------------------------
// @return [bool] boolean
@function _json-encode--bool($boolean) {
@return $boolean;
}
// Encode a color to JSON
// --------------------------------------------------------------------------------
// @param $color: color to be encoded
// --------------------------------------------------------------------------------
// @return [string] encoded color
@function _json-encode--color($color) {
@return _proof-quote($color);
}
// Encode a list to JSON
// --------------------------------------------------------------------------------
// @param $list: list to be encoded
// --------------------------------------------------------------------------------
// @return [string] encoded list
@function _json-encode--list($list) {
$str: "";
@each $item in $list {
$str: $str + ', ' + json-encode($item);
}
@return '[' + str-slice($str, 3) + ']';
}
// Encode a map to JSON
// --------------------------------------------------------------------------------
// @param $map: map to be encoded
// --------------------------------------------------------------------------------
// @return [string] encoded map
@function _json-encode--map($map) {
$str: "";
@each $key, $value in $map {
$str: $str + ', ' + _proof-quote($key) + ': ' + json-encode($value);
}
@return '{' + str-slice($str, 3) + '}';
}
// Encode a number to JSON
// --------------------------------------------------------------------------------
// @param $number: number to be encoded
// --------------------------------------------------------------------------------
// @return [string] encoded number
@function _json-encode--number($number) {
@return if(unitless($number), $number, _proof-quote($number));
}
// Encode a string to JSON
// --------------------------------------------------------------------------------
// @param $string: string to be encoded
// --------------------------------------------------------------------------------
// @return [string] encoded string
@function _json-encode--string($string) {
@return _proof-quote($string);
}
// Encode `null` to JSON
// --------------------------------------------------------------------------------
// @param $null: `null`
// --------------------------------------------------------------------------------
// @return [string] `null`
@function _json-encode--null($null) {
@return "null";
}
// JSON.stringify a value and pass it as a font-family of head element
// --------------------------------------------------------------------------------
// @param $value: value to be stringified
@mixin json-encode($value) {
head {
font-family: json-encode($value);
}
}
// Delay the encoding of ta literal to JSON
// to a type-specific method
// --------------------------------------------------------------------------------
// @param $value: value to be stringified
// --------------------------------------------------------------------------------
// @throw "Unknown type for $value ({x})."
// --------------------------------------------------------------------------------
// @return [string|false] JSON encoded string
@function json-encode($value) {
$types: list, map, number, string, bool, color;
$value-type: type-of($value);
@if $value-type != null or index($types, $value-type) {
@return call('_json-encode--#{$value-type}', $value);
}
@warn "Unknown type for #{$value} (#{type-of($value)}).";
@return false;
}
// SassyControles
@function strip-unit($number) {
@return $number / ($number * 0 + 1);
}
$controll-data-map : ( );
@mixin controllable($property, $value, $options: ()){
$selector: #{&};
$value-type: type-of($value);
$options: map-merge($options, (type: $value-type));
$type-options: ();
@if $value-type == number{
$type-options: (value: strip-unit($value), unit: unit($value));
} @else {
$type-options: (value: $value);
}
$options: map-merge($options, $type-options);
$property-options-map: (#{$property}: $options);
@if map-has-key($controll-data-map, $selector){
$property-options-map : map-merge(map-get($controll-data-map, $selector), $property-options-map);
}
$controll-data-map: map-merge($controll-data-map, (#{$selector}: $property-options-map) );
#{$property}: $value;
}
.test{
@include controllable('width', 50px, (min: 0, max: 500));
@include controllable('height', 50px, (min: 0, max: 100));
@include controllable('background-color', #f00);
}
body:before{
display:none;
content:json-encode($controll-data-map);
}
/*! sassyjson - v0.0.2 - 2014-01-17 */
.test {
width: 50px;
height: 50px;
background-color: red;
}
body:before {
display: none;
content: '{".test": {"width": {"min": 0, "max": 500, "type": "number", "value": 50, "unit": "px"}, "height": {"min": 0, "max": 100, "type": "number", "value": 50, "unit": "px"}, "background-color": {"type": "color", "value": "red"}}}';
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment