Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: shinvdu/libcss2less
base: master
...
head fork: thomaspierson/libcss2less
compare: master
Checking mergeability… Don't worry, you can still create the pull request.
  • 2 commits
  • 3 files changed
  • 0 commit comments
  • 2 contributors
Commits on Jun 02, 2013
@grundprinzip grundprinzip Introducing color extraction and vendor prefix handling
This commit allows to automatically extract all colors that are used
in the css file into global variables that can be changed more easily.
In addition this adds an option to automatically extract vendor
prefixed logic into mixins so that they become easier digestible.

Essentially this means, that this block:

    .service-block .span4 {
      color: red;
      -webkit-transition:all 0.3s ease-in-out;
      -moz-transition:all 0.3s ease-in-out;
      -o-transition:all 0.3s ease-in-out;
      transition:all 0.3s ease-in-out;
    }

will become this in LESS

    @color0: red;

    .vp-transition(@p0; @p1; @p2) {
      -webkit-transition: @p0 @p1 @p2;
      -moz-transition: @p0 @p1 @p2;
      -o-transition: @p0 @p1 @p2;
      transition: @p0 @p1 @p2;
    }

    .service-block .span4 {
      color: @color0;
      .vp-transition(all; 0.3s; ease-in-out);
    }

The goal of these modifications is to make transitioning with a given
CSS code base easier. The new options are available to the command
line via:

    Usage: css2less [options] filename
    -c, --colors                     Automatically extract colors from less
    -m, --mixins                     Automatically extract vendor prefixed mixins
    -h, --help                       Show this message]

The code is portable Ruby 1.8.7 and Ruby 1.9
4aca3ff
Commits on Jun 03, 2013
@thomaspierson thomaspierson Merge pull request #18 from grundprinzip/master
Automatic Color Extraction and Vendor Prefixed Modules
6f1cf20
Showing with 307 additions and 5 deletions.
  1. +28 −1 bin/css2less
  2. +151 −4 lib/css2less.rb
  3. +128 −0 spec/css2less_spec.rb
View
29 bin/css2less
@@ -1,9 +1,36 @@
#!/usr/bin/env ruby
+require 'optparse'
require 'css2less'
+options = {}
+parser = OptionParser.new do |opts|
+ opts.banner = "Usage: css2less [options] filename"
+
+ opts.on("-c", "--colors", "Automatically extract colors from less") do |v|
+ options[:update_colors] = v
+ end
+
+ opts.on("-m", "--mixins", "Automatically extract vendor prefixed mixins") do |v|
+ options[:vendor_mixins] = v
+ end
+
+ opts.on_tail("-h", "--help", "Show this message") do
+ puts opts
+ exit
+ end
+
+end
+
+parser.parse!
+
+if ARGV.empty?
+ puts parser
+ exit
+end
+
css = File.read(ARGV[0])
-converter = Css2Less::Converter.new(css)
+converter = Css2Less::Converter.new(css, options)
converter.process_less
puts converter.get_less
View
155 lib/css2less.rb
@@ -15,18 +15,42 @@
#
# You should have received a copy of the GNU General Public License
# along with Foobar. If not, see <http://www.gnu.org/licenses/>.
+require 'set'
module Css2Less
+ # These are the official colors
+ CSS_COLORS = Set.new %w{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}
+ VENDOR_PREFIXES_LIST = %w{-moz -o -ms -webkit}
+ VENDOR_PREFIXES = /^(-moz|-o|-ms|-webkit)-/
+
require 'enumerator'
+ # This is the CSS2Less converter class.
class Converter
- def initialize(css=nil)
+
+ # This is the constructor of the class
+ #
+ # The following options are supported:
+ #
+ # * Matching colors in the CSS document and replacing them
+ # update_colors => true
+ def initialize(css=nil, options = {})
if not css.nil?
@css = css
end
+
+ # Option merge, instead of rails reverse_merge
+ @options = {:update_colors => false, :vendor_mixins => false}.merge(options)
+
@tree = {}
@less = ''
+
+ # We want to store all color information
+ @colors = {}
+
+ # Storing all vendor prefix mixins here
+ @vendor_mixins = {}
end
def process_less
@@ -57,9 +81,109 @@ def cleanup
@less = ''
end
+ # Split set of rules into single item
+ def convert_rules(data)
+ data.split(';').map { |s| s.strip }.reject { |s| s.empty? }
+ end
+
+ def color?(value)
+ if CSS_COLORS.include?(value.strip) || /^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/.match(value.strip) != nil ||
+ /(rgba?)\(.*\)/.match(value)
+ true
+ else
+ false
+ end
+ end
+
+ # Check if the global index contains the color and replace the value
+ # accordingly
+ def convert_if_color(color)
+ if color?(color)
+ unless @colors.key?(color.strip)
+ @colors[color.strip] = "@color#{@colors.size}"
+ end
+ @colors[color.strip]
+ else
+ color
+ end
+ end
+
+ # Try to match a color of a set of rules
+ def match_color(style)
+ convert_rules(style).map { |r|
+ (key, value) = r.split(":").map { |e| e.strip }
+ if value.nil?
+ "#{key}"
+ else
+ "#{key}: #{value.split(/\s+/).map { |e| convert_if_color(e) }.join(" ")}"
+ end
+ }.join(";\n") << ";\n"
+ end
+
+ def match_vendor_prefix_mixin(style)
+ normal_rules = {}
+ prefixed_rules = {}
+
+ # First identify all those vendor prefixed rules that are similar
+ convert_rules(style).each { |e|
+ (key, value) = e.split(":").map { |e| e.strip }
+ if value.nil?
+ normal_rules[key] = nil
+ else
+ # If this is a vendor prefixed rule, collect all similar ones in a
+ # single entry
+ if key.match(VENDOR_PREFIXES)
+ rule_key = key.gsub(VENDOR_PREFIXES, "")
+ val = value.split(/\s+/).map { |e| e.strip }
+
+ if prefixed_rules.key?(rule_key) && prefixed_rules[rule_key] != val
+ # Abort, because we have different values for different vendor
+ # prefixed values, this can only mean intended different behavior
+ # for different browsers
+ return style
+ end
+
+ prefixed_rules[rule_key] = val
+ else
+ normal_rules[key] = value
+ end
+ end
+ }
+
+ # Now we have all information to proceed. First, we check if the mixin is
+ # already available globally. If not we announce it
+ prefixed_rules.each { |k,v|
+ unless @vendor_mixins.key?(k)
+ @vendor_mixins[k] = v.size
+ end
+
+ if normal_rules.key?(k)
+ normal_rules.delete(k)
+ normal_rules[".vp-#{k}(#{v.join("; ")})"] = nil
+ end
+ }
+
+ result = normal_rules.to_a.map { |e|
+ val = "#{e[0]}"
+ val << ": #{e[1]}" unless e[1].nil?
+ val}.join(";\n") << ";\n"
+ end
+
+ # This method is called for each selector that we want to add as a rule.
+ # Since we have plain CSS rules here, we should try to bring some order into
+ # the chaos.
def add_rule(tree, selectors, style)
return if style.nil? || style.empty?
+
+ # Stop recursion and add styles
if selectors.empty?
+
+ # Match and replace global colors
+ style = match_color(style) if @options[:update_colors]
+
+ # Match and replace global mixins for vendor specific behavior
+ style = match_vendor_prefix_mixin(style) if @options[:vendor_mixins]
+
(tree[:style] ||= ';') << style
else
first, rest = selectors.first, selectors[1..-1]
@@ -92,18 +216,41 @@ def generate_tree
end
end
+
+ def build_mixin_list(indent)
+ less = ""
+ @vendor_mixins.each { |k,v|
+ args = Array(0..v-1).map { |e| "@p#{e}" }
+ less << ".vp-#{k}(#{args.join("; ")}) {\n"
+ VENDOR_PREFIXES_LIST.each { |vp|
+ less << " " * (indent+4) << "#{vp}-#{k}: #{args.join(" ")};\n"
+ }
+ less << " " * (indent+4) << "#{k}: #{args.join(" ")};\n"
+ less << "}\n"
+ }
+ less << "\n"
+ end
+
def render_less(tree=nil, indent=0)
if tree.nil?
- tree = @tree
+ # This is the initial node, add all global vars / mixins here
+ @colors.each { |k,v|
+ @less << "#{v}: #{k};\n"
+ }
+ @less << "\n" if @colors.size > 0
+
+ @less << build_mixin_list(indent) if @options[:vendor_mixins]
+
+ tree = @tree
end
tree.each do |element, children|
if element == :style
- @less = @less + children.split(';').map { |s| s.strip }.reject { |s| s.empty? }.map { |s| s + ";" }.join("\n") + "\n"
+ @less = @less + convert_rules(children).map { |s| s + ";" }.join("\n") + "\n"
else
@less = @less + ' ' * indent + element + " {\n"
style = children.delete(:style)
if style
- @less = @less + style.split(';').map { |s| s.strip }.reject { |s| s.empty? }.map { |s| ' ' * (indent+4) + s + ";" }.join("\n") + "\n"
+ @less = @less + convert_rules(style).map { |s| ' ' * (indent+4) + s + ";" }.join("\n") + "\n"
end
render_less(children, indent + 4)
@less = @less + ' ' * indent + "}\n"
View
128 spec/css2less_spec.rb
@@ -129,4 +129,132 @@
converter.process_less
converter.get_less.should eq(less)
end
+
+ it "should convert basic css colors into global variables" do
+ css = <<EOF
+#hello {
+ color: blue;
+}
+
+#hello #buddy {
+ background: red;
+ color: #333;
+}
+
+p {
+ color: rgb(1,1,1);
+ border: 1px dotted #e4e9f0;
+}
+EOF
+ less = <<EOF
+@color0: blue;
+@color1: red;
+@color2: #333;
+@color3: rgb(1,1,1);
+@color4: #e4e9f0;
+
+#hello {
+ color: @color0;
+ #buddy {
+ background: @color1;
+ color: @color2;
+ }
+}
+p {
+ color: @color3;
+ border: 1px dotted @color4;
+}
+EOF
+ converter = Css2Less::Converter.new(css, {:update_colors => true})
+ converter.process_less
+ converter.get_less.should eq(less)
+ end
+
+ it "should generate appropriate vendor mixins" do
+ css = <<EOF
+.thumbnail-kenburn img {
+ left:10px;
+ margin-left:-10px;
+ position:relative;
+ -webkit-transition: all 0.8s ease-in-out;
+ -moz-transition: all 0.8s ease-in-out;
+ -o-transition: all 0.8s ease-in-out;
+ -ms-transition: all 0.8s ease-in-out;
+ transition: all 0.8s ease-in-out;
+}
+.thumbnail-kenburn:hover img {
+ -webkit-transform: scale(1.2) rotate(2deg);
+ -moz-transform: scale(1.2) rotate(2deg);
+ -o-transform: scale(1.2) rotate(2deg);
+ -ms-transform: scale(1.2) rotate(2deg);
+ transform: scale(1.2) rotate(2deg);
+}
+
+/*Welcome Block*/
+.service-block .span4 {
+ padding:20px 30px;
+ text-align:center;
+ color: red;
+ margin-bottom:20px;
+ border-radius:2px;
+ -webkit-transition:all 0.3s ease-in-out;
+ -moz-transition:all 0.3s ease-in-out;
+ -o-transition:all 0.3s ease-in-out;
+ transition:all 0.3s ease-in-out;
+}
+EOF
+
+ less = <<EOF
+@color0: red;
+
+.vp-transition(@p0; @p1; @p2) {
+ -moz-transition: @p0 @p1 @p2;
+ -o-transition: @p0 @p1 @p2;
+ -ms-transition: @p0 @p1 @p2;
+ -webkit-transition: @p0 @p1 @p2;
+ transition: @p0 @p1 @p2;
+}
+.vp-transform(@p0; @p1) {
+ -moz-transform: @p0 @p1;
+ -o-transform: @p0 @p1;
+ -ms-transform: @p0 @p1;
+ -webkit-transform: @p0 @p1;
+ transform: @p0 @p1;
+}
+
+.thumbnail-kenburn {
+ img {
+ left: 10px;
+ margin-left: -10px;
+ position: relative;
+ .vp-transition(all;
+ 0.8s;
+ ease-in-out);
+ }
+}
+.thumbnail-kenburn:hover {
+ img {
+ .vp-transform(scale(1.2);
+ rotate(2deg));
+ }
+}
+.service-block {
+ .span4 {
+ padding: 20px 30px;
+ text-align: center;
+ color: @color0;
+ margin-bottom: 20px;
+ border-radius: 2px;
+ .vp-transition(all;
+ 0.3s;
+ ease-in-out);
+ }
+}
+EOF
+
+ converter = Css2Less::Converter.new(css, {:update_colors => true, :vendor_mixins => true})
+ converter.process_less
+ converter.get_less.should eq(less)
+
+ end
end

No commit comments for this range

Something went wrong with that request. Please try again.