From 63de5f6765ea9eae3f91ad2ec057d0d609cc0fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C3=B3rik?= Date: Mon, 17 Oct 2016 12:27:34 +0200 Subject: [PATCH 01/14] completelly reworked solution to Transport stream class in ES6 --- .eslintrc | 20 ++ readme.md => README.md | 0 csscolors.json | 149 +++++++++++ index.js | 542 +++++++++++++++-------------------------- package.json | 23 +- 5 files changed, 385 insertions(+), 349 deletions(-) create mode 100644 .eslintrc rename readme.md => README.md (100%) create mode 100644 csscolors.json diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..b604b75 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,20 @@ +{ + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module", + "ecmaFeatures": { + "impliedStrict": true + } + }, + "env": { + "node": true + }, + "rules": { + "semi": [2, "always"], + "indent": [1, "tab", {"SwitchCase": 1}], + "brace-style": [2, "stroustrup"], + "no-array-constructor": [2], + "space-before-function-paren": [2, "always"], + "no-eq-null": 2, + } +} diff --git a/readme.md b/README.md similarity index 100% rename from readme.md rename to README.md diff --git a/csscolors.json b/csscolors.json new file mode 100644 index 0000000..e81b137 --- /dev/null +++ b/csscolors.json @@ -0,0 +1,149 @@ +[ + "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", + "darkgrey", + "darkgreen", + "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", + "grey", + "green", + "greenyellow", + "honeydew", + "hotpink", + "indianred", + "indigo", + "ivory", + "khaki", + "lavender", + "lavenderblush", + "lawngreen", + "lemonchiffon", + "lightblue", + "lightcoral", + "lightcyan", + "lightgoldenrodyellow", + "lightgray", + "lightgrey", + "lightgreen", + "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" +] diff --git a/index.js b/index.js index 2a756e8..64ce0a6 100644 --- a/index.js +++ b/index.js @@ -1,476 +1,338 @@ -Array.lambda = function (s) { - if (typeof s !== "string") { - return s; - } - - s = s.replace("=>", "`return ") + ";"; - s = s.split("`"); - s = Function.apply(null, s); - - return s; -}; -Array.prototype.any = function (d) { - var result; - - d = Array.lambda(d); - - if (d) { - this.forEach(function (it) { - if (d(it)) { - result = it; - return { stop: true }; - } - }); - } else { - result = this[0]; - } - - return result ? true : false; -} -Array.prototype.select = function (d) { - var result = []; - - d = Array.lambda(d); - - this.forEach(function (it, i) { - result.push(d(it, i)); - }); - - return result; -}; -Array.prototype.where = function (d) { - var result = []; - - d = Array.lambda(d); +'use strict'; - this.forEach(function (it, i) { - if (d(it, i)) { - result.push(it); - } - }); +const _ = require('lodash'); +const cssc = require('./csscolors.json'); +const stream = require('stream'); - return result; -}; +const stringSplitAndTrim = (str, del) => _.compact(str.split(del).map((item) => item.trim())); -var css2less = function (css, options) { - var me = this; - - var ctor = function () { - me.css = css || ""; - me.options = { - cssColors: ["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", "darkgrey", "darkgreen", "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", "grey", "green", "greenyellow", "honeydew", "hotpink", "indianred", "indigo", "ivory", "khaki", "lavender", "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral", "lightcyan", "lightgoldenrodyellow", "lightgray", "lightgrey", "lightgreen", "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"], - vendorPrefixesList: ["-moz", "-o", "-ms", "-webkit"], - //vendorPrefixesReg: /^(-moz|-o|-ms|-webkit)-/gi, - indentSymbol: "\t", +//----------------------------------------------------------------------------- +class css2less extends stream.Transform { + constructor (options) { + this.options = _.defaults({}, options || {}, { + encoding: 'utf8', + vendorPrefixesList: ['moz', 'o', 'ms', 'webkit'], + indentSymbol: '\t', indentSize: 1, - selectorSeparator: ",\n", + selectorSeparator: ',\n', blockFromNewLine: false, - blockSeparator: "\n", - updateColors: false, + blockSeparator: '\n', + updateColors: true, vendorMixins: true, - nameValueSeparator: ": " - }; - var vendorPrefixesListStr = me.options.vendorPrefixesList.join('|'); - - me.options.vendorPrefixesReg = new RegExp('^(' + vendorPrefixesListStr + ')-', 'gi'); - - for (var i in me.options) { - if (typeof options[i] === 'undefined') continue; - me.options[i] = options[i]; - } - - me.tree = {}; - me.less = []; - me.colors = {}; - me.colors_index = 0; - me.vendorMixins = {}; - }, - isBase64 = function(str) { - return str.indexOf('base64') !== -1; - }; - - me.processLess = function () { - me.cleanup(); - - if (!me.css) { - return false; - } + nameValueSeparator: ': ' + }); - me.generateTree(); - me.renderLess(); - me.less = me.less.join(""); + super({ encoding: this.options.encoding }); - return true; - }; + this.options.vendorPrefixesReg = new RegExp('^-(' + + this.options.vendorPrefixesList.join('|') + ')-', 'gi'); - me.cleanup = function () { - me.tree = {}; - me.less = []; + this.css = ''; + this.tree = {}; + this.less = []; + this.colors = {}; + this.colors_index = 0; + this.vendorMixins = {}; } - me.convertRules = function (data) { - var arr = data.split(/[;]/gi).select("val=>val.trim()").where("val=>val"), - base64Index = false; - - arr.forEach(function(item, i) { - if ( isBase64(item) ) base64Index = i; - }); - - if (base64Index) { - arr[base64Index - 1] = arr[base64Index - 1] + ';' + arr[base64Index]; - arr.splice(base64Index, 1); - } - - return arr; - }; - - me.color = function (value) { - value = value.trim(); + _transform (chunk, enc, done) { + this.css += chunk.toString(enc); + done(); + } - if (me.options.cssColors.indexOf(value) >= 0 || /^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/gi.test(value) || /(rgba?)\(.*\)/gi.test(value)) { - return true; - } + _flush (done) { + this.generateTree(); + this.renderLess(); - return false; - }; + this.push(this.less.join('')); + done(); + } - me.convertIfColor = function (color) { +//----------------------------------------------------------------------------- + convertIfColor (color) { color = color.trim(); - if (me.color(color)) { - if (!me.colors[color]) { - me.colors[color] = "@color" + me.colors_index; - me.colors_index++; + if (cssc.indexOf(color) >= 0 || /^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/gi.test(color) || /(rgba?)\(.*\)/gi.test(color)) { + if (!this.colors[color]) { + this.colors[color] = '@color' + this.colors_index; + this.colors_index++; } - return me.colors[color]; - } + return this.colors[color]; + } return color; - }; + } - me.matchColor = function (style) { - var rules = me.convertRules(style); - var result = []; + matchColor (style) { + let rules = stringSplitAndTrim(style, /\;(?!base64)/gi); + let result = []; rules.forEach(function (r, i) { - var parts = r.split(/[:]/gi); - var key = parts[0].trim(); - var value = parts[1].trim(); - - result.push(i > 0 ? "\n" : "", key); - - if (!value) { + let parts = r.split(/:/); + if (!(parts > 1 && parts[1].trim())) { return; } - var oldValues = value.split(/\s+/gi); - var newValues = oldValues.select(function (v) { - return me.convertIfColor(v); - }); + let key = parts[0].trim(); + let values = parts[1] + .trim() + .split(/\s+/gi) + .map((v) => this.convertIfColor(v)); - result.push(me.options.nameValueSeparator, newValues.join(" "), ";"); + result.push((i ? '\n' : '') + key, + this.options.nameValueSeparator, + values.join(' ') + ';'); }); - return result.join(""); - }; - - me.matchVendorPrefixMixin = function (style) { - var normal_rules = {}; - var prefixed_rules = {}; - var rules = me.convertRules(style); - - for (var i = 0; i < rules.length; i++) { - var e = rules[i].trim(); - - var parts = e.split(/[:]/gi); + return result.join(''); + } - if ( isBase64(e) ) parts = [parts[0], parts[1] + parts[2]]; + matchVendorPrefixMixin (style) { + let normal_rules = {}; + let prefixed_rules = {}; + let rules = stringSplitAndTrim(style, /\;(?!base64)/gi); - var key = parts[0].trim(); - var value = parts[1].trim(); + rules.forEach(function (rule) { + let [ key, value ] = stringSplitAndTrim(rule, /\:(?!image)/gi); if (!value) { - normal_rules[key] = ""; - } else if (me.options.vendorPrefixesReg.test(key)) { - var rule_key = key.replace(me.options.vendorPrefixesReg, ""); - var values = value.split(/\s+/gi); - var newValue = []; - - for (var j = 0; j < values.length; j++) { - newValue.push(values[j].trim()); - } - - newValue = newValue.join(" "); + normal_rules[key] = ''; + } + else if (this.options.vendorPrefixesReg.test(key)) { + let rule_key = key.replace(this.options.vendorPrefixesReg, ''); + let newValue = value.replace(/\s+/gi, ' ').trim(); if (prefixed_rules[rule_key] && prefixed_rules[rule_key] != newValue) { return style; } prefixed_rules[rule_key] = newValue; - } else { + } + else { normal_rules[key] = value; } - } - - for (var k in prefixed_rules) { - var v = prefixed_rules[k]; + }); - v = v.split(/\s+/gi).select("val=>val.trim()").where("val=>val"); + _.forOwn(prefixed_rules, function (value, k) { + let v = stringSplitAndTrim(value, /\s+/gi); - if (!me.vendorMixins[k]) { - me.vendorMixins[k] = v.length; - } + if (!this.vendorMixins[k]) + this.vendorMixins[k] = v.length; if (normal_rules[k]) { delete normal_rules[k]; - normal_rules[".vp-" + k + "(" + v.join(", ") + ")"] = ""; - } - } - - var result = []; - for (var k in normal_rules) { - var v = normal_rules[k]; - var r = [k]; + let newKey = '.vp-' + k + '(' + v.join(', ') + ')'; + normal_rules[newKey] = ''; + } + }); - if (v) { - r.push(me.options.nameValueSeparator, v, ";\n"); + let result = []; + _.forOwn(prefixed_rules, function (v, k) { + let rule = k; + if (v.trim()) { + rule += this.options.nameValueSeparator + v + ';\n'; } - result.push(r.join("")); - } + result.push(rule); + }); - return result.join(""); - }; + return result.join(''); + } - me.addRule = function (tree, selectors, style) { + addRule (tree, selectors, style) { if (!style) { return; } if (!selectors || !selectors.length) { - if (me.options.updateColors) { - style = me.matchColor(style) + if (this.options.updateColors) { + style = this.matchColor(style); } - - if (me.options.vendorMixins) { - style = me.matchVendorPrefixMixin(style); + if (this.options.vendorMixins) { + style = this.matchVendorPrefixMixin(style); } - if (!tree.style) { - tree.style = style; - } else { - tree.style += style; - } - } else { - var first = selectors[0].split(/\s*[,]\s*/gi).select("val=>val.trim()").where("val=>val").join(me.options.selectorSeparator); - + tree.style = (tree.style || '') + style; + } + else { + let first = stringSplitAndTrim(selectors[0], /\s*[,]\s*/gi) + .join(this.options.selectorSeparator); + if (!tree.children) { tree.children = []; } - if (!tree[first]) { - tree[first] = {}; + let node = tree[first]; + if (!node) { + node = tree[first] = {}; } - var node = tree[first]; - selectors.splice(0, 1); tree.children.push(node); - me.addRule(node, selectors, style); + this.addRule(node, selectors, style); } - }; - - me.generateTree = function () { - var csss = me.css.split(/\n/gi); - var temp = csss.select("val=>val.trim()").where("val=>val"); + } - temp = temp.join(""); - temp = temp.replace(/[/][*]+[^\*]*[*]+[/]/gi, ""); - temp = temp.replace(/[^{}]+[{]\s*[}]/ig, " "); - temp = temp.split(/[{}]/gi).where("val=>val"); + generateTree (data) { + let temp = stringSplitAndTrim(data, /\n/g) + .join('') + .replace(/\/\*+[^\*]*\*+\//g, '') + .replace(/[^\{\}]+\{\s*\}/g, ' '); - var styles = []; + temp = stringSplitAndTrim(temp, /[\{\}]/g); - for (var i = 0; i < temp.length; i++) { - if (i % 2 === 0) { - styles.push([temp[i]]); - } else { - styles[styles.length - 1].push(temp[i]); + let styles = []; + temp.forEach(function (val, i) { + if (!(i & 1)) { + styles.push(val); } - } + else { + styles[styles.length - 1].push(val); + } + }); - for (var i = 0; i < styles.length; i++) { - var style = styles[i]; - var rules = style[0]; + styles.forEach(function (style) { + let rule = style[0].replace(/\s*>\s*/gi, ' &>'); - if (rules.indexOf(">") >= 0) { - rules = rules.replace(/\s*>\s*/gi, " &>"); + if (~rule.indexOf('@import')) { + let import_rule = rule.match(/@import.*;/gi)[0]; + rule = rule.replace(/@import.*;/gi, ''); + this.addRule(this.tree, [], import_rule); } - if (rules.indexOf("@import") >= 0) { - var import_rule = rules.match(/@import.*;/gi)[0]; - rules = rules.replace(/@import.*;/gi, ""); - me.addRule(me.tree, [], import_rule); + if (~rule.indexOf(',')) { + this.addRule(this.tree, [rule], style[1]); } - - if (rules.indexOf(",") >= 0) { - me.addRule(me.tree, [rules], style[1]); - } else { - var rules_split = rules.replace(/[:]/gi, " &:").split(/\s+/gi).select("val=>val.trim()").where("val=>val").select(function (it, i) { - return it.replace(/[&][>]/gi, "& > "); + else { + let rules_split = stringSplitAndTrim(rule.replace(/[:]/gi, ' &:'), /\s+/gi).map(function (it) { + return it.replace(/[&][>]/gi, '& > '); }); - me.addRule(me.tree, rules_split, style[1]); + this.addRule(this.tree, rules_split, style[1]); } - } - }; - - me.buildMixinList = function (indent) { - var less = []; - - for (var k in me.vendorMixins) { - var v = me.vendorMixins[k]; - var args = []; + }); + } - for (var i = 0; i < v; i++) { - args.push("@p" + i); - } + buildMixinList (indent) { + let less = []; - less.push(".vp-", k, "(", args.join(", "), ")"); + _.forOwn(this.vendorMixins, function (v, k) { + let args = []; - if (me.options.blockFromNewLine) { - less.push("\n"); - } else { - less.push(" "); + for (let i = 0; i < v; i++) { + args.push('@p' + i); } - less.push("{\n"); + less.push('.vp-', k, '(', args.join(', '), ')'); + less.push(this.options.blockFromNewLine ? '\n' : ' '); - me.options.vendorPrefixesList.forEach(function (vp, i) { - less.push(getIndent(indent + me.options.indentSize)); - less.push(vp, "-", k, me.options.nameValueSeparator, args.join(" "), ";\n"); + less.push('{\n'); + this.options.vendorPrefixesList.forEach(function (vp, i) { + less.push(this.getIndent(indent + this.options.indentSize)); + less.push('-', vp, '-', k, this.options.nameValueSeparator, args.join(' '), ';\n'); }); + less.push(this.getIndent(indent + this.options.indentSize), k, this.options.nameValueSeparator, args.join(' '), ';\n'); + less.push('}\n'); + }); - less.push(getIndent(indent + me.options.indentSize), k, me.options.nameValueSeparator, args.join(" "), ";\n"); - less.push(/*getIndent(indent), */"}\n"); - } - - if (less.any()) { - less.push("\n"); + if (less.length) { + less.push('\n'); } - return less.join(""); + return less.join(''); }; - me.renderLess = function (tree, indent) { + renderLess (tree, indent) { indent = indent || 0; if (!tree) { - for (var k in me.colors) { - var v = me.colors[k]; - me.less.push(v, me.options.nameValueSeparator, k, ";\n"); + for (let k in this.colors) { + let v = this.colors[k]; + this.less.push(v, this.options.nameValueSeparator, k, ';\n'); } - if (me.colors_index > 0) { - me.less.push("\n"); + if (this.colors_index > 0) { + this.less.push('\n'); } - if (me.options.vendorMixins) { - me.less.push(me.buildMixinList(indent)); + if (this.options.vendorMixins) { + this.less.push(this.buildMixinList(indent)); } - tree = me.tree; + tree = this.tree; } - var index = 0; + let index = 0; - for (var i in tree) { - if (i == "children") { + for (let i in tree) { + if (i == 'children') { continue; } - var element = tree[i]; - var children = element.children; + let element = tree[i]; + let children = element.children; - if (i == "style") { - me.less.push(me.convertRules(element).join(";\n"), "\n"); - } else { + if (i == 'style') { + let rules = stringSplitAndTrim(element, /\;(?!base64)/gi); + this.less.push(rules.join(';\n'), '\n'); + } + else { if (index > 0) { - me.less.push(me.options.blockSeparator); + this.less.push(this.options.blockSeparator); } - //Selector indent - if (indent > 0) me.less.push(getIndent(indent), i); - else me.less.push(/*getIndent(indent),*/ i); + if (indent > 0) { + this.less.push(this.getIndent(indent)); + } + this.less.push(i); - if (me.options.blockFromNewLine) { - me.less.push("\n", getIndent(indent)); - } else { - me.less.push(" "); + if (this.options.blockFromNewLine) { + this.less.push('\n' + this.getIndent(indent)); + } + else { + this.less.push(' '); } - me.less.push("{\n"); + this.less.push('{\n'); - var style = element.style; + let style = element.style; delete element.style; if (style) { - var temp = me.convertRules(style); - - temp = temp.select(function (it, i) { - return getIndent(indent + me.options.indentSize) + it + ";"; - }); + let temp = stringSplitAndTrim(style, /\;(?!base64)/gi); + let indented = temp.map(it => this.getIndent(indent + this.options.indentSize) + it + ';').join('\n'); - me.less.push(temp.join("\n"), "\n"); + this.less.push(indented, '\n'); if (children && children.length) { - me.less.push(me.options.blockSeparator); + this.less.push(this.options.blockSeparator); } } - me.renderLess(element, indent + me.options.indentSize); + this.renderLess(element, indent + this.options.indentSize); + + if (indent > 0) { + this.less.push(this.getIndent(indent)); + } + this.less.push('}\n'); - if (indent > 0) me.less.push(getIndent(indent), "}\n"); - else me.less.push(/*getIndent(indent),*/ "}\n"); - index++; } } - }; - - function getIndent(size) { - size = size || me.options.indentSize; + } - var result = '', - indent; + getIndent (size) { + let result = ''; + let max = size || this.options.indentSize; - var max = size, - n = 0; - for (; n < max; n++) { - result += me.options.indentSymbol; + for (let n = 0; n < max; n++) { + result += this.options.indentSymbol; } return result; - /*return (new Array(size)).select(function (it, i) { - return me.options.indentSymbol; - }).join("");*/ } - - ctor(); }; - - -module.exports = function(cssString, options) { - if (!cssString) { - console.log('cssString require'); - return false; - } - - var lessInst = new css2less(cssString, options || {}); - - lessInst.processLess(); - - return lessInst.less; -}; \ No newline at end of file +//----------------------------------------------------------------------------- +module.exports = css2less; diff --git a/package.json b/package.json index 888edba..27e6ad4 100644 --- a/package.json +++ b/package.json @@ -1,24 +1,29 @@ { "name": "css2less", - "version": "0.1.4", + "version": "0.2.0", "description": "Convert css to less", - "homepage": "https://github.com/serheyShmyg/css2less", - "author": { + "homepage": "https://github.com/mborik/css2less", + "authors": { "name": "Serhey Shmyg", "email": "serhey.shmyg.all@gmail.com" }, - "repository": { + "contributors": [{ + "name": "Martin Bórik", + "email": "mborik@users.sourceforge.net" + }], + "repository": { "type": "git", - "url": "https://github.com/serheyShmyg/css2less.git" + "url": "https://github.com/mborik/css2less.git" }, "bugs": { - "url": "https://github.com/serheyShmyg/css2less/issues" + "url": "https://github.com/mborik/css2less/issues" }, "main": "index.js", "scripts": { - "start" : "gulp", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "exit 1" }, "license": "ISC", - "dependencies": {} + "dependencies": { + "lodash": "^4.16.2" + } } From 35b59fc18cfadc2e579e79da72547e18061f2179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C3=B3rik?= Date: Mon, 17 Oct 2016 12:34:30 +0200 Subject: [PATCH 02/14] implemented command line interface --- .gitignore | 12 ++++----- README.md | 73 +++++++++++++++++++++++----------------------------- cli.js | 64 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 6 ++++- 4 files changed, 107 insertions(+), 48 deletions(-) create mode 100644 cli.js diff --git a/.gitignore b/.gitignore index 22575a0..3fb8c18 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ -node_modules -test -.DS_Store -LICENSE-MIT -Gruntfile.js -npm-debug.log \ No newline at end of file +node_modules +test +.DS_Store +LICENSE* +npm-debug.log +.tern-project diff --git a/README.md b/README.md index 60ceeda..ea4f785 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,51 @@ -#CSS to LESS +# CSS to LESS Convert css to less ```shell -npm install css2less --save-dev +npm install -g css2less ``` -###Options: -#### options.indentSize -Type: `Number` -Default value: `1` -Desc: Indent size. +## Usage: +>+ `$ css2less [options] ` -#### options.vendorPrefixesList -Type: `Array` -Default value: `["-moz", "-o", "-ms", "-webkit"]` -Desc: List of vendor prefixes. +### CLI Options ### +#### --indentSize +Type: `number` +Default value: `1` +Desc: Indent size. -#### options.indentSymbol -Type: `String` -Default value: `\t` +#### --indentSymbol +Type: `string` +Default value: `\t` Desc: Indent symbol. -#### options.selectorSeparator -Type: `String` -Default value: `,\n` +#### --selectorSeparator +Type: `string` +Default value: `,\n` Desc: Selector separator. -#### options.blockFromNewLine -Type: `Bolean` -Default value: `false` -Desc: Start first '{' from the new line - -#### options.blockSeparator -Type: `String` -Default value: `\n` +#### --blockSeparator +Type: `string` +Default value: `\n` Desc: Separator between blocks. -#### options.updateColors -Type: `Bolean` -Default value: `false` +#### --blockFromNewLine +Type: `boolean` +Default value: `false` +Desc: Start first '{' from the new line + +#### --updateColors +Type: `boolean` +Default value: `true` Desc: Use variables for colors. -#### options.vendorMixins -Type: `Boolean` -Default value: `true` +#### --vendorMixins +Type: `boolean` +Default value: `true` Desc: Create function for vendor styles. -##Example +## Pure JavaScript usage example: ```javascript -var css2less = require('css2less'), - cssString = 'a {color:green; text-decoration:none; } a:hover {color:lime; } a:active {text-decoration:underline; }', - options = {}, - result; - -result = css2less(cssString, options); -console.log(result); +var css2less = require('css2less'); +var lessResult = css2less(inputCSSInString, options); ``` - - \ No newline at end of file diff --git a/cli.js b/cli.js new file mode 100644 index 0000000..0b0ba04 --- /dev/null +++ b/cli.js @@ -0,0 +1,64 @@ +#!/usr/bin/env node +'use strict'; + +const appname = 'css2less'; + +const fs = require('fs'); +const path = require('path'); +const meow = require('meow'); +const css2less = require('./index.js'); + +let cli = meow(` + Usage: + $ ${appname} [options] + + Options: + --indentSize Indent size (default 1) + --indentSymbol Indentation symbol (default: tab character) + --selectorSeparator String separator between selectors (default: comma and newline) + --blockSeparator Separator between blocks (default: newline character) + --blockFromNewLine Start first '{' from the newline after selector. + --updateColors Create variables for colors. + --vendorMixins Create function for vendor styles. + + -h, --help Show help + -v, --version Version number +`, { + string: [ 'indentSymbol', 'selectorSeparator', 'blockSeparator' ], + boolean: [ 'updateColors', 'vendorMixins' ], + alias: { + h: 'help', + v: 'version' + } +}); + +if (!cli.input.length) + cli.showHelp(); + +cli.input.forEach(function (file) { + let cwd = process.cwd(); + let filePath = path.resolve(cwd, file); + + try { + if (!fs.statSync(filePath).isFile()) + throw new Error("ENOTFILE"); + } + catch (err) { + console.error('Invalid file: "%s" (%s)', filePath, + (err.code || err.message || err)); + return; + } + + let ext = path.extname(file); + if (ext.toLowerCase() !== '.css') + console.warn("%s hasn't proper extension, you've been warned!", file); + + let lessFile = path.join( + path.dirname(filePath), + path.basename(file, ext) + '.less' + ); + + fs.createReadStream(filePath) + .pipe(css2less(cli.flags)) + .pipe(fs.createWriteStream(lessFile)); +}); diff --git a/package.json b/package.json index 27e6ad4..d78f170 100644 --- a/package.json +++ b/package.json @@ -19,11 +19,15 @@ "url": "https://github.com/mborik/css2less/issues" }, "main": "index.js", + "bin": { + "css2less": "./cli.js" + }, "scripts": { "test": "exit 1" }, "license": "ISC", "dependencies": { - "lodash": "^4.16.2" + "lodash": "^4.16.2", + "meow": "^3.7.0" } } From 168e35d0536b9ac14c0e4eca9b6b7997a1cb6e6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C3=B3rik?= Date: Mon, 17 Oct 2016 14:26:57 +0200 Subject: [PATCH 03/14] fixed lot of scope issues and stabilized functionality --- cli.js | 14 ++++++-- index.js | 106 ++++++++++++++++++++++++++----------------------------- 2 files changed, 62 insertions(+), 58 deletions(-) diff --git a/cli.js b/cli.js index 0b0ba04..a80ac38 100644 --- a/cli.js +++ b/cli.js @@ -26,6 +26,11 @@ let cli = meow(` `, { string: [ 'indentSymbol', 'selectorSeparator', 'blockSeparator' ], boolean: [ 'updateColors', 'vendorMixins' ], + default: { + updateColors: true, + vendorMixins: true + }, + stopEarly: true, alias: { h: 'help', v: 'version' @@ -40,8 +45,9 @@ cli.input.forEach(function (file) { let filePath = path.resolve(cwd, file); try { - if (!fs.statSync(filePath).isFile()) + if (!fs.statSync(filePath).isFile()) { throw new Error("ENOTFILE"); + } } catch (err) { console.error('Invalid file: "%s" (%s)', filePath, @@ -50,8 +56,10 @@ cli.input.forEach(function (file) { } let ext = path.extname(file); - if (ext.toLowerCase() !== '.css') + if (ext.toLowerCase() !== '.css') { console.warn("%s hasn't proper extension, you've been warned!", file); + return; + } let lessFile = path.join( path.dirname(filePath), @@ -59,6 +67,6 @@ cli.input.forEach(function (file) { ); fs.createReadStream(filePath) - .pipe(css2less(cli.flags)) + .pipe(new css2less(cli.flags)) .pipe(fs.createWriteStream(lessFile)); }); diff --git a/index.js b/index.js index 64ce0a6..fe4562b 100644 --- a/index.js +++ b/index.js @@ -9,7 +9,7 @@ const stringSplitAndTrim = (str, del) => _.compact(str.split(del).map((item) => //----------------------------------------------------------------------------- class css2less extends stream.Transform { constructor (options) { - this.options = _.defaults({}, options || {}, { + options = _.defaults({}, options, { encoding: 'utf8', vendorPrefixesList: ['moz', 'o', 'ms', 'webkit'], indentSymbol: '\t', @@ -22,10 +22,10 @@ class css2less extends stream.Transform { nameValueSeparator: ': ' }); - super({ encoding: this.options.encoding }); + super({ encoding: options.encoding }); - this.options.vendorPrefixesReg = new RegExp('^-(' + - this.options.vendorPrefixesList.join('|') + ')-', 'gi'); + options.vendorPrefixesReg = new RegExp('^-(' + options.vendorPrefixesList.join('|') + ')-', 'gi'); + this.options = options; this.css = ''; this.tree = {}; @@ -36,7 +36,7 @@ class css2less extends stream.Transform { } _transform (chunk, enc, done) { - this.css += chunk.toString(enc); + this.css += chunk.toString(this.options.encoding); done(); } @@ -49,41 +49,50 @@ class css2less extends stream.Transform { } //----------------------------------------------------------------------------- - convertIfColor (color) { - color = color.trim(); + getIndent (size) { + let result = ''; + let max = size || this.options.indentSize; - if (cssc.indexOf(color) >= 0 || /^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/gi.test(color) || /(rgba?)\(.*\)/gi.test(color)) { - if (!this.colors[color]) { - this.colors[color] = '@color' + this.colors_index; + for (let n = 0; n < max; n++) { + result += this.options.indentSymbol; + } + + return result; + } + + convertIfColor (value) { + if (cssc.indexOf(value) >= 0 || + /^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/gi.test(value) || + /(rgba?)\(.*\)/gi.test(value)) { + + if (!this.colors[value]) { + this.colors[value] = '@color' + this.colors_index; this.colors_index++; } - return this.colors[color]; + return this.colors[value]; } - return color; + return value; } matchColor (style) { let rules = stringSplitAndTrim(style, /\;(?!base64)/gi); let result = []; - rules.forEach(function (r, i) { - let parts = r.split(/:/); - if (!(parts > 1 && parts[1].trim())) { + rules.forEach(function (rule, i) { + let [ key, value ] = stringSplitAndTrim(rule, /\:(?!image)/gi); + if (!value) { return; } - let key = parts[0].trim(); - let values = parts[1] - .trim() - .split(/\s+/gi) + let values = value.split(/\s+/gi) .map((v) => this.convertIfColor(v)); result.push((i ? '\n' : '') + key, this.options.nameValueSeparator, values.join(' ') + ';'); - }); + }, this); return result.join(''); } @@ -112,9 +121,9 @@ class css2less extends stream.Transform { else { normal_rules[key] = value; } - }); + }, this); - _.forOwn(prefixed_rules, function (value, k) { + _.forOwn(prefixed_rules, (function (value, k) { let v = stringSplitAndTrim(value, /\s+/gi); if (!this.vendorMixins[k]) @@ -126,17 +135,15 @@ class css2less extends stream.Transform { let newKey = '.vp-' + k + '(' + v.join(', ') + ')'; normal_rules[newKey] = ''; } - }); + }).bind(this)); let result = []; - _.forOwn(prefixed_rules, function (v, k) { - let rule = k; - if (v.trim()) { - rule += this.options.nameValueSeparator + v + ';\n'; + _.forOwn(normal_rules, (function (value, rule) { + if (value.trim()) { + rule += this.options.nameValueSeparator + value + ';\n'; } - result.push(rule); - }); + }).bind(this)); return result.join(''); } @@ -175,25 +182,25 @@ class css2less extends stream.Transform { } } - generateTree (data) { - let temp = stringSplitAndTrim(data, /\n/g) + generateTree () { + let temp = stringSplitAndTrim(this.css, /\n/g) .join('') .replace(/\/\*+[^\*]*\*+\//g, '') .replace(/[^\{\}]+\{\s*\}/g, ' '); - temp = stringSplitAndTrim(temp, /[\{\}]/g); + let styles = stringSplitAndTrim(temp, /[\{\}]/g); + let styleDefs = []; - let styles = []; - temp.forEach(function (val, i) { + styles.forEach(function (val, i) { if (!(i & 1)) { - styles.push(val); + styleDefs.push([val]); } else { - styles[styles.length - 1].push(val); + styleDefs[styleDefs.length - 1].push(val); } }); - styles.forEach(function (style) { + styleDefs.forEach(function (style) { let rule = style[0].replace(/\s*>\s*/gi, ' &>'); if (~rule.indexOf('@import')) { @@ -212,13 +219,13 @@ class css2less extends stream.Transform { this.addRule(this.tree, rules_split, style[1]); } - }); + }, this); } buildMixinList (indent) { let less = []; - _.forOwn(this.vendorMixins, function (v, k) { + _.forOwn(this.vendorMixins, (function (v, k) { let args = []; for (let i = 0; i < v; i++) { @@ -232,10 +239,11 @@ class css2less extends stream.Transform { this.options.vendorPrefixesList.forEach(function (vp, i) { less.push(this.getIndent(indent + this.options.indentSize)); less.push('-', vp, '-', k, this.options.nameValueSeparator, args.join(' '), ';\n'); - }); + }, this); + less.push(this.getIndent(indent + this.options.indentSize), k, this.options.nameValueSeparator, args.join(' '), ';\n'); less.push('}\n'); - }); + }).bind(this)); if (less.length) { less.push('\n'); @@ -248,10 +256,9 @@ class css2less extends stream.Transform { indent = indent || 0; if (!tree) { - for (let k in this.colors) { - let v = this.colors[k]; + _.forOwn(this.colors, (function (v, k) { this.less.push(v, this.options.nameValueSeparator, k, ';\n'); - } + }).bind(this)); if (this.colors_index > 0) { this.less.push('\n'); @@ -322,17 +329,6 @@ class css2less extends stream.Transform { } } } - - getIndent (size) { - let result = ''; - let max = size || this.options.indentSize; - - for (let n = 0; n < max; n++) { - result += this.options.indentSymbol; - } - - return result; - } }; //----------------------------------------------------------------------------- module.exports = css2less; From cae2e0719c87960cf5ec9846c19755d644b14743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C3=B3rik?= Date: Tue, 1 Aug 2017 11:00:34 +0200 Subject: [PATCH 04/14] unstable vendor mixin detection disabled by default; color variable names enhanced by path --- cli.js | 16 +++++++++++----- index.js | 5 +++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/cli.js b/cli.js index a80ac38..3f04ba3 100644 --- a/cli.js +++ b/cli.js @@ -28,7 +28,7 @@ let cli = meow(` boolean: [ 'updateColors', 'vendorMixins' ], default: { updateColors: true, - vendorMixins: true + vendorMixins: false }, stopEarly: true, alias: { @@ -61,10 +61,16 @@ cli.input.forEach(function (file) { return; } - let lessFile = path.join( - path.dirname(filePath), - path.basename(file, ext) + '.less' - ); + let fileDir = path.dirname(filePath); + let fileBaseName = path.basename(file, ext); + let lessFile = path.join(fileDir, fileBaseName + '.less'); + + // linton specific path rule + let lintonCssPathRule = fileDir.indexOf('linton' + path.sep + 'css'); + if (~lintonCssPathRule) + fileDir = fileDir.substr(lintonCssPathRule + 11); + + cli.flags.filePathway = fileDir.split(path.sep).concat(fileBaseName); fs.createReadStream(filePath) .pipe(new css2less(cli.flags)) diff --git a/index.js b/index.js index fe4562b..8c691b2 100644 --- a/index.js +++ b/index.js @@ -10,6 +10,7 @@ const stringSplitAndTrim = (str, del) => _.compact(str.split(del).map((item) => class css2less extends stream.Transform { constructor (options) { options = _.defaults({}, options, { + filePathway: [], encoding: 'utf8', vendorPrefixesList: ['moz', 'o', 'ms', 'webkit'], indentSymbol: '\t', @@ -18,7 +19,7 @@ class css2less extends stream.Transform { blockFromNewLine: false, blockSeparator: '\n', updateColors: true, - vendorMixins: true, + vendorMixins: false, nameValueSeparator: ': ' }); @@ -66,7 +67,7 @@ class css2less extends stream.Transform { /(rgba?)\(.*\)/gi.test(value)) { if (!this.colors[value]) { - this.colors[value] = '@color' + this.colors_index; + this.colors[value] = '@cl-' + this.options.filePathway.concat(this.colors_index).join('-'); this.colors_index++; } From a62a3b268885d4c4543a1eba7a6f203c58418782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C3=B3rik?= Date: Tue, 15 Aug 2017 10:14:39 +0200 Subject: [PATCH 05/14] massive improved, ES6 beautyfied and bugfixed version --- cli.js | 41 +++++++++++-------- index.js | 123 +++++++++++++++++++++++++++++++++---------------------- 2 files changed, 99 insertions(+), 65 deletions(-) diff --git a/cli.js b/cli.js index 3f04ba3..22bd93b 100644 --- a/cli.js +++ b/cli.js @@ -13,25 +13,30 @@ let cli = meow(` $ ${appname} [options] Options: - --indentSize Indent size (default 1) - --indentSymbol Indentation symbol (default: tab character) - --selectorSeparator String separator between selectors (default: comma and newline) - --blockSeparator Separator between blocks (default: newline character) - --blockFromNewLine Start first '{' from the newline after selector. - --updateColors Create variables for colors. - --vendorMixins Create function for vendor styles. + --indent-size Indent size (default 1) + --indent-symbol Indentation symbol (default: tab character) + --selector-separator String separator between selectors (default: comma and newline) + --block-separator Separator between blocks (default: newline character) + --block-on-newline Start first '{' from the newline after selector. + --update-colors Create variables for colors. + --vendor-mixins Create function for vendor styles. + -var, --variables-path Path to 'variables.less' file where will be all colors stored. + Defaultly was colors stored on the top of each file, but with + this given path will be generated with name prepended by + relative path where 'variables.less' was stored. - -h, --help Show help - -v, --version Version number + -h, --help Show help + -v, --version Version number `, { - string: [ 'indentSymbol', 'selectorSeparator', 'blockSeparator' ], - boolean: [ 'updateColors', 'vendorMixins' ], + string: [ 'variables-path', 'indent-symbol', 'selector-separator', 'block-separator' ], + boolean: [ 'update-colors', 'vendor-mixins' ], default: { updateColors: true, vendorMixins: false }, stopEarly: true, alias: { + var: 'variables-path', h: 'help', v: 'version' } @@ -65,12 +70,16 @@ cli.input.forEach(function (file) { let fileBaseName = path.basename(file, ext); let lessFile = path.join(fileDir, fileBaseName + '.less'); - // linton specific path rule - let lintonCssPathRule = fileDir.indexOf('linton' + path.sep + 'css'); - if (~lintonCssPathRule) - fileDir = fileDir.substr(lintonCssPathRule + 11); + if (cli.flags.variablesPath) { + let varDir = path.dirname(cli.flags.variablesPath); + let varRelPath = path.relative(varDir, fileDir); - cli.flags.filePathway = fileDir.split(path.sep).concat(fileBaseName); + cli.flags.filePathway = []; + if (varRelPath.length > 0) { + cli.flags.filePathway = varRelPath.split(path.sep); + } + cli.flags.filePathway.push(fileBaseName); + } fs.createReadStream(filePath) .pipe(new css2less(cli.flags)) diff --git a/index.js b/index.js index 8c691b2..f360e72 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,8 @@ const _ = require('lodash'); const cssc = require('./csscolors.json'); const stream = require('stream'); +const path = require('path'); +const fs = require('fs'); const stringSplitAndTrim = (str, del) => _.compact(str.split(del).map((item) => item.trim())); @@ -25,14 +27,17 @@ class css2less extends stream.Transform { super({ encoding: options.encoding }); - options.vendorPrefixesReg = new RegExp('^-(' + options.vendorPrefixesList.join('|') + ')-', 'gi'); this.options = options; + this.vendorPrefixesReg = new RegExp('^-(' + options.vendorPrefixesList.join('|') + ')-', 'gi'); + this.rgbaMatchReg = /(((0|\d{1,}px)\s+?){3})?rgba?\((\d{1,3},\s*?){2}\d{1,3}(,\s*?0?\.\d+?)?\)$/gi; + this.commentMatchReg = /\/\*(?:[^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+\//g; + this.css = ''; this.tree = {}; this.less = []; - this.colors = {}; - this.colors_index = 0; + this.vars = {}; + this.vars_index = 0; this.vendorMixins = {}; } @@ -61,34 +66,35 @@ class css2less extends stream.Transform { return result; } - convertIfColor (value) { - if (cssc.indexOf(value) >= 0 || - /^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/gi.test(value) || - /(rgba?)\(.*\)/gi.test(value)) { + convertIfVariable (value) { + if (cssc.indexOf(value) < 0 && ( + /^#([0-9a-f]{6}|[0-9a-f]{3})$/i.test(value) || + /^url\(([\"'])((?:\\\1|.)+?)\1\)$/i.test(value) || + this.rgbaMatchReg.test(value))) { - if (!this.colors[value]) { - this.colors[value] = '@cl-' + this.options.filePathway.concat(this.colors_index).join('-'); - this.colors_index++; + if (!this.vars[value]) { + this.vars[value] = '@var-' + this.options.filePathway.concat(this.vars_index).join('-'); + this.vars_index++; } - return this.colors[value]; + return this.vars[value]; } return value; } - matchColor (style) { + matchVariable (style) { let rules = stringSplitAndTrim(style, /\;(?!base64)/gi); let result = []; - rules.forEach(function (rule, i) { + rules.forEach((rule, i) => { let [ key, value ] = stringSplitAndTrim(rule, /\:(?!image)/gi); if (!value) { return; } - let values = value.split(/\s+/gi) - .map((v) => this.convertIfColor(v)); + value = value.replace(this.rgbaMatchReg, match => this.convertIfVariable(match)); + let values = value.split(/\s+/gi).map(v => this.convertIfVariable(v)); result.push((i ? '\n' : '') + key, this.options.nameValueSeparator, @@ -103,14 +109,14 @@ class css2less extends stream.Transform { let prefixed_rules = {}; let rules = stringSplitAndTrim(style, /\;(?!base64)/gi); - rules.forEach(function (rule) { + rules.forEach(rule => { let [ key, value ] = stringSplitAndTrim(rule, /\:(?!image)/gi); if (!value) { normal_rules[key] = ''; } - else if (this.options.vendorPrefixesReg.test(key)) { - let rule_key = key.replace(this.options.vendorPrefixesReg, ''); + else if (this.vendorPrefixesReg.test(key)) { + let rule_key = key.replace(this.vendorPrefixesReg, ''); let newValue = value.replace(/\s+/gi, ' ').trim(); if (prefixed_rules[rule_key] && prefixed_rules[rule_key] != newValue) { @@ -124,7 +130,7 @@ class css2less extends stream.Transform { } }, this); - _.forOwn(prefixed_rules, (function (value, k) { + _.forOwn(prefixed_rules, (value, k) => { let v = stringSplitAndTrim(value, /\s+/gi); if (!this.vendorMixins[k]) @@ -133,18 +139,18 @@ class css2less extends stream.Transform { if (normal_rules[k]) { delete normal_rules[k]; - let newKey = '.vp-' + k + '(' + v.join(', ') + ')'; + let newKey = `.vp-${k}(${v.join(', ')})`; normal_rules[newKey] = ''; } - }).bind(this)); + }); let result = []; - _.forOwn(normal_rules, (function (value, rule) { + _.forOwn(normal_rules, (value, rule) => { if (value.trim()) { rule += this.options.nameValueSeparator + value + ';\n'; } result.push(rule); - }).bind(this)); + }); return result.join(''); } @@ -156,7 +162,7 @@ class css2less extends stream.Transform { if (!selectors || !selectors.length) { if (this.options.updateColors) { - style = this.matchColor(style); + style = this.matchVariable(style); } if (this.options.vendorMixins) { style = this.matchVendorPrefixMixin(style); @@ -166,7 +172,7 @@ class css2less extends stream.Transform { } else { let first = stringSplitAndTrim(selectors[0], /\s*[,]\s*/gi) - .join(this.options.selectorSeparator); + .join(this.options.selectorSeparator); if (!tree.children) { tree.children = []; @@ -184,15 +190,26 @@ class css2less extends stream.Transform { } generateTree () { + let introCommentMatch = this.commentMatchReg.exec(this.css.trim()); + if (introCommentMatch && introCommentMatch.index === 0) { + this.introComment = introCommentMatch[0]; + } + + let searchImportFn = (match, q, path) => { + this.less.push(`@import "${path}";\n`); + return ''; + }; + let temp = stringSplitAndTrim(this.css, /\n/g) .join('') - .replace(/\/\*+[^\*]*\*+\//g, '') + .replace(this.commentMatchReg, '') + .replace(/@import\s+(?:url\()?([\"'])((?:\\\1|.)+?)\1[^;]*?;/gi, searchImportFn) .replace(/[^\{\}]+\{\s*\}/g, ' '); let styles = stringSplitAndTrim(temp, /[\{\}]/g); let styleDefs = []; - styles.forEach(function (val, i) { + styles.forEach((val, i) => { if (!(i & 1)) { styleDefs.push([val]); } @@ -201,22 +218,15 @@ class css2less extends stream.Transform { } }); - styleDefs.forEach(function (style) { + styleDefs.forEach(style => { let rule = style[0].replace(/\s*>\s*/gi, ' &>'); - if (~rule.indexOf('@import')) { - let import_rule = rule.match(/@import.*;/gi)[0]; - rule = rule.replace(/@import.*;/gi, ''); - this.addRule(this.tree, [], import_rule); - } - if (~rule.indexOf(',')) { this.addRule(this.tree, [rule], style[1]); } else { - let rules_split = stringSplitAndTrim(rule.replace(/[:]/gi, ' &:'), /\s+/gi).map(function (it) { - return it.replace(/[&][>]/gi, '& > '); - }); + let rules_split = stringSplitAndTrim(rule.replace(/:(?!:?\-)/gi, ' &:'), /\s+/gi) + .map(it => it.replace(/&>/gi, '& > ')); this.addRule(this.tree, rules_split, style[1]); } @@ -226,25 +236,26 @@ class css2less extends stream.Transform { buildMixinList (indent) { let less = []; - _.forOwn(this.vendorMixins, (function (v, k) { + _.forOwn(this.vendorMixins, (v, k) => { let args = []; for (let i = 0; i < v; i++) { args.push('@p' + i); } - less.push('.vp-', k, '(', args.join(', '), ')'); + less.push(`.vp-${k}(${args.join(', ')})`); less.push(this.options.blockFromNewLine ? '\n' : ' '); less.push('{\n'); - this.options.vendorPrefixesList.forEach(function (vp, i) { + this.options.vendorPrefixesList.forEach((vp, i) => { less.push(this.getIndent(indent + this.options.indentSize)); - less.push('-', vp, '-', k, this.options.nameValueSeparator, args.join(' '), ';\n'); + less.push(`-${vp}-${k}${this.options.nameValueSeparator}${args.join(' ')};\n`); }, this); - less.push(this.getIndent(indent + this.options.indentSize), k, this.options.nameValueSeparator, args.join(' '), ';\n'); + less.push(this.getIndent(indent + this.options.indentSize)); + less.push(`${k}${this.options.nameValueSeparator}${args.join(' ')};\n`); less.push('}\n'); - }).bind(this)); + }); if (less.length) { less.push('\n'); @@ -257,14 +268,28 @@ class css2less extends stream.Transform { indent = indent || 0; if (!tree) { - _.forOwn(this.colors, (function (v, k) { - this.less.push(v, this.options.nameValueSeparator, k, ';\n'); - }).bind(this)); - - if (this.colors_index > 0) { - this.less.push('\n'); + let colorVariables = []; + _.forOwn(this.vars, (v, k) => { + colorVariables.push(`${v}${this.options.nameValueSeparator}${k};\n`); + }); + + if (this.options.variablesPath) { + fs.appendFile(this.options.variablesPath, colorVariables.join('') + '\n', ()=>{}); + if (this.vars_index > 0) { + this.less.unshift(`@import (reference) "${path.basename(this.options.variablesPath)}";\n\n`); + this.less.push('\n'); + } + } + else { + this.less.push.apply(this.less, colorVariables); + if (this.vars_index > 0) { + this.less.push('\n'); + } } + if (this.introComment) { + this.less.unshift(this.introComment + '\n\n'); + } if (this.options.vendorMixins) { this.less.push(this.buildMixinList(indent)); } From 2a2ff9b6b25af1fa4e19f5ac5419170f03b38686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C3=B3rik?= Date: Tue, 15 Aug 2017 11:14:42 +0200 Subject: [PATCH 06/14] color variables sorted out --- cli.js | 1 - csscolors.json | 299 +++++++++++++++++++++++++------------------------ index.js | 21 +++- 3 files changed, 165 insertions(+), 156 deletions(-) diff --git a/cli.js b/cli.js index 22bd93b..3321c58 100644 --- a/cli.js +++ b/cli.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -'use strict'; const appname = 'css2less'; diff --git a/csscolors.json b/csscolors.json index e81b137..33a0bca 100644 --- a/csscolors.json +++ b/csscolors.json @@ -1,149 +1,150 @@ -[ - "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", - "darkgrey", - "darkgreen", - "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", - "grey", - "green", - "greenyellow", - "honeydew", - "hotpink", - "indianred", - "indigo", - "ivory", - "khaki", - "lavender", - "lavenderblush", - "lawngreen", - "lemonchiffon", - "lightblue", - "lightcoral", - "lightcyan", - "lightgoldenrodyellow", - "lightgray", - "lightgrey", - "lightgreen", - "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" -] +{ + "aliceblue": "#f0f8ff", + "antiquewhite": "#faebd7", + "aqua": "#00ffff", + "aquamarine": "#7fffd4", + "azure": "#f0ffff", + "beige": "#f5f5dc", + "bisque": "#ffe4c4", + "black": "#000000", + "blanchedalmond": "#ffebcd", + "blue": "#0000ff", + "blueviolet": "#8a2be2", + "brown": "#a52a2a", + "burlywood": "#deb887", + "cadetblue": "#5f9ea0", + "chartreuse": "#7fff00", + "chocolate": "#d2691e", + "coral": "#ff7f50", + "cornflowerblue": "#6495ed", + "cornsilk": "#fff8dc", + "crimson": "#dc143c", + "cyan": "#00ffff", + "darkblue": "#00008b", + "darkcyan": "#008b8b", + "darkgoldenrod": "#b8860b", + "darkgray": "#a9a9a9", + "darkgrey": "#a9a9a9", + "darkgreen": "#006400", + "darkkhaki": "#bdb76b", + "darkmagenta": "#8b008b", + "darkolivegreen": "#556b2f", + "darkorange": "#ff8c00", + "darkorchid": "#9932cc", + "darkred": "#8b0000", + "darksalmon": "#e9967a", + "darkseagreen": "#8fbc8f", + "darkslateblue": "#483d8b", + "darkslategray": "#2f4f4f", + "darkslategrey": "#2f4f4f", + "darkturquoise": "#00ced1", + "darkviolet": "#9400d3", + "deeppink": "#ff1493", + "deepskyblue": "#00bfff", + "dimgray": "#696969", + "dimgrey": "#696969", + "dodgerblue": "#1e90ff", + "firebrick": "#b22222", + "floralwhite": "#fffaf0", + "forestgreen": "#228b22", + "fuchsia": "#ff00ff", + "gainsboro": "#dcdcdc", + "ghostwhite": "#f8f8ff", + "gold": "#ffd700", + "goldenrod": "#daa520", + "gray": "#808080", + "grey": "#808080", + "green": "#008000", + "greenyellow": "#adff2f", + "honeydew": "#f0fff0", + "hotpink": "#ff69b4", + "indianred": "#cd5c5c", + "indigo": "#4b0082", + "ivory": "#fffff0", + "khaki": "#f0e68c", + "lavender": "#e6e6fa", + "lavenderblush": "#fff0f5", + "lawngreen": "#7cfc00", + "lemonchiffon": "#fffacd", + "lightblue": "#add8e6", + "lightcoral": "#f08080", + "lightcyan": "#e0ffff", + "lightgoldenrodyellow": "#fafad2", + "lightgray": "#d3d3d3", + "lightgrey": "#d3d3d3", + "lightgreen": "#90ee90", + "lightpink": "#ffb6c1", + "lightsalmon": "#ffa07a", + "lightseagreen": "#20b2aa", + "lightskyblue": "#87cefa", + "lightslategray": "#778899", + "lightslategrey": "#778899", + "lightsteelblue": "#b0c4de", + "lightyellow": "#ffffe0", + "lime": "#00ff00", + "limegreen": "#32cd32", + "linen": "#faf0e6", + "magenta": "#ff00ff", + "maroon": "#800000", + "mediumaquamarine": "#66cdaa", + "mediumblue": "#0000cd", + "mediumorchid": "#ba55d3", + "mediumpurple": "#9370db", + "mediumseagreen": "#3cb371", + "mediumslateblue": "#7b68ee", + "mediumspringgreen": "#00fa9a", + "mediumturquoise": "#48d1cc", + "mediumvioletred": "#c71585", + "midnightblue": "#191970", + "mintcream": "#f5fffa", + "mistyrose": "#ffe4e1", + "moccasin": "#ffe4b5", + "navajowhite": "#ffdead", + "navy": "#000080", + "oldlace": "#fdf5e6", + "olive": "#808000", + "olivedrab": "#6b8e23", + "orange": "#ffa500", + "orangered": "#ff4500", + "orchid": "#da70d6", + "palegoldenrod": "#eee8aa", + "palegreen": "#98fb98", + "paleturquoise": "#afeeee", + "palevioletred": "#db7093", + "papayawhip": "#ffefd5", + "peachpuff": "#ffdab9", + "peru": "#cd853f", + "pink": "#ffc0cb", + "plum": "#dda0dd", + "powderblue": "#b0e0e6", + "purple": "#800080", + "rebeccapurple": "#663399", + "red": "#ff0000", + "rosybrown": "#bc8f8f", + "royalblue": "#4169e1", + "saddlebrown": "#8b4513", + "salmon": "#fa8072", + "sandybrown": "#f4a460", + "seagreen": "#2e8b57", + "seashell": "#fff5ee", + "sienna": "#a0522d", + "silver": "#c0c0c0", + "skyblue": "#87ceeb", + "slateblue": "#6a5acd", + "slategray": "#708090", + "slategrey": "#708090", + "snow": "#fffafa", + "springgreen": "#00ff7f", + "steelblue": "#4682b4", + "tan": "#d2b48c", + "teal": "#008080", + "thistle": "#d8bfd8", + "tomato": "#ff6347", + "turquoise": "#40e0d0", + "violet": "#ee82ee", + "wheat": "#f5deb3", + "white": "#ffffff", + "whitesmoke": "#f5f5f5", + "yellow": "#ffff00", + "yellowgreen": "#9acd32" +} diff --git a/index.js b/index.js index f360e72..c022823 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,3 @@ -'use strict'; - const _ = require('lodash'); const cssc = require('./csscolors.json'); const stream = require('stream'); @@ -67,10 +65,21 @@ class css2less extends stream.Transform { } convertIfVariable (value) { - if (cssc.indexOf(value) < 0 && ( - /^#([0-9a-f]{6}|[0-9a-f]{3})$/i.test(value) || - /^url\(([\"'])((?:\\\1|.)+?)\1\)$/i.test(value) || - this.rgbaMatchReg.test(value))) { + if (/(^@var)|(^\d+(\.\d+)?(%|p[ctx]|e[mx]|[cm]m|in)?$)/i.test(value)) { + return value; + } + + // find the named value or convert hex triplet e.g. #639 to full hex color... + value = _.get(cssc, value.toLowerCase()) || value.replace(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/i, '#$1$1$2$2$3$3'); + + let isColor = false; + if (/^#[0-9a-f]{6}$/i.test(value)) { + isColor = true; + value = value.toLowerCase(); + } + + if (isColor || this.rgbaMatchReg.test(value) || + /^url\(([\"'])((?:\\\1|.)+?)\1\)$/i.test(value)) { if (!this.vars[value]) { this.vars[value] = '@var-' + this.options.filePathway.concat(this.vars_index).join('-'); From 0d9674a90eb07beb93da62337649a01d1607076d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C3=B3rik?= Date: Wed, 20 Sep 2017 16:41:53 +0200 Subject: [PATCH 07/14] implemented algorithm to keep comments of rules/properties in their position and reformat --- cli.js | 7 +++ cssprops.json | 1 + index.js | 115 +++++++++++++++++++++++++++++++++++++++++++------- utils.js | 27 ++++++++++++ 4 files changed, 135 insertions(+), 15 deletions(-) create mode 100644 cssprops.json create mode 100644 utils.js diff --git a/cli.js b/cli.js index 3321c58..eaa1fdc 100644 --- a/cli.js +++ b/cli.js @@ -1,4 +1,11 @@ #!/usr/bin/env node +/** + * css2less - entry point - command line interface + * + * Converter of pure CSS into the structured LESS keeping all the imports & comments + * and optionally extracting all the colors into variables. + * Original code by Serhey Shmyg, continued and extended by Martin Bórik. + */ const appname = 'css2less'; diff --git a/cssprops.json b/cssprops.json new file mode 100644 index 0000000..d16024d --- /dev/null +++ b/cssprops.json @@ -0,0 +1 @@ +["align-content:", "align-items:", "align-self:", "animation:", "animation-delay:", "animation-direction:", "animation-duration:", "animation-fill-mode:", "animation-iteration-count:", "animation-name:", "animation-play-state:", "animation-timing-function:", "backface-visibility:", "background:", "background-attachment:", "background-blend-mode:", "background-clip:", "background-color:", "background-image:", "background-origin:", "background-position:", "background-repeat:", "background-size:", "border:", "border-bottom:", "border-bottom-color:", "border-bottom-left-radius:", "border-bottom-right-radius:", "border-bottom-style:", "border-bottom-width:", "border-collapse:", "border-color:", "border-image:", "border-image-outset:", "border-image-repeat:", "border-image-slice:", "border-image-source:", "border-image-width:", "border-left:", "border-left-color:", "border-left-style:", "border-left-width:", "border-radius:", "border-right:", "border-right-color:", "border-right-style:", "border-right-width:", "border-spacing:", "border-style:", "border-top:", "border-top-color:", "border-top-left-radius:", "border-top-right-radius:", "border-top-style:", "border-top-width:", "border-width:", "bottom:", "box-shadow:", "box-sizing:", "caption-side:", "clear:", "clip:", "color:", "column-count:", "column-fill:", "column-gap:", "column-rule:", "column-rule-color:", "column-rule-style:", "column-rule-width:", "column-span:", "column-width:", "columns:", "content:", "counter-increment:", "counter-reset:", "cursor:", "direction:", "display:", "empty-cells:", "filter:", "flex:", "flex-basis:", "flex-direction:", "flex-flow:", "flex-grow:", "flex-shrink:", "flex-wrap:", "float:", "font:", "@font-face:", "font-family:", "font-size:", "font-size-adjust:", "font-stretch:", "font-style:", "font-variant:", "font-weight:", "hanging-punctuation:", "height:", "justify-content:", "@keyframes:", "left:", "letter-spacing:", "line-height:", "list-style:", "list-style-image:", "list-style-position:", "list-style-type:", "margin:", "margin-bottom:", "margin-left:", "margin-right:", "margin-top:", "max-height:", "max-width:", "@media:", "min-height:", "min-width:", "nav-down:", "nav-index:", "nav-left:", "nav-right:", "nav-up:", "opacity:", "order:", "outline:", "outline-color:", "outline-offset:", "outline-style:", "outline-width:", "overflow:", "overflow-x:", "overflow-y:", "padding:", "padding-bottom:", "padding-left:", "padding-right:", "padding-top:", "page-break-after:", "page-break-before:", "page-break-inside:", "perspective:", "perspective-origin:", "position:", "quotes:", "resize:", "right:", "tab-size:", "table-layout:", "text-align:", "text-align-last:", "text-decoration:", "text-decoration-color:", "text-decoration-line:", "text-decoration-style:", "text-indent:", "text-justify:", "text-overflow:", "text-shadow:", "text-transform:", "top:", "transform:", "transform-origin:", "transform-style:", "transition:", "transition-delay:", "transition-duration:", "transition-property:", "transition-timing-function:", "unicode-bidi:", "vertical-align:", "visibility:", "white-space:", "width:", "word-break:", "word-spacing:", "word-wrap:", "z-index:"] \ No newline at end of file diff --git a/index.js b/index.js index c022823..fdbd36d 100644 --- a/index.js +++ b/index.js @@ -1,10 +1,19 @@ +/** + * css2less - main code of the converter implemented into transform stream + * + * Converter of pure CSS into the structured LESS keeping all the imports & comments + * and optionally extracting all the colors into variables. + * Original code by Serhey Shmyg, continued and extended by Martin Bórik. + */ + const _ = require('lodash'); -const cssc = require('./csscolors.json'); const stream = require('stream'); const path = require('path'); const fs = require('fs'); -const stringSplitAndTrim = (str, del) => _.compact(str.split(del).map((item) => item.trim())); +const cssc = require('./csscolors.json'); +const cssp = require('./cssprops.json'); +const { stringSplitAndTrim, repeatReplaceUntil } = require('./utils'); //----------------------------------------------------------------------------- class css2less extends stream.Transform { @@ -29,7 +38,6 @@ class css2less extends stream.Transform { this.vendorPrefixesReg = new RegExp('^-(' + options.vendorPrefixesList.join('|') + ')-', 'gi'); this.rgbaMatchReg = /(((0|\d{1,}px)\s+?){3})?rgba?\((\d{1,3},\s*?){2}\d{1,3}(,\s*?0?\.\d+?)?\)$/gi; - this.commentMatchReg = /\/\*(?:[^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+\//g; this.css = ''; this.tree = {}; @@ -37,6 +45,7 @@ class css2less extends stream.Transform { this.vars = {}; this.vars_index = 0; this.vendorMixins = {}; + this.commentsMapper = []; } _transform (chunk, enc, done) { @@ -47,8 +56,8 @@ class css2less extends stream.Transform { _flush (done) { this.generateTree(); this.renderLess(); + this.finalize(); - this.push(this.less.join('')); done(); } @@ -199,24 +208,46 @@ class css2less extends stream.Transform { } generateTree () { - let introCommentMatch = this.commentMatchReg.exec(this.css.trim()); - if (introCommentMatch && introCommentMatch.index === 0) { - this.introComment = introCommentMatch[0]; - } + const introCommentRegex = /\/\*(?:[^*]|[\r\n]|(?:\*+(?:[^*/]|[\r\n])))*\*+\/\s*/; - let searchImportFn = (match, q, path) => { + const searchImportFn = (match, q, path) => { this.less.push(`@import "${path}";\n`); return ''; }; + const commentKeeperFn = (flags, char, content) => { + flags.done = true; - let temp = stringSplitAndTrim(this.css, /\n/g) - .join('') - .replace(this.commentMatchReg, '') - .replace(/@import\s+(?:url\()?([\"'])((?:\\\1|.)+?)\1[^;]*?;/gi, searchImportFn) - .replace(/[^\{\}]+\{\s*\}/g, ' '); + const idx = this.commentsMapper.length || 1; + const mark = `\u2588${idx}\u2502`; + + this.commentsMapper[idx] = content.trim(); + + if (char === ';') { + return mark + char; + } + + return char + mark; + }; + + let temp = this.css.trim().replace(introCommentRegex, (match, index) => { + if (index === 0) { + this.introComment = match.trim(); + return ''; + } + return match; + }); + + temp = stringSplitAndTrim( + repeatReplaceUntil(temp, + /([}{\u2502]|(?:\u2502?;))\s*\/\*((?:[^*]|[\r\n]|(\*+([^*/]|[\r\n])))*)\*+\/\s*/gi, + commentKeeperFn + ), /\n/g) + .join('') + .replace(/@import\s+(?:url\()?([\"'])((?:\\\1|.)+?)\1[^;]*?;/gi, searchImportFn) + .replace(/[^\{\}]+\{\s*\}/g, ' '); - let styles = stringSplitAndTrim(temp, /[\{\}]/g); let styleDefs = []; + let styles = stringSplitAndTrim(temp, /[\{\}]/g); styles.forEach((val, i) => { if (!(i & 1)) { @@ -364,6 +395,60 @@ class css2less extends stream.Transform { } } } + + finalize () { + let output = this.less.join(''); + + if (this.commentsMapper.length > 0) { + output = repeatReplaceUntil(output, + /(^[\t ]+)?\u2588(\d+)\u2502((?!\u2588);?)/gm, + (flags, indent, id, suffix, pos, haystack) => { + flags.done = true; + + indent = indent || ''; + + let comment = this.commentsMapper[+id]; + let isCommentedProperty = false; + + // commented css rule + if (cssp.some(rule => comment.indexOf(rule) === 0)) { + isCommentedProperty = true; + + let nextLineIndent = haystack + .substr(pos + id.length + 3, 80) + .match(/^[\r\n]+(\s*)/); + + if (nextLineIndent) { + indent = nextLineIndent[1]; + } + } + // multiline comment + else if (~comment.indexOf('\n')) { + comment = comment.replace(/\n/g, `\n${indent}`) + .replace(/^(\*\s+)/, `\n${indent} $1`) + '\n'; + } + + comment = `/* ${comment.replace(/^\*\s+/, '')} */`; + if (suffix === ';') { + if (isCommentedProperty) { + comment = `;\n${indent}${comment}`; + indent = ''; + } + else { + comment = '; ' + comment; + } + } + else { + comment += `\n${indent}`; + } + + return indent + comment; + } + ); + } + + this.push(output); + } }; //----------------------------------------------------------------------------- module.exports = css2less; diff --git a/utils.js b/utils.js new file mode 100644 index 0000000..cf3ccfc --- /dev/null +++ b/utils.js @@ -0,0 +1,27 @@ +/** + * css2less - some handy utilities + * + * Converter of pure CSS into the structured LESS keeping all the imports & comments + * and optionally extracting all the colors into variables. + * Original code by Serhey Shmyg, continued and extended by Martin Bórik. + */ + +const _ = require('lodash'); + +module.exports = { + 'stringSplitAndTrim': (str, del) => _.compact( + str.split(del).map((item) => item.trim()) + ), + + 'repeatReplaceUntil': (haystack, needle, callback) => { + let flags = {}; + do { + flags.done = false; + haystack = haystack.replace(needle, + (m, ...found) => callback.apply(null, found.unshift(flags) && found) + ); + } while (flags.done); + + return haystack; + } +}; From 37f7526e86bbd883cadae686005e7dd11bedfdfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C3=B3rik?= Date: Thu, 21 Sep 2017 16:09:22 +0200 Subject: [PATCH 08/14] fixed rules with just comment inside --- index.js | 76 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 43 insertions(+), 33 deletions(-) diff --git a/index.js b/index.js index fdbd36d..8cf63ad 100644 --- a/index.js +++ b/index.js @@ -178,12 +178,15 @@ class css2less extends stream.Transform { return; } - if (!selectors || !selectors.length) { - if (this.options.updateColors) { - style = this.matchVariable(style); - } - if (this.options.vendorMixins) { - style = this.matchVendorPrefixMixin(style); + if (!(selectors && selectors.length)) { + // test if it's not just comment in the rule... + if (!/^\u2588.+\u2502$/.test(style)) { + if (this.options.updateColors) { + style = this.matchVariable(style); + } + if (this.options.vendorMixins) { + style = this.matchVendorPrefixMixin(style); + } } tree.style = (tree.style || '') + style; @@ -260,16 +263,18 @@ class css2less extends stream.Transform { styleDefs.forEach(style => { let rule = style[0].replace(/\s*>\s*/gi, ' &>'); - - if (~rule.indexOf(',')) { - this.addRule(this.tree, [rule], style[1]); + let props = style[1]; + + let rules = [ rule ]; + if (!~rule.indexOf(',')) { + rules = stringSplitAndTrim( + rule.replace(/:(?!:?\-)/gi, ' &:'), /\s+/gi) + .map(it => it.replace(/&>/gi, '& > ') + ); } - else { - let rules_split = stringSplitAndTrim(rule.replace(/:(?!:?\-)/gi, ' &:'), /\s+/gi) - .map(it => it.replace(/&>/gi, '& > ')); - this.addRule(this.tree, rules_split, style[1]); - } + this.addRule(this.tree, rules, props); + }, this); } @@ -349,7 +354,7 @@ class css2less extends stream.Transform { if (i == 'style') { let rules = stringSplitAndTrim(element, /\;(?!base64)/gi); - this.less.push(rules.join(';\n'), '\n'); + this.less.push(rules.join(';\n') + '\n'); } else { if (index > 0) { @@ -370,14 +375,21 @@ class css2less extends stream.Transform { this.less.push('{\n'); - let style = element.style; - delete element.style; + if (element.style) { + let style = element.style; + delete element.style; - if (style) { - let temp = stringSplitAndTrim(style, /\;(?!base64)/gi); - let indented = temp.map(it => this.getIndent(indent + this.options.indentSize) + it + ';').join('\n'); + let ind = this.getIndent(indent + this.options.indentSize); - this.less.push(indented, '\n'); + // test if it's just comment in the rule... + if (/^\u2588.+\u2502$/.test(style)) { + this.less.push(ind + style); + } + else { + this.less.push(stringSplitAndTrim(style, /\;(?!base64)/gi) + .map(it => `${ind}${it};`) + .join('\n') + '\n'); + } if (children && children.length) { this.less.push(this.options.blockSeparator); @@ -408,18 +420,16 @@ class css2less extends stream.Transform { indent = indent || ''; let comment = this.commentsMapper[+id]; - let isCommentedProperty = false; + let commentedPropertyIndent = ''; - // commented css rule + // commented css property if (cssp.some(rule => comment.indexOf(rule) === 0)) { - isCommentedProperty = true; - - let nextLineIndent = haystack - .substr(pos + id.length + 3, 80) - .match(/^[\r\n]+(\s*)/); - - if (nextLineIndent) { - indent = nextLineIndent[1]; + commentedPropertyIndent = indent; + for (let i = pos - 1; i > 0; --i) { + if (~'\n\r'.indexOf(haystack[i])) { + commentedPropertyIndent = haystack.substr(++i, 80).match(/^\s*/)[0]; + break; + } } } // multiline comment @@ -430,8 +440,8 @@ class css2less extends stream.Transform { comment = `/* ${comment.replace(/^\*\s+/, '')} */`; if (suffix === ';') { - if (isCommentedProperty) { - comment = `;\n${indent}${comment}`; + if (commentedPropertyIndent) { + comment = `;\n${commentedPropertyIndent}${comment}`; indent = ''; } else { From 6726a74fb87eae91c97000fe2a2d00d4fb9dd615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C3=B3rik?= Date: Fri, 22 Sep 2017 09:22:11 +0200 Subject: [PATCH 09/14] some improvements of rule branching and code cleanup --- index.js | 58 +++++++++++++++++++++++++++----------------------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/index.js b/index.js index 8cf63ad..a94ffc4 100644 --- a/index.js +++ b/index.js @@ -38,6 +38,8 @@ class css2less extends stream.Transform { this.vendorPrefixesReg = new RegExp('^-(' + options.vendorPrefixesList.join('|') + ')-', 'gi'); this.rgbaMatchReg = /(((0|\d{1,}px)\s+?){3})?rgba?\((\d{1,3},\s*?){2}\d{1,3}(,\s*?0?\.\d+?)?\)$/gi; + this.introCommentRegex = /\/\*(?:[^*]|[\r\n]|(?:\*+(?:[^*/]|[\r\n])))*\*+\/\s*/; + this.markedCommentReg = /^\u2588.+\u2502$/; this.css = ''; this.tree = {}; @@ -180,7 +182,7 @@ class css2less extends stream.Transform { if (!(selectors && selectors.length)) { // test if it's not just comment in the rule... - if (!/^\u2588.+\u2502$/.test(style)) { + if (!this.markedCommentReg.test(style)) { if (this.options.updateColors) { style = this.matchVariable(style); } @@ -210,29 +212,23 @@ class css2less extends stream.Transform { } } - generateTree () { - const introCommentRegex = /\/\*(?:[^*]|[\r\n]|(?:\*+(?:[^*/]|[\r\n])))*\*+\/\s*/; - - const searchImportFn = (match, q, path) => { - this.less.push(`@import "${path}";\n`); - return ''; - }; - const commentKeeperFn = (flags, char, content) => { - flags.done = true; + commentKeeperCallback (flags, char, content) { + flags.done = true; - const idx = this.commentsMapper.length || 1; - const mark = `\u2588${idx}\u2502`; + const idx = this.commentsMapper.length || 1; + const mark = `\u2588${idx}\u2502`; - this.commentsMapper[idx] = content.trim(); + this.commentsMapper[idx] = content.trim(); - if (char === ';') { - return mark + char; - } + if (char === ';') { + return mark + char; + } - return char + mark; - }; + return char + mark; + } - let temp = this.css.trim().replace(introCommentRegex, (match, index) => { + generateTree () { + let temp = this.css.trim().replace(this.introCommentRegex, (match, index) => { if (index === 0) { this.introComment = match.trim(); return ''; @@ -243,10 +239,11 @@ class css2less extends stream.Transform { temp = stringSplitAndTrim( repeatReplaceUntil(temp, /([}{\u2502]|(?:\u2502?;))\s*\/\*((?:[^*]|[\r\n]|(\*+([^*/]|[\r\n])))*)\*+\/\s*/gi, - commentKeeperFn + this.commentKeeperCallback.bind(this) ), /\n/g) .join('') - .replace(/@import\s+(?:url\()?([\"'])((?:\\\1|.)+?)\1[^;]*?;/gi, searchImportFn) + .replace(/@import\s+(?:url\()?([\"'])((?:\\\1|.)+?)\1[^;]*?;/gi, + (match, q, path) => this.less.push(`@import "${path}";\n`) && '') .replace(/[^\{\}]+\{\s*\}/g, ' '); let styleDefs = []; @@ -262,18 +259,18 @@ class css2less extends stream.Transform { }); styleDefs.forEach(style => { - let rule = style[0].replace(/\s*>\s*/gi, ' &>'); - let props = style[1]; - + let rule = style[0]; let rules = [ rule ]; + if (!~rule.indexOf(',')) { - rules = stringSplitAndTrim( - rule.replace(/:(?!:?\-)/gi, ' &:'), /\s+/gi) - .map(it => it.replace(/&>/gi, '& > ') - ); + rule = rule.replace(/\s*([+~>])\s*/gi, ' &$1') + .replace(/(\w):(?!(:?\-)|not|dir|lang|first|last|nth|only)/gi, '$1 &:'); + + rules = stringSplitAndTrim(rule, /\s+/gi) + .map(it => it.replace(/&([+~>])/gi, '$1 ')); } - this.addRule(this.tree, rules, props); + this.addRule(this.tree, rules, style[1]); }, this); } @@ -382,7 +379,7 @@ class css2less extends stream.Transform { let ind = this.getIndent(indent + this.options.indentSize); // test if it's just comment in the rule... - if (/^\u2588.+\u2502$/.test(style)) { + if (this.markedCommentReg.test(style)) { this.less.push(ind + style); } else { @@ -412,6 +409,7 @@ class css2less extends stream.Transform { let output = this.less.join(''); if (this.commentsMapper.length > 0) { + // revert all marked comment into output... output = repeatReplaceUntil(output, /(^[\t ]+)?\u2588(\d+)\u2502((?!\u2588);?)/gm, (flags, indent, id, suffix, pos, haystack) => { From f70defe2e362608b94a4967eea5c96f37e096b76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C3=B3rik?= Date: Fri, 22 Sep 2017 13:50:28 +0200 Subject: [PATCH 10/14] implemented handy "usage comment" to each variable --- index.js | 57 ++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/index.js b/index.js index a94ffc4..3865bda 100644 --- a/index.js +++ b/index.js @@ -75,7 +75,7 @@ class css2less extends stream.Transform { return result; } - convertIfVariable (value) { + convertIfVariable (value, key) { if (/(^@var)|(^\d+(\.\d+)?(%|p[ctx]|e[mx]|[cm]m|in)?$)/i.test(value)) { return value; } @@ -92,12 +92,23 @@ class css2less extends stream.Transform { if (isColor || this.rgbaMatchReg.test(value) || /^url\(([\"'])((?:\\\1|.)+?)\1\)$/i.test(value)) { - if (!this.vars[value]) { - this.vars[value] = '@var-' + this.options.filePathway.concat(this.vars_index).join('-'); + let usages = []; + if (!this.vendorPrefixesReg.test(key)) { + usages = this.currentProcessingSelector.map(value => `${key} @\`${value}`); + } + + if (this.vars[value]) { + Array.prototype.push.apply(this.vars[value].usages, usages); + } + else { this.vars_index++; + this.vars[value] = { + 'id': '@var-' + this.options.filePathway.concat(this.vars_index).join('-'), + 'usages': usages + }; } - return this.vars[value]; + return this.vars[value].id; } return value; @@ -113,12 +124,13 @@ class css2less extends stream.Transform { return; } - value = value.replace(this.rgbaMatchReg, match => this.convertIfVariable(match)); - let values = value.split(/\s+/gi).map(v => this.convertIfVariable(v)); + value = value.replace(this.rgbaMatchReg, m => this.convertIfVariable(m, key)); + let values = value.split(/\s+/gi).map(v => this.convertIfVariable(v, key)); result.push((i ? '\n' : '') + key, this.options.nameValueSeparator, values.join(' ') + ';'); + }, this); return result.join(''); @@ -195,19 +207,17 @@ class css2less extends stream.Transform { } else { let first = stringSplitAndTrim(selectors[0], /\s*[,]\s*/gi) - .join(this.options.selectorSeparator); + .join(this.options.selectorSeparator); if (!tree.children) { tree.children = []; } - let node = tree[first]; - if (!node) { - node = tree[first] = {}; - } + let node = tree[first] || (tree[first] = {}); selectors.splice(0, 1); tree.children.push(node); + this.addRule(node, selectors, style); } } @@ -262,6 +272,11 @@ class css2less extends stream.Transform { let rule = style[0]; let rules = [ rule ]; + // store current processing selector(s) to put into variable's comment... + this.currentProcessingSelector = stringSplitAndTrim(rule, /\s*[,]\s*/gi) + .map(s => s.replace(/\u2588\d+\u2502/g, '').replace(/\s+/g, ' ')); + + if (!~rule.indexOf(',')) { rule = rule.replace(/\s*([+~>])\s*/gi, ' &$1') .replace(/(\w):(?!(:?\-)|not|dir|lang|first|last|nth|only)/gi, '$1 &:'); @@ -312,21 +327,27 @@ class css2less extends stream.Transform { if (!tree) { let colorVariables = []; _.forOwn(this.vars, (v, k) => { - colorVariables.push(`${v}${this.options.nameValueSeparator}${k};\n`); + let usg = v.usages.sort(); + if (usg.length > 1) { + colorVariables.push(`\n/*\n * ${usg.join('\n * ')}\n */\n`); + } + else { + colorVariables.push(usg.length ? `\n/* ${usg[0]} */\n`: ''); + } + + colorVariables.push(`${v.id}${this.options.nameValueSeparator}${k};\n`); }); if (this.options.variablesPath) { - fs.appendFile(this.options.variablesPath, colorVariables.join('') + '\n', ()=>{}); + fs.appendFile(this.options.variablesPath, colorVariables.join('').trim() + '\n', ()=>{}); if (this.vars_index > 0) { this.less.unshift(`@import (reference) "${path.basename(this.options.variablesPath)}";\n\n`); this.less.push('\n'); } } - else { - this.less.push.apply(this.less, colorVariables); - if (this.vars_index > 0) { - this.less.push('\n'); - } + else if (this.vars_index > 0) { + this.less.push.apply(this.less, colorVariables.filter((v, i) => (i & 1))); + this.less.push('\n'); } if (this.introComment) { From 2635321141ce6e7455b8ff5ec33da8a76914a16a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C3=B3rik?= Date: Fri, 22 Sep 2017 15:11:07 +0200 Subject: [PATCH 11/14] version increase --- README.md | 25 +++++++++++++++++-------- package.json | 2 +- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index ea4f785..dfd34c1 100644 --- a/README.md +++ b/README.md @@ -9,43 +9,52 @@ npm install -g css2less >+ `$ css2less [options] ` ### CLI Options ### -#### --indentSize +#### --indent-size Type: `number` Default value: `1` Desc: Indent size. -#### --indentSymbol +#### --indent-symbol Type: `string` Default value: `\t` Desc: Indent symbol. -#### --selectorSeparator +#### --selector-separator Type: `string` Default value: `,\n` Desc: Selector separator. -#### --blockSeparator +#### --block-separator Type: `string` Default value: `\n` Desc: Separator between blocks. -#### --blockFromNewLine +#### --block-on-newline Type: `boolean` Default value: `false` Desc: Start first '{' from the new line -#### --updateColors +#### --update-colors Type: `boolean` Default value: `true` Desc: Use variables for colors. -#### --vendorMixins +#### --vendor-mixins Type: `boolean` Default value: `true` Desc: Create function for vendor styles. +#### --variables-path +Type: `string` +Desc: Path to 'variables.less' file where will be all colors stored. + Defaultly was colors stored on the top of each file, but with this given path will be generated with name prepended by relative path where 'variables.less' was stored. + ## Pure JavaScript usage example: ```javascript +var fs = require('fs'); var css2less = require('css2less'); -var lessResult = css2less(inputCSSInString, options); + +fs.createReadStream(cssFilePath) + .pipe(new css2less(options)) + .pipe(fs.createWriteStream(lessFilePath)); ``` diff --git a/package.json b/package.json index d78f170..4ce67e1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "css2less", - "version": "0.2.0", + "version": "0.3.0", "description": "Convert css to less", "homepage": "https://github.com/mborik/css2less", "authors": { From 124e6262f6bbbd7fdc0f73bb1a95fbe97828865c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C3=B3rik?= Date: Thu, 28 Sep 2017 13:57:21 +0200 Subject: [PATCH 12/14] final touches and sequential file processing with Promises --- cli.js | 57 ++++++++++++++++++++++++++++++++-------------------- index.js | 31 +++++++++++++++++++--------- package.json | 15 ++++++++------ utils.js | 6 +++++- 4 files changed, 71 insertions(+), 38 deletions(-) diff --git a/cli.js b/cli.js index eaa1fdc..a556ebc 100644 --- a/cli.js +++ b/cli.js @@ -12,7 +12,10 @@ const appname = 'css2less'; const fs = require('fs'); const path = require('path'); const meow = require('meow'); +const promisePipe = require("promisepipe"); + const css2less = require('./index.js'); +const { processFiles } = require('./utils.js'); let cli = meow(` Usage: @@ -51,43 +54,53 @@ let cli = meow(` if (!cli.input.length) cli.showHelp(); -cli.input.forEach(function (file) { - let cwd = process.cwd(); - let filePath = path.resolve(cwd, file); +const cwd = process.cwd(); +const opt = Object.assign({ filePathway: [] }, cli.flags); + +processFiles(cli.input, file => { + const filePath = path.resolve(cwd, file); try { if (!fs.statSync(filePath).isFile()) { - throw new Error("ENOTFILE"); + throw new Error('ENOTFILE'); } } catch (err) { - console.error('Invalid file: "%s" (%s)', filePath, - (err.code || err.message || err)); - return; + return Promise.reject( + new Error(`Invalid file: '${file}' (${err.message || err.code})`) + ); } - let ext = path.extname(file); + const ext = path.extname(file); if (ext.toLowerCase() !== '.css') { - console.warn("%s hasn't proper extension, you've been warned!", file); - return; + return Promise.reject( + new Error(`Invalid file: '${file}': Not a proper extension!`) + ); } - let fileDir = path.dirname(filePath); - let fileBaseName = path.basename(file, ext); - let lessFile = path.join(fileDir, fileBaseName + '.less'); + const fileDir = path.dirname(filePath); + const fileBaseName = path.basename(file, ext); + const lessFile = path.join(fileDir, fileBaseName + '.less'); - if (cli.flags.variablesPath) { - let varDir = path.dirname(cli.flags.variablesPath); - let varRelPath = path.relative(varDir, fileDir); + if (opt.variablesPath) { + const varDir = path.dirname(opt.variablesPath); + const varRelPath = path.relative(varDir, fileDir); - cli.flags.filePathway = []; if (varRelPath.length > 0) { - cli.flags.filePathway = varRelPath.split(path.sep); + opt.filePathway = varRelPath.split(path.sep); } - cli.flags.filePathway.push(fileBaseName); + opt.filePathway.push(fileBaseName); } - fs.createReadStream(filePath) - .pipe(new css2less(cli.flags)) - .pipe(fs.createWriteStream(lessFile)); + console.log(`Converting '${file}'...`); + return promisePipe( + fs.createReadStream(filePath), + new css2less(opt), + fs.createWriteStream(lessFile) + ).then(stream => { + console.log(`> stored into '${lessFile}'.`); + }, err => { + console.error("> error: ", err.originalError); + fs.unlinkSync(lessFile); + }); }); diff --git a/index.js b/index.js index 3865bda..5a66f60 100644 --- a/index.js +++ b/index.js @@ -18,7 +18,7 @@ const { stringSplitAndTrim, repeatReplaceUntil } = require('./utils'); //----------------------------------------------------------------------------- class css2less extends stream.Transform { constructor (options) { - options = _.defaults({}, options, { + let opt = _.defaults({}, options, { filePathway: [], encoding: 'utf8', vendorPrefixesList: ['moz', 'o', 'ms', 'webkit'], @@ -32,11 +32,15 @@ class css2less extends stream.Transform { nameValueSeparator: ': ' }); - super({ encoding: options.encoding }); + super({ + objectMode: true, + highWaterMark: 16, + encoding: opt.encoding + }); - this.options = options; + this.options = opt; - this.vendorPrefixesReg = new RegExp('^-(' + options.vendorPrefixesList.join('|') + ')-', 'gi'); + this.vendorPrefixesReg = new RegExp('^-(' + opt.vendorPrefixesList.join('|') + ')-', 'gi'); this.rgbaMatchReg = /(((0|\d{1,}px)\s+?){3})?rgba?\((\d{1,3},\s*?){2}\d{1,3}(,\s*?0?\.\d+?)?\)$/gi; this.introCommentRegex = /\/\*(?:[^*]|[\r\n]|(?:\*+(?:[^*/]|[\r\n])))*\*+\/\s*/; this.markedCommentReg = /^\u2588.+\u2502$/; @@ -203,7 +207,10 @@ class css2less extends stream.Transform { } } - tree.style = (tree.style || '') + style; + if (!tree.style) { + tree.style = ''; + } + tree.style += (style || ''); } else { let first = stringSplitAndTrim(selectors[0], /\s*[,]\s*/gi) @@ -248,13 +255,16 @@ class css2less extends stream.Transform { temp = stringSplitAndTrim( repeatReplaceUntil(temp, - /([}{\u2502]|(?:\u2502?;))\s*\/\*((?:[^*]|[\r\n]|(\*+([^*/]|[\r\n])))*)\*+\/\s*/gi, + /(^|[}{\u2502]|(?:\u2502?;))\s*\/\*((?:[^*]|[\r\n]|(\*+([^*/]|[\r\n])))*)\*+\/\s*/gi, this.commentKeeperCallback.bind(this) ), /\n/g) .join('') .replace(/@import\s+(?:url\()?([\"'])((?:\\\1|.)+?)\1[^;]*?;/gi, - (match, q, path) => this.less.push(`@import "${path}";\n`) && '') - .replace(/[^\{\}]+\{\s*\}/g, ' '); + (match, q, path) => { + let lessPath = path.replace('.css', '.less'); + this.less.push(`@import "${lessPath}";\n`); + return ''; + }).replace(/[^\{\}]+\{\s*\}/g, ' '); let styleDefs = []; let styles = stringSplitAndTrim(temp, /[\{\}]/g); @@ -395,8 +405,11 @@ class css2less extends stream.Transform { if (element.style) { let style = element.style; - delete element.style; + if (typeof element.style !== 'string') { + console.warn("element.style invalid type!\n`%s`", JSON.stringify(element, null, '\t')); + } + delete element.style; let ind = this.getIndent(indent + this.options.indentSize); // test if it's just comment in the rule... diff --git a/package.json b/package.json index 4ce67e1..968e14e 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,18 @@ { "name": "css2less", - "version": "0.3.0", + "version": "0.4.0", "description": "Convert css to less", "homepage": "https://github.com/mborik/css2less", "authors": { "name": "Serhey Shmyg", "email": "serhey.shmyg.all@gmail.com" }, - "contributors": [{ - "name": "Martin Bórik", - "email": "mborik@users.sourceforge.net" - }], + "contributors": [ + { + "name": "Martin Bórik", + "email": "mborik@users.sourceforge.net" + } + ], "repository": { "type": "git", "url": "https://github.com/mborik/css2less.git" @@ -28,6 +30,7 @@ "license": "ISC", "dependencies": { "lodash": "^4.16.2", - "meow": "^3.7.0" + "meow": "^3.7.0", + "promisepipe": "^2.0.0" } } diff --git a/utils.js b/utils.js index cf3ccfc..9ddc98d 100644 --- a/utils.js +++ b/utils.js @@ -23,5 +23,9 @@ module.exports = { } while (flags.done); return haystack; - } + }, + + 'processFiles': (files, callback) => files.reduce((promise, file) => { + return promise.then(() => callback(file)); + }, Promise.resolve()) }; From 6a3f35902be6a0f2e81780af1bca9bbc6ddf2a06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C3=B3rik?= Date: Tue, 3 Oct 2017 09:30:16 +0200 Subject: [PATCH 13/14] fixed relative urls and implemeted advanced path relativizer for absolute server urls --- cli.js | 8 ++++++-- index.js | 38 ++++++++++++++++++++++++++++++++------ package.json | 2 +- utils.js | 4 +++- 4 files changed, 42 insertions(+), 10 deletions(-) diff --git a/cli.js b/cli.js index a556ebc..9018937 100644 --- a/cli.js +++ b/cli.js @@ -15,7 +15,7 @@ const meow = require('meow'); const promisePipe = require("promisepipe"); const css2less = require('./index.js'); -const { processFiles } = require('./utils.js'); +const utils = require('./utils.js'); let cli = meow(` Usage: @@ -57,7 +57,7 @@ if (!cli.input.length) const cwd = process.cwd(); const opt = Object.assign({ filePathway: [] }, cli.flags); -processFiles(cli.input, file => { +utils.processFiles(cli.input, file => { const filePath = path.resolve(cwd, file); try { @@ -82,6 +82,8 @@ processFiles(cli.input, file => { const fileBaseName = path.basename(file, ext); const lessFile = path.join(fileDir, fileBaseName + '.less'); + opt.absFilePath = opt.absBasePath = utils.path2posix(path.resolve(fileDir)); + if (opt.variablesPath) { const varDir = path.dirname(opt.variablesPath); const varRelPath = path.relative(varDir, fileDir); @@ -89,7 +91,9 @@ processFiles(cli.input, file => { if (varRelPath.length > 0) { opt.filePathway = varRelPath.split(path.sep); } + opt.filePathway.push(fileBaseName); + opt.absBasePath = utils.path2posix(path.resolve(varDir)); } console.log(`Converting '${file}'...`); diff --git a/index.js b/index.js index 5a66f60..593ec55 100644 --- a/index.js +++ b/index.js @@ -13,7 +13,7 @@ const fs = require('fs'); const cssc = require('./csscolors.json'); const cssp = require('./cssprops.json'); -const { stringSplitAndTrim, repeatReplaceUntil } = require('./utils'); +const { stringSplitAndTrim, repeatReplaceUntil, path2posix } = require('./utils'); //----------------------------------------------------------------------------- class css2less extends stream.Transform { @@ -87,18 +87,44 @@ class css2less extends stream.Transform { // find the named value or convert hex triplet e.g. #639 to full hex color... value = _.get(cssc, value.toLowerCase()) || value.replace(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/i, '#$1$1$2$2$3$3'); - let isColor = false; + let matches; + let processed = false; if (/^#[0-9a-f]{6}$/i.test(value)) { - isColor = true; + processed = true; value = value.toLowerCase(); } + else if (!!(matches = /^url\(([\"'])((?:\\\1|.)+?)\1\)$/i.exec(value))) { + processed = true; + + // path relativizer - try to find portion of url path in base file path + let file = path2posix(matches[2]).replace(/^\/([^\/]+?\/)+/, (match => { + let head = match.substr(0, match.length - 1); + let tail = ''; + let relative = ''; + + do { + if (~this.options.absFilePath.indexOf(head)) { + return relative + tail; + } + + let lastSlash = head.lastIndexOf('/'); + tail = head.substr(lastSlash + 1) + '/' + tail; + relative = '../' + relative; + + head = head.substr(0, lastSlash); + } while (head.length > 1); - if (isColor || this.rgbaMatchReg.test(value) || - /^url\(([\"'])((?:\\\1|.)+?)\1\)$/i.test(value)) { + return match; + })); + + let base = path.resolve(this.options.absFilePath, file); + value = `url("${path2posix(path.relative(this.options.absBasePath, base))}")`; + } + if (processed || this.rgbaMatchReg.test(value)) { let usages = []; if (!this.vendorPrefixesReg.test(key)) { - usages = this.currentProcessingSelector.map(value => `${key} @\`${value}`); + usages = this.currentProcessingSelector.map(value => `${key} @ ${value}`); } if (this.vars[value]) { diff --git a/package.json b/package.json index 968e14e..5396e23 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "css2less", - "version": "0.4.0", + "version": "0.5.0", "description": "Convert css to less", "homepage": "https://github.com/mborik/css2less", "authors": { diff --git a/utils.js b/utils.js index 9ddc98d..0682987 100644 --- a/utils.js +++ b/utils.js @@ -27,5 +27,7 @@ module.exports = { 'processFiles': (files, callback) => files.reduce((promise, file) => { return promise.then(() => callback(file)); - }, Promise.resolve()) + }, Promise.resolve()), + + 'path2posix': (str) => str.replace(/\\/g, '/') }; From 52610e7f247e5284ff24a2d300ad841bc5abb5b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C3=B3rik?= Date: Mon, 9 Oct 2017 15:00:36 +0200 Subject: [PATCH 14/14] bugfixes --- cli.js | 7 +++---- index.js | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cli.js b/cli.js index 9018937..46a2904 100644 --- a/cli.js +++ b/cli.js @@ -54,11 +54,10 @@ let cli = meow(` if (!cli.input.length) cli.showHelp(); -const cwd = process.cwd(); -const opt = Object.assign({ filePathway: [] }, cli.flags); - +// file processor... utils.processFiles(cli.input, file => { - const filePath = path.resolve(cwd, file); + const filePath = path.resolve(process.cwd(), file); + const opt = Object.assign({ filePathway: [] }, cli.flags); try { if (!fs.statSync(filePath).isFile()) { diff --git a/index.js b/index.js index 593ec55..e6276df 100644 --- a/index.js +++ b/index.js @@ -93,7 +93,7 @@ class css2less extends stream.Transform { processed = true; value = value.toLowerCase(); } - else if (!!(matches = /^url\(([\"'])((?:\\\1|.)+?)\1\)$/i.exec(value))) { + else if (!!(matches = /^url\(([\"']?)((?:\\\1|.)+?)\1\)$/i.exec(value))) { processed = true; // path relativizer - try to find portion of url path in base file path