├── .gitignore ├── .jshintignore ├── .jshintrc ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── bin └── rtl-css.js ├── config.json ├── lib └── index.js ├── package.json └── test ├── fixtures ├── input.css ├── output-ltr.css └── output-rtl.css └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lib-cov/ 3 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | node_modules/ -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true 3 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | - 0.12 5 | sudo: false # Use container-based architecture 6 | env: 7 | - 8 | - COVERALLS=1 9 | matrix: 10 | exclude: 11 | - node_js: 0.10 12 | env: COVERALLS=1 13 | script: 14 | - make test 15 | - make lint 16 | after_success: 17 | - if [ "x$COVERALLS" = "x1" ]; then npm install jscoverage; fi 18 | - if [ "x$COVERALLS" = "x1" ]; then npm install mocha-lcov-reporter; fi 19 | - if [ "x$COVERALLS" = "x1" ]; then npm install coveralls; fi 20 | - if [ "x$COVERALLS" = "x1" ]; then make test-coveralls; fi 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | ----------- 3 | 4 | Copyright (C) 2015 Yuriy Nasretdinov 5 | and other rtl-css contributors. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: npm-install 2 | 3 | npm-install: node_modules/npm-install-stamp 4 | 5 | node_modules/npm-install-stamp: 6 | npm install 7 | touch ./node_modules/npm-install-stamp 8 | 9 | clean: 10 | rm -rf ./node_modules 11 | 12 | test: npm-install 13 | ./node_modules/.bin/mocha test/test.js -R spec 14 | 15 | test-coveralls: npm-install 16 | rm -rf ./lib-cov && ./node_modules/.bin/jscoverage lib lib-cov 17 | LIB_COV=1 ./node_modules/.bin/mocha test/test.js -R mocha-lcov-reporter | ./node_modules/coveralls/bin/coveralls.js 18 | 19 | lint: npm-install 20 | ./node_modules/.bin/jshint . --show-non-errors 21 | 22 | .PHONY: all npm-install clean test test-coveralls 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Utility for converting CSS files using external rules 2 | 3 | [![NPM version][NPMVI]][NPMVURL] [![Build statusS][BSI]][BSURL] [![Code coverage][CSI]][CSURL] 4 | 5 | [NPMVI]: https://badge.fury.io/js/rtl-css.png 6 | [NPMVURL]: http://badge.fury.io/js/rtl-css 7 | [BSI]: https://secure.travis-ci.org/badoo/rtl-css.png?branch=master 8 | [BSURL]: http://travis-ci.org/badoo/rtl-css 9 | [CSI]: https://coveralls.io/repos/badoo/rtl-css/badge.png 10 | [CSURL]: https://coveralls.io/r/badoo/rtl-css 11 | 12 | ----- 13 | 14 | An example config.json contains rules for RTL conversion 15 | 16 | Usage example: `node bin/rtl-css.js -i ./test/fixtures/input.css -c config.json -d rtl` 17 | 18 | Config format: 19 | 20 | ## properties: 21 | 22 | Replacements for property names in format `{ old_name: new_name }`, for example `{ "left": "right" }` 23 | 24 | ## values: 25 | 26 | Replacement patterns in format `property_name: old_value_pattern = new_value_pattern` 27 | 28 | Value patterns are defined in format `%i` or using direct value. 29 | 30 | For example: 31 | 32 | `float: left = right` 33 | 34 | This rule will convert `float: left` to `float: right` 35 | 36 | Another, more sophisticated example: 37 | 38 | `box-shadow: %1 %2 inset = -%1 %2 inset` 39 | 40 | This rule will convert `box-shadow: 1px 2px inset` to `box-shadow: -1px 2px inset` 41 | 42 | ## options: 43 | 44 | You can also specify prefixes for properties and suffixes to values that will be ignored. For example, `//` is an IE hack that should be ignored when rules are applied, as well as `\9` in the value end. 45 | -------------------------------------------------------------------------------- /bin/rtl-css.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /*! 4 | * Copyright (C) 2015 by Yuriy Nasretdinov 5 | * 6 | * See license text in LICENSE file 7 | */ 8 | 9 | var ArgumentParser = require('argparse').ArgumentParser; 10 | var parser = new ArgumentParser({ 11 | prog: "proto2json", 12 | description: require('../package.json').description, 13 | version: require('../package.json').version, 14 | addHelp: true 15 | }); 16 | parser.addArgument( 17 | [ '-i', '--input' ], 18 | { 19 | action: 'store', 20 | defaultValue: '/dev/stdin', 21 | type: 'string', 22 | help: 'Input file. Default: read from /dev/stdin.' 23 | } 24 | ); 25 | parser.addArgument( 26 | [ '-o', '--output' ], 27 | { 28 | action: 'store', 29 | defaultValue: '/dev/stdout', 30 | help: 'Output file. Default: write to /dev/stdout.' 31 | } 32 | ); 33 | parser.addArgument( 34 | [ '-d', '--direction' ], 35 | { 36 | action: 'store', 37 | defaultValue: 'ltr', 38 | help: 'Language direction: ltr or rtl. Default: ltr.' 39 | } 40 | ); 41 | parser.addArgument( 42 | [ '-c', '--config' ], 43 | { 44 | action: 'store', 45 | defaultValue: false, 46 | help: 'Config file. Default: config that is used in Badoo.' 47 | } 48 | ); 49 | var args = parser.parseArgs(); 50 | 51 | var fs = require('fs'), path = require('path'); 52 | 53 | // Get config 54 | var default_config_name = path.join(path.dirname(fs.realpathSync(__filename)), '..', 'config.json'); 55 | var config_name = args.config ? args.config : default_config_name; 56 | var config = fs.readFileSync(config_name); 57 | if (!config) { 58 | console.log("Cannot read config from " + config_name); 59 | process.exit(1); 60 | } 61 | 62 | config = JSON.parse(config.toString()); 63 | 64 | // Read input 65 | var input = fs.readFileSync(args.input); 66 | if (!input) { 67 | console.log("Cannot read input from " + args.input); 68 | process.exit(1); 69 | } 70 | input = input.toString(); 71 | 72 | // Process 73 | var rtlCss = require('../'); 74 | var output = rtlCss.processCss(rtlCss.processConfig(config), args.direction, input); 75 | 76 | // Write output 77 | var fd = fs.openSync(args.output, 'w'); 78 | if (!fd) { 79 | console.log("Cannot open output file " + args.output); 80 | process.exit(1); 81 | } 82 | 83 | var result = fs.writeSync(fd, output); 84 | if (!result) { 85 | console.log("Cannot write output to " + args.output); 86 | process.exit(1); 87 | } 88 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "left": "right", 4 | "right": "left", 5 | "margin-left": "margin-right", 6 | "margin-right": "margin-left", 7 | "padding-left": "padding-right", 8 | "padding-right": "padding-left", 9 | "border-left": "border-right", 10 | "border-right": "border-left", 11 | "border-left-width": "border-right-width", 12 | "border-right-width": "border-left-width", 13 | "border-left-style": "border-right-style", 14 | "border-right-style": "border-left-style", 15 | "border-left-color": "border-right-color", 16 | "border-right-color": "border-left-color", 17 | "border-top-left-radius": "border-top-right-radius", 18 | "border-top-right-radius": "border-top-left-radius", 19 | "border-bottom-left-radius": "border-bottom-right-radius", 20 | "border-bottom-right-radius": "border-bottom-left-radius" 21 | }, 22 | "values": [ 23 | "direction: ltr = rtl", 24 | "direction: rtl = ltr", 25 | "float: left = right", 26 | "float: right = left", 27 | "clear: left = right", 28 | "clear: right = left", 29 | "text-align: left = right", 30 | "text-align: right = left", 31 | "margin: %1 %2 %3 %4 = %1 %4 %3 %2", 32 | "padding: %1 %2 %3 %4 = %1 %4 %3 %2", 33 | "border-width: %1 %2 %3 %4 = %1 %4 %3 %2", 34 | "border-style: %1 %2 %3 %4 = %1 %4 %3 %2", 35 | "border-color: %1 %2 %3 %4 = %1 %4 %3 %2", 36 | "border-radius: %1 %2 = %2 %1", 37 | "border-radius: %1 %2 %3 = %2 %1 %2 %3", 38 | "border-radius: %1 %2 %3 %4 = %2 %1 %4 %3", 39 | "box-shadow: %1 %2 = -%1 %2", 40 | "box-shadow: %1 %2 inset = -%1 %2 inset", 41 | "box-shadow: inset %1 %2 = inset -%1 %2", 42 | "box-shadow: %1 %2 %3 = -%1 %2 %3", 43 | "box-shadow: %1 %2 %3 inset = -%1 %2 %3 inset", 44 | "box-shadow: inset %1 %2 %3 = inset -%1 %2 %3", 45 | "box-shadow: %1 %2 %3 %4 = -%1 %2 %3 %4", 46 | "box-shadow: %1 %2 %3 %4 inset = -%1 %2 %3 %4 inset", 47 | "box-shadow: inset %1 %2 %3 %4 = inset -%1 %2 %3 %4", 48 | "box-shadow: %1 %2 %3 %4 %5 = -%1 %2 %3 %4 %5", 49 | "box-shadow: %1 %2 %3 %4 %5 inset = -%1 %2 %3 %4 %5 inset", 50 | "box-shadow: inset %1 %2 %3 %4 %5 = inset -%1 %2 %3 %4 %5", 51 | "-moz-box-shadow: %1 %2 = -%1 %2", 52 | "-moz-box-shadow: %1 %2 inset = -%1 %2 inset", 53 | "-moz-box-shadow: inset %1 %2 = inset -%1 %2", 54 | "-moz-box-shadow: %1 %2 %3 = -%1 %2 %3", 55 | "-moz-box-shadow: %1 %2 %3 inset = -%1 %2 %3 inset", 56 | "-moz-box-shadow: inset %1 %2 %3 = inset -%1 %2 %3", 57 | "-moz-box-shadow: %1 %2 %3 %4 = -%1 %2 %3 %4", 58 | "-moz-box-shadow: %1 %2 %3 %4 inset = -%1 %2 %3 %4 inset", 59 | "-moz-box-shadow: inset %1 %2 %3 %4 = inset -%1 %2 %3 %4", 60 | "-moz-box-shadow: %1 %2 %3 %4 %5 = -%1 %2 %3 %4 %5", 61 | "-moz-box-shadow: %1 %2 %3 %4 %5 inset = -%1 %2 %3 %4 %5 inset", 62 | "-moz-box-shadow: inset %1 %2 %3 %4 %5 = inset -%1 %2 %3 %4 %5", 63 | "-webkit-box-shadow: %1 %2 = -%1 %2", 64 | "-webkit-box-shadow: %1 %2 inset = -%1 %2 inset", 65 | "-webkit-box-shadow: inset %1 %2 = inset -%1 %2", 66 | "-webkit-box-shadow: %1 %2 %3 = -%1 %2 %3", 67 | "-webkit-box-shadow: %1 %2 %3 inset = -%1 %2 %3 inset", 68 | "-webkit-box-shadow: inset %1 %2 %3 = inset -%1 %2 %3", 69 | "-webkit-box-shadow: %1 %2 %3 %4 = -%1 %2 %3 %4", 70 | "-webkit-box-shadow: %1 %2 %3 %4 inset = -%1 %2 %3 %4 inset", 71 | "-webkit-box-shadow: inset %1 %2 %3 %4 = inset -%1 %2 %3 %4", 72 | "-webkit-box-shadow: %1 %2 %3 %4 %5 = -%1 %2 %3 %4 %5", 73 | "-webkit-box-shadow: %1 %2 %3 %4 %5 inset = -%1 %2 %3 %4 %5 inset", 74 | "-webkit-box-shadow: inset %1 %2 %3 %4 %5 = inset -%1 %2 %3 %4 %5", 75 | "text-shadow: %1 %2 = -%1 %2", 76 | "text-shadow: %1 %2 %3 = -%1 %2 %3", 77 | "text-shadow: %1 %2 %3 %4 = -%1 %2 %3 %4" 78 | ], 79 | "options": { 80 | "prefix": ["//"], 81 | "suffix": ["!important", "\\9"] 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2015 by Yuriy Nasretdinov 3 | * 4 | * See license text in LICENSE file 5 | */ 6 | 7 | (function () { 8 | var RTL_BEGIN = "/* @rtl begin */"; 9 | var RTL_END = "/* @rtl end */"; 10 | 11 | var LTR_BEGIN = "/* @ltr begin */"; 12 | var LTR_END = "/* @ltr end */"; 13 | 14 | var NOFLIP_BEGIN = "/* @noflip begin */"; 15 | var NOFLIP_END = "/* @noflip end */"; 16 | 17 | var SECTIONS_MARKERS = [ 18 | [NOFLIP_BEGIN, NOFLIP_END], 19 | [RTL_BEGIN, RTL_END] 20 | ]; 21 | 22 | function trim(str) { 23 | return str.replace(/(^\s+|\s+$)/g, ''); 24 | } 25 | 26 | // parse rules object and form the config that is usable for processing 27 | function processRTLConfig(config) { 28 | var properties = config.properties; 29 | if (!properties) { 30 | throw new Error("Invalid config: section 'properties' is missing"); 31 | } 32 | 33 | var values = config.values; 34 | if (!values) { 35 | throw new Error("Invalid config: section 'values' is missing"); 36 | } 37 | 38 | var dynamic = {}; 39 | for (var i = 0; i < values.length; i++) { 40 | var elem = values[i]; 41 | var elem_parts = elem.split('=', 2); 42 | var key = elem_parts[0]; 43 | var replace = elem_parts[1]; 44 | 45 | var key_parts = key.split(':', 2); 46 | if (key_parts.length != 2) { 47 | throw new Error("Invalid config: incorrect 'values' rule: ':' expected in key for " + i); 48 | } 49 | 50 | var rule = trim(key_parts[0]); 51 | var pattern_parts = trim(key_parts[1]).split(/\s+/g); 52 | 53 | if (!dynamic[rule]) dynamic[rule] = {}; 54 | if (!dynamic[rule][pattern_parts.length]) dynamic[rule][pattern_parts.length] = []; 55 | 56 | dynamic[rule][pattern_parts.length].push({pattern: pattern_parts, replace: trim(replace)}); 57 | } 58 | 59 | return {dynamic: dynamic, options: config.options, properties: config.properties}; 60 | } 61 | 62 | /* 63 | * For LTR we do not need to do any automatic replaces, we just need to get rid of sections between 64 | * RTL_BEGIN and RTL_END 65 | * 66 | * algorithm does not use regular expressions to archieve maximum perfomance 67 | */ 68 | function processLTR(contents) { 69 | var parts = contents.split(RTL_BEGIN); 70 | var response = [parts[0]]; 71 | for (var i = 1; i < parts.length; i++) { 72 | var v = parts[i]; 73 | var end_pos = v.indexOf(RTL_END); 74 | if (end_pos < 0) continue; 75 | response.push(v.substr(end_pos + RTL_END.length)); 76 | } 77 | return response.join('').replace(LTR_BEGIN, '').replace(LTR_END, '').replace(NOFLIP_BEGIN, '').replace(NOFLIP_END, ''); 78 | } 79 | 80 | /* 81 | * For RTL we need to get rid of sections between LTR_BEGIN and LTR_END and apply flip-replaces. 82 | * These replaces must ignore sections between NOFLIP_BEGIN and NOFLIP_END as well as RTL_BEGIN and RTL_END 83 | */ 84 | function processCss(config, direction, contents) { 85 | if (direction == 'ltr') { 86 | return processLTR(contents); 87 | } 88 | 89 | var i, j, v, end_pos, raw_cnt = 0; 90 | var parts = contents.split(LTR_BEGIN); 91 | var response_arr = [parts[0]]; 92 | for (i = 1; i < parts.length; i++) { 93 | v = parts[i]; 94 | end_pos = v.indexOf(LTR_END); 95 | if (end_pos < 0) continue; 96 | response_arr.push(v.substr(end_pos + LTR_END.length)); 97 | } 98 | 99 | /* sections that do not need to be replaced */ 100 | var raw_sections = { 101 | // key => value 102 | }; 103 | 104 | for (i = 0; i < SECTIONS_MARKERS.length; i++) { 105 | var m = SECTIONS_MARKERS[i]; 106 | parts = response_arr.join('').split(m[0]); 107 | response_arr = [parts[0]]; 108 | for (j = 1; j < parts.length; j++) { 109 | v = parts[j]; 110 | end_pos = v.indexOf(m[1]); 111 | if (end_pos < 0) continue; 112 | var raw_v = v.substr(0, end_pos); 113 | var raw_k = 'raw_' + (raw_cnt++) + ';'; 114 | raw_sections[raw_k] = raw_v; 115 | response_arr.push(raw_k); 116 | response_arr.push(v.substr(end_pos + m[1].length)); 117 | } 118 | } 119 | 120 | var response = processRTL(config, response_arr.join('')); 121 | 122 | for (var from in raw_sections) { 123 | response = response.replace(from, raw_sections[from]); 124 | } 125 | 126 | return response; 127 | } 128 | 129 | function processSimpleRTLRule(value_raw, patterns, value_suffixes) { 130 | var value = value_raw.replace(/\\s+$/g, ''); 131 | var k, v, i; 132 | 133 | // cut suffix from values 134 | 135 | for (k in value_suffixes) { 136 | var suffix = value_suffixes[k]; 137 | if (value.indexOf(suffix, value.length - suffix.length) !== -1) { 138 | value = value.substr(0, -suffix.length); 139 | value = value.replace(/\\s+$/g, ''); 140 | } 141 | } 142 | 143 | var len = value.length - value_raw.length; 144 | var value_suffix = len ? value_raw.substr(len) : ''; 145 | var ltrimmed = value.replace(/^\s+/g, ''); 146 | 147 | len = value.length - ltrimmed.length; 148 | var value_prefix = len ? value.substr(0, len) : ''; 149 | 150 | var value_parts = ltrimmed.split(/\s+/g); 151 | 152 | if (!patterns[value_parts.length]) { 153 | return false; 154 | } 155 | 156 | var my_patterns = patterns[value_parts.length]; 157 | 158 | upper_loop: 159 | for (k = 0; k < my_patterns.length; k++) { 160 | var pat = my_patterns[k]; 161 | var m = {}; // match 162 | for (i = 0; i < pat.pattern.length; i++) { 163 | v = pat.pattern[i]; 164 | if (v.charAt(0) != '%') { 165 | if (value_parts[i] !== v) { 166 | continue upper_loop; 167 | } 168 | } else { 169 | m[v] = value_parts[i]; 170 | } 171 | } 172 | 173 | var result = pat.replace; 174 | for (var k_m in m) { 175 | result = result.replace(k_m, m[k_m]); 176 | } 177 | 178 | return value_prefix + result.replace('--', '').replace('-0', '0') + value_suffix; 179 | } 180 | 181 | return false; 182 | } 183 | 184 | // Process RTL rules for contents 185 | function processRTL(config, contents) { 186 | var rules = contents.split(';'); 187 | var response_arr = []; 188 | 189 | var dynamic = config.dynamic; 190 | var property_rules = config.properties; 191 | var key_prefixes = config.options.prefix; 192 | var value_suffixes = config.options.suffix; 193 | 194 | var last_part = rules.pop(); 195 | var rule, value, k, v, i; 196 | 197 | var replaced_expressions = {}; 198 | var idx = 0; 199 | var replaceFunc = function (str) { 200 | var transform = '_replaced_value_' + (idx++) + '_'; 201 | replaced_expressions[transform] = str; 202 | return transform; 203 | }; 204 | 205 | for (i in rules) { 206 | var part = rules[i]; 207 | var last_brace = part.lastIndexOf('{'); 208 | if (last_brace < 0) { 209 | rule = part; 210 | } else { 211 | response_arr.push(part.substr(0, last_brace + 1)); 212 | rule = part.substr(last_brace + 1); 213 | } 214 | 215 | // parse "rule: value", ignore other parts 216 | var rule_parts = rule.split(':', 2); 217 | var key = rule_parts[0]; 218 | for (k in key_prefixes) { 219 | key = key.replace(k, key_prefixes[k]); 220 | } 221 | 222 | if (key.indexOf('/*') >= 0) { 223 | key = key.replace(/\/\*.*?\*\//g, '', key); 224 | } 225 | 226 | key = trim(key); 227 | 228 | if (rule_parts.length < 2 || !dynamic[key] && !property_rules[key]) { 229 | response_arr.push(rule); 230 | response_arr.push(';'); 231 | continue; 232 | } 233 | 234 | var key_raw = rule_parts[0], value_raw = rule_parts[1]; 235 | 236 | if (property_rules[key]) { 237 | response_arr.push(key_raw.replace(key, property_rules[key])); 238 | response_arr.push(':'); 239 | response_arr.push(value_raw); 240 | response_arr.push(';'); 241 | continue; 242 | } 243 | 244 | var patterns = dynamic[key]; 245 | 246 | if (value_raw.indexOf(',') < 0 && value_raw.indexOf("(") < 0) { // simple rule 247 | value = processSimpleRTLRule(value_raw, patterns, value_suffixes); 248 | if (!value) { 249 | response_arr.push(rule); 250 | response_arr.push(';'); 251 | continue; 252 | } 253 | } else { // complex rule with "(...)" and "," 254 | replaced_expressions = {}; 255 | idx = 0; 256 | 257 | value = value_raw.replace( 258 | /\([^)]+\)/g, 259 | replaceFunc 260 | ); 261 | 262 | var value_components = value.split(','); 263 | for (k = 0; k < value_components.length; k++) { 264 | v = processSimpleRTLRule(value_components[k], patterns, value_suffixes); 265 | if (v) { 266 | value_components[k] = v; 267 | } 268 | } 269 | 270 | value = value_components.join(','); 271 | for (k in replaced_expressions) { 272 | value = value.replace(k, replaced_expressions[k]); 273 | } 274 | } 275 | 276 | response_arr.push(key_raw); 277 | response_arr.push(':'); 278 | response_arr.push(value); 279 | response_arr.push(';'); 280 | } 281 | 282 | response_arr.push(last_part); 283 | 284 | return response_arr.join(''); 285 | } 286 | 287 | exports.processConfig = processRTLConfig; 288 | exports.processCss = processCss; 289 | })(); 290 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rtl-css", 3 | "version": "1.0.0", 4 | "description": "Utility for converting CSS files using external rules", 5 | "main": "lib/index.js", 6 | "bin": { 7 | "rtl-css": "./bin/rtl-css.js" 8 | }, 9 | "scripts": { 10 | "test": "make test" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/badoo/rtl-css.git" 15 | }, 16 | "keywords": [ 17 | "CSS", 18 | "RTL", 19 | "LTR", 20 | "transformation" 21 | ], 22 | "dependencies": { 23 | "argparse": "~0.1.16" 24 | }, 25 | "devDependencies" : { 26 | "mocha" : "~2.2.1", 27 | "jshint" : "~2.6.3" 28 | }, 29 | "author": "Yuriy Nasretdinov", 30 | "license": "MIT" 31 | } 32 | -------------------------------------------------------------------------------- /test/fixtures/input.css: -------------------------------------------------------------------------------- 1 | ololo 2 | { left: 1px; /* comment */ float: left; margin: 1px rgba(1, 2) 2px 3px; } 3 | /* @rtl begin */ 4 | { Test RTL } 5 | /* @rtl end */ 6 | selector {between: rtl; and: noflip} 7 | /* @noflip begin */ 8 | _noflip_ 9 | /* @noflip end */ 10 | after noflip 11 | { some_rule: some_value; } 12 | -------------------------------------------------------------------------------- /test/fixtures/output-ltr.css: -------------------------------------------------------------------------------- 1 | ololo 2 | { left: 1px; /* comment */ float: left; margin: 1px rgba(1, 2) 2px 3px; } 3 | 4 | selector {between: rtl; and: noflip} 5 | 6 | _noflip_ 7 | 8 | after noflip 9 | { some_rule: some_value; } 10 | -------------------------------------------------------------------------------- /test/fixtures/output-rtl.css: -------------------------------------------------------------------------------- 1 | ololo 2 | { right: 1px; /* comment */ float: right; margin: 1px 3px 2px rgba(1, 2); } 3 | 4 | { Test RTL } 5 | 6 | selector {between: rtl; and: noflip} 7 | 8 | _noflip_ 9 | 10 | after noflip 11 | { some_rule: some_value; } 12 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2015 by Yuriy Nasretdinov 3 | * 4 | * See license text in LICENSE file 5 | */ 6 | 7 | /*global describe, it*/ 8 | 9 | "use strict"; 10 | 11 | var assert = require("assert"); 12 | var fs = require("fs"); 13 | 14 | var rtlCss = require(process.env.LIB_COV ? '../lib-cov' : '../'); 15 | 16 | var config = rtlCss.processConfig(JSON.parse(fs.readFileSync("config.json").toString())); 17 | var input = fs.readFileSync("test/fixtures/input.css").toString(); 18 | var output_ltr = fs.readFileSync("test/fixtures/output-ltr.css").toString(); 19 | var output_rtl = fs.readFileSync("test/fixtures/output-rtl.css").toString(); 20 | 21 | describe("rlt-css", function () { 22 | it("should properly convert file to ltr version", function () { 23 | assert.equal(output_ltr, rtlCss.processCss(config, 'ltr', input)); 24 | }); 25 | 26 | it("should properly convert file to rtl version", function () { 27 | assert.equal(output_rtl, rtlCss.processCss(config, 'rtl', input)); 28 | }); 29 | 30 | describe("config", function () { 31 | it("should contains 'properties' section", function () { 32 | assert.throws( 33 | function () { 34 | rtlCss.processConfig({values: {}}); 35 | }, 36 | Error, 37 | "section 'properties' is missing" 38 | ); 39 | }); 40 | 41 | it("should contains 'values' section", function () { 42 | assert.throws( 43 | function () { 44 | rtlCss.processConfig({properties: {}}); 45 | }, 46 | Error, 47 | "section 'values' is missing" 48 | ); 49 | }); 50 | 51 | it("should contains valid 'values' section", function () { 52 | assert.throws( 53 | function () { 54 | rtlCss.processConfig({ 55 | properties: {}, 56 | values: [ 57 | "direction ltr = rtl" 58 | ] 59 | }); 60 | }, 61 | Error, 62 | "incorrect 'values' rule" 63 | ); 64 | }); 65 | }); 66 | }); 67 | --------------------------------------------------------------------------------