123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 |
- /*
- * RTLCSS https://github.com/MohammadYounes/rtlcss
- * Framework for transforming Cascading Style Sheets (CSS) from Left-To-Right (LTR) to Right-To-Left (RTL).
- * Copyright 2017 Mohammad Younes.
- * Licensed under MIT <http://opensource.org/licenses/mit-license.php>
- * */
- 'use strict'
- var postcss = require('postcss')
- var state = require('./state.js')
- var config = require('./config.js')
- var util = require('./util.js')
- module.exports = postcss.plugin('rtlcss', function (options, plugins, hooks) {
- var configuration = config.configure(options, plugins, hooks)
- var context = {
- // provides access to postcss
- 'postcss': postcss,
- // provides access to the current configuration
- 'config': configuration,
- // provides access to utilities object
- 'util': util.configure(configuration)
- }
- return function (css, result) {
- var flipped = 0
- var toBeRenamed = {}
- context.config.hooks.pre(css, postcss)
- css.walk(function (node) {
- var prevent = false
- state.walk(function (current) {
- // check if current directive is expecting this node
- if (!current.metadata.blacklist && current.directive.expect[node.type]) {
- // perform action and prevent further processing if result equals true
- if (current.directive.begin(node, current.metadata, context)) {
- prevent = true
- }
- // if should end? end it.
- if (current.metadata.end && current.directive.end(node, current.metadata, context)) {
- state.pop(current)
- }
- }
- })
- if (prevent === false) {
- switch (node.type) {
- case 'atrule':
- // @rules requires url flipping only
- if (context.config.processUrls === true || context.config.processUrls.atrule === true) {
- var params = context.util.applyStringMap(node.params, true)
- node.params = params
- }
- break
- case 'comment':
- state.parse(node, result, function (current) {
- var push = true
- if (current.directive === null) {
- current.preserve = !context.config.clean
- context.util.each(context.config.plugins, function (plugin) {
- var blacklist = context.config.blacklist[plugin.name]
- if (blacklist && blacklist[current.metadata.name] === true) {
- current.metadata.blacklist = true
- if (current.metadata.end) {
- push = false
- }
- if (current.metadata.begin) {
- result.warn('directive "' + plugin.name + '.' + current.metadata.name + '" is blacklisted.', {node: current.source})
- }
- // break each
- return false
- }
- current.directive = plugin.directives.control[current.metadata.name]
- if (current.directive) {
- // break each
- return false
- }
- })
- }
- if (current.directive) {
- if (!current.metadata.begin && current.metadata.end) {
- if (current.directive.end(node, current.metadata, context)) {
- state.pop(current)
- }
- push = false
- } else if (current.directive.expect.self && current.directive.begin(node, current.metadata, context)) {
- if (current.metadata.end && current.directive.end(node, current.metadata, context)) {
- push = false
- }
- }
- } else if (!current.metadata.blacklist) {
- push = false
- result.warn('unsupported directive "' + current.metadata.name + '".', {node: current.source})
- }
- return push
- })
- break
- case 'decl':
- // if broken by a matching value directive .. break
- if (!context.util.each(context.config.plugins,
- function (plugin) {
- return context.util.each(plugin.directives.value, function (directive) {
- if (node.raws.value && node.raws.value.raw) {
- var expr = context.util.regexDirective(directive.name)
- if (expr.test(node.raws.value.raw)) {
- expr.lastIndex = 0
- if (directive.action(node, expr, context)) {
- if (context.config.clean) {
- node.value = node.raws.value.raw = context.util.trimDirective(node.raws.value.raw)
- }
- flipped++
- // break
- return false
- }
- }
- }
- })
- })) break
- // loop over all plugins/property processors
- context.util.each(context.config.plugins, function (plugin) {
- return context.util.each(plugin.processors, function (processor) {
- if (node.prop.match(processor.expr)) {
- var raw = node.raws.value && node.raws.value.raw ? node.raws.value.raw : node.value
- var state = context.util.saveComments(raw)
- var pair = processor.action(node.prop, state.value, context)
- state.value = pair.value
- pair.value = context.util.restoreComments(state)
- if (pair.prop !== node.prop || pair.value !== raw) {
- flipped++
- node.prop = pair.prop
- node.value = pair.value
- }
- // match found, break
- return false
- }
- })
- })
- // if last decl, apply auto rename
- // decl. may be found inside @rules
- if (context.config.autoRename && !flipped && node.parent.type === 'rule' && context.util.isLastOfType(node)) {
- var renamed = context.util.applyStringMap(node.parent.selector)
- if (context.config.autoRenameStrict === true) {
- var pair = toBeRenamed[renamed]
- if (pair) {
- pair.selector = node.parent.selector
- node.parent.selector = renamed
- } else {
- toBeRenamed[node.parent.selector] = node.parent
- }
- } else {
- node.parent.selector = renamed
- }
- }
- break
- case 'rule':
- // new rule, reset flipped decl count to zero
- flipped = 0
- break
- }
- }
- })
- state.walk(function (item) {
- result.warn('unclosed directive "' + item.metadata.name + '".', {node: item.source})
- })
- Object.keys(toBeRenamed).forEach(function (key) {
- result.warn('renaming skipped due to lack of a matching pair.', {node: toBeRenamed[key]})
- })
- context.config.hooks.post(css, postcss)
- }
- })
- /**
- * Creates a new RTLCSS instance, process the input and return its result.
- * @param {String} css A string containing input CSS.
- * @param {Object} options An object containing RTLCSS settings.
- * @param {Object|Array} plugins An array containing a list of RTLCSS plugins or a single RTLCSS plugin.
- * @param {Object} hooks An object containing pre/post hooks.
- * @returns {String} A string contining the RTLed css.
- */
- module.exports.process = function (css, options, plugins, hooks) {
- return postcss([this(options, plugins, hooks)]).process(css).css
- }
- /**
- * Creates a new instance of RTLCSS using the passed configuration object
- * @param {Object} config An object containing RTLCSS options, plugins and hooks.
- * @returns {Object} A new RTLCSS instance.
- */
- module.exports.configure = function (config) {
- config = config || {}
- return postcss([this(config.options, config.plugins, config.hooks)])
- }
|