215 lines
5.6 KiB
JavaScript
215 lines
5.6 KiB
JavaScript
/*!
|
|
* R2 - a CSS LTR ∞ RTL converter
|
|
* Copyright Dustin Diaz 2012
|
|
* https://github.com/ded/r2
|
|
* License MIT
|
|
*/
|
|
|
|
var fs = require('fs')
|
|
, css = require('css');
|
|
|
|
function quad(v, m) {
|
|
// 1px 2px 3px 4px => 1px 4px 3px 2px
|
|
if ((m = v.trim().split(/\s+/)) && m.length == 4) {
|
|
return [m[0], m[3], m[2], m[1]].join(' ')
|
|
}
|
|
return v
|
|
}
|
|
|
|
function quad_radius(v) {
|
|
var m = v.trim().split(/\s+/)
|
|
// 1px 2px 3px 4px => 1px 2px 4px 3px
|
|
// since border-radius: top-left top-right bottom-right bottom-left
|
|
// will be border-radius: top-right top-left bottom-left bottom-right
|
|
if (m && m.length == 4) {
|
|
return [m[1], m[0], m[3], m[2]].join(' ')
|
|
} else if (m && m.length == 3) {
|
|
// super odd how this works
|
|
// 5px 10px 20px => 10px 5px 10px 20px
|
|
// yeah it's pretty dumb
|
|
return [m[1], m[0], m[1], m[2]].join(' ')
|
|
}
|
|
return v
|
|
}
|
|
|
|
function direction(v) {
|
|
return v.match(/ltr/) ? 'rtl' : v.match(/rtl/) ? 'ltr' : v
|
|
}
|
|
|
|
function rtltr(v) {
|
|
if (v.match(/left/)) return 'right'
|
|
if (v.match(/right/)) return 'left'
|
|
return v
|
|
}
|
|
|
|
function bgPosition(values) {
|
|
return values.split(/\s*,\s*/g).map(function (v) {
|
|
if (v.match(/\bleft\b/)) {
|
|
v = v.replace(/\bleft\b/, 'right')
|
|
} else if (v.match(/\bright\b/)) {
|
|
v = v.replace(/\bright\b/, 'left')
|
|
}
|
|
|
|
var m = v.trim().split(/\s+/)
|
|
if (m && (m.length == 1) && v.match(/(\d+)([a-z]{2}|%)/)) {
|
|
v = 'right ' + v
|
|
}
|
|
|
|
if (m && m.length == 2 && m[0].match(/\d+%/)) {
|
|
// 30% => 70% (100 - x)
|
|
v = (100 - parseInt(m[0], 10)) + '% ' + m[1]
|
|
}
|
|
|
|
return v
|
|
}).join(', ')
|
|
}
|
|
|
|
var propertyMap = {
|
|
'margin-left': 'margin-right'
|
|
, 'margin-right': 'margin-left'
|
|
|
|
, 'padding-left': 'padding-right'
|
|
, 'padding-right': 'padding-left'
|
|
|
|
, 'border-left': 'border-right'
|
|
, 'border-right': 'border-left'
|
|
|
|
, 'border-left-color': 'border-right-color'
|
|
, 'border-right-color': 'border-left-color'
|
|
|
|
, 'border-left-width': 'border-right-width'
|
|
, 'border-right-width': 'border-left-width'
|
|
|
|
, 'border-radius-bottomleft': 'border-radius-bottomright'
|
|
, 'border-radius-bottomright': 'border-radius-bottomleft'
|
|
, 'border-bottom-right-radius': 'border-bottom-left-radius'
|
|
, 'border-bottom-left-radius': 'border-bottom-right-radius'
|
|
, '-webkit-border-bottom-right-radius': '-webkit-border-bottom-left-radius'
|
|
, '-webkit-border-bottom-left-radius': '-webkit-border-bottom-right-radius'
|
|
, '-moz-border-radius-bottomright': '-moz-border-radius-bottomleft'
|
|
, '-moz-border-radius-bottomleft': '-moz-border-radius-bottomright'
|
|
|
|
, 'border-radius-topleft': 'border-radius-topright'
|
|
, 'border-radius-topright': 'border-radius-topleft'
|
|
, 'border-top-right-radius': 'border-top-left-radius'
|
|
, 'border-top-left-radius': 'border-top-right-radius'
|
|
, '-webkit-border-top-right-radius': '-webkit-border-top-left-radius'
|
|
, '-webkit-border-top-left-radius': '-webkit-border-top-right-radius'
|
|
, '-moz-border-radius-topright': '-moz-border-radius-topleft'
|
|
, '-moz-border-radius-topleft': '-moz-border-radius-topright'
|
|
|
|
, 'left': 'right'
|
|
, 'right': 'left'
|
|
}
|
|
|
|
var valueMap = {
|
|
'padding': quad,
|
|
'margin': quad,
|
|
'text-align': rtltr,
|
|
'float': rtltr,
|
|
'clear': rtltr,
|
|
'direction': direction,
|
|
'-webkit-border-radius': quad_radius,
|
|
'-moz-border-radius': quad_radius,
|
|
'border-radius': quad_radius,
|
|
'border-color': quad,
|
|
'border-width': quad,
|
|
'border-style': quad,
|
|
'background-position': bgPosition
|
|
}
|
|
|
|
function processRule(rule, idx, list) {
|
|
var prev = list[idx-1]
|
|
if (prev && prev.type === 'comment' && prev.comment.trim() === '@noflip')
|
|
return;
|
|
|
|
if (rule.declarations)
|
|
rule.declarations.forEach(processDeclaration)
|
|
else if (rule.rules)
|
|
rule.rules.forEach(processRule)
|
|
}
|
|
|
|
function processDeclaration(declaration) {
|
|
// Ignore comments in declarations
|
|
if (declaration.type !== 'declaration')
|
|
return
|
|
|
|
var prop = declaration.property
|
|
, val = declaration.value
|
|
, important = /!important/
|
|
, isImportant = val.match(important)
|
|
, asterisk = prop.match(/^(\*+)(.+)/, '')
|
|
|
|
if (asterisk) {
|
|
prop = asterisk[2]
|
|
asterisk = asterisk[1]
|
|
} else {
|
|
asterisk = ''
|
|
}
|
|
prop = propertyMap[prop] || prop
|
|
val = valueMap[prop] ? valueMap[prop](val) : val
|
|
|
|
if (!val.match(important) && isImportant) val += '!important'
|
|
|
|
declaration.property = asterisk + prop;
|
|
declaration.value = val;
|
|
}
|
|
|
|
function r2(cssString, options) {
|
|
var ast
|
|
if (!options)
|
|
options = { compress: true }
|
|
|
|
ast = css.parse(cssString)
|
|
ast.stylesheet.rules.forEach(processRule)
|
|
|
|
return css.stringify(ast, options)
|
|
}
|
|
|
|
module.exports.exec = function (args) {
|
|
var out
|
|
, read = args[0]
|
|
, out = args[1]
|
|
, options = { compress: args[2] !== '--no-compress' }
|
|
, data
|
|
|
|
/*
|
|
/ If no read arg then read from stdin
|
|
/ allows for standard piping and reading in
|
|
/ ex: lessc file.less | r2 > file-rtl.css
|
|
/ ex: r2 < file.css
|
|
*/
|
|
if (!read) {
|
|
var buffer = ''
|
|
process.stdin.resume()
|
|
process.stdin.setEncoding('utf8')
|
|
|
|
process.stdin.on('data', function(chunk) {
|
|
buffer += chunk
|
|
});
|
|
|
|
process.stdin.on('end', function() {
|
|
if (buffer) {
|
|
console.log(r2(buffer, options))
|
|
}
|
|
});
|
|
} else {
|
|
/*
|
|
/ If reading from a file then print to stdout or out arg
|
|
/ To stdout: r2 styles.css
|
|
/ To file: r2 styles.cc styles-rtl.css
|
|
*/
|
|
data = fs.readFileSync(read, 'utf8')
|
|
if (out) {
|
|
console.log('Swapping ' + read + ' to ' + out + '...')
|
|
fs.writeFileSync(out, r2(data, options), 'utf8')
|
|
} else {
|
|
console.log(r2(data, options))
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports.swap = function (cssString, options) {
|
|
return r2(cssString, options)
|
|
}
|