diff --git a/.gitignore b/.gitignore index 4288db2..fd69015 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ test/tmp test/version_tmp tmp _site +.vscode diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..73462a5 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.5.1 diff --git a/.travis.yml b/.travis.yml index 222078e..9323019 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: ruby rvm: - - 1.9.3 - - 2.0.0 + - 2.5 + - 2.4 + - 2.3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 08b367d..024f5ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ -## (Unreleased) ## +## 1.3.3 - 5/30/2014 ## + +* Upgrades parslet dependency to v1.6.1 and drops optimization monkeypatch +* Use correct terminology "declartions" instead of "rules" in the output + +## 1.3.2 - 6/22/2013 ## * Fixes attribute parsing bug that includes comments with braces +* Fixes parsing bug with empty media selectors #67 +* Fixes parsing bug with quoted brackets #72 +* Fixes parsing bug with nested media queries #73 +* Removes --compass-with-config deprecation +* Adds a CONTRIBUTING.md file with instructions ## 1.3.1 - 4/20/2013 ## diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..fc1d02f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,53 @@ +# Contributing to csscss + +First of all: Thanks! + +## Bugs & Feature Requests + +You can file bugs on the [issues +tracker](https://github.com/zmoazeni/csscss/issues), and tag them with +'bug'. Feel free to discuss features there, too. + +## Good report structure + +Please include the following four things in your report: + +1. The smallest CSS snippet to explain the problem. +2. What you did. +3. What you expected to happen. +4. What happened instead. + +The more information the better. + +## Contributing Code + +It's easy to contribute code to csscss: + +1. Fork csscss. +2. Create a topic branch - `git checkout -b my_branch` +3. Push to your branch - `git push origin my_branch` +4. Make sure the code follows the contributing guidelines below. +5. Create a [Pull Request](http://help.github.com/pull-requests/) from your + branch. +6. That's it! + +## First Time OSS Contributors + +Submitting your first pull request can be a little daunting. If this is +your first Open Source contribution, please mention it in your pull +request and I'll help guide you through the process. + +## Contributing Guidelines + +* Make sure your code follows typical [ruby +conventions](https://github.com/bbatsov/ruby-style-guide). +* Make sure the test suite is green. A simple `bundle exec rake test` +will run all the tests. +* Include a CHANGELOG entry with your change. Add an `(Unreleased)` +section at the top if one doesn't exist. +* Try to keep the git commits squashed and concise. Keep tests and code +changes together in the same commit. Keep only logical changes together +in a single commit. +* I strongly encourage [well written git commit +messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). +* Make sure all whitespace is trimmed from the end of lines. diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 492bdb3..30a5a63 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -4,3 +4,5 @@ * Ivan Lazarevic @kopipejst * Matt DuVall @mduvall twitter:@mduvall_ * Mekka Okereke @mekka @mekkaokereke +* JoseLuis Torres @joseluistorres twitter:@joseluis_torres +* Paul Simpson @prsimp diff --git a/Gemfile b/Gemfile index 46e2ea9..f34d154 100644 --- a/Gemfile +++ b/Gemfile @@ -4,16 +4,16 @@ source 'https://rubygems.org' gemspec # optional runtime dependencies -gem "sass" -gem "compass" -gem "less" -gem "therubyracer", :platform => :mri +gem 'sass' +gem 'compass' +gem 'less' +gem 'therubyracer', :platform => :mri -gem "rake", :require => false -gem "debugger" +gem 'rake', :require => false +gem 'byebug' -gem "minitest" -gem "m" -gem "minitest-rg" +gem 'minitest' +gem 'm' +gem 'minitest-rg' -gem "ruby-prof" +gem 'ruby-prof' diff --git a/Gemfile.lock b/Gemfile.lock index 8a089c2..33ae5c4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,55 +1,56 @@ PATH remote: . specs: - csscss (1.3.1) + csscss (1.3.3) colorize - parslet (~> 1.5) + parslet (>= 1.6.1, < 2.0) GEM remote: https://rubygems.org/ specs: - blankslate (2.1.2.4) - chunky_png (1.2.7) - colorize (0.5.8) - columnize (0.3.6) - commonjs (0.2.6) + byebug (10.0.2) + chunky_png (1.3.11) + colorize (0.8.1) + commonjs (0.2.7) compass (0.12.2) chunky_png (~> 1.2) fssm (>= 0.2.7) sass (~> 3.1) - debugger (1.5.0) - columnize (>= 0.3.1) - debugger-linecache (~> 1.2.0) - debugger-ruby_core_source (~> 1.2.0) - debugger-linecache (1.2.0) - debugger-ruby_core_source (1.2.0) + ffi (1.9.25) fssm (0.2.10) - less (2.3.1) - commonjs (~> 0.2.6) - libv8 (3.11.8.17) - m (1.3.1) + less (2.6.0) + commonjs (~> 0.2.7) + libv8 (3.16.14.19) + m (1.5.1) method_source (>= 0.6.7) rake (>= 0.9.2.2) - method_source (0.8.1) - minitest (2.12.1) - minitest-rg (1.1.0) - parslet (1.5.0) - blankslate (~> 2.0) - rake (10.0.3) - ref (1.0.4) - ruby-prof (0.13.0) - sass (3.2.7) - therubyracer (0.11.4) - libv8 (~> 3.11.8.12) + method_source (0.9.2) + minitest (5.11.3) + minitest-rg (5.2.0) + minitest (~> 5.0) + parslet (1.8.2) + rake (12.3.1) + rb-fsevent (0.10.3) + rb-inotify (0.9.10) + ffi (>= 0.5.0, < 2) + ref (2.0.0) + ruby-prof (0.17.0) + sass (3.7.2) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + therubyracer (0.12.3) + libv8 (~> 3.16.14.15) ref PLATFORMS ruby DEPENDENCIES + byebug compass csscss! - debugger less m minitest @@ -58,3 +59,6 @@ DEPENDENCIES ruby-prof sass therubyracer + +BUNDLED WITH + 1.16.4 diff --git a/README.md b/README.md index b229466..ea7df7d 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,21 @@ LESS requires an additional javascript runtime. rubies, and [therubyrhino](https://rubygems.org/gems/therubyrhino) on jruby. +## Are there any community extensions? ## + +* [compass-csscss](https://github.com/Comcast/compass-csscss) integrates csscss with [compass](http://compass-style.org/) projects. +* [grunt-csscss](https://github.com/peterkeating/grunt-csscss) a [grunt](http://gruntjs.com/) task to automatically run csscss. +* [gulp-csscss](https://www.npmjs.org/package/gulp-csscss/) a [gulp](http://gulpjs.com/) task to automatically run csscss. + +_Please submit [an issue](https://github.com/zmoazeni/csscss/issues/new) if you know of any others._ + +## Why doesn't csscss automatically remove duplications for me? ## + +I have been asked this a lot, but csscss is intentionally designed this +way. Check out [this +post](https://connectionrequired.com/blog/2013/04/why-csscss-doesnt-remove-duplication-for-you) +for my reasoning. + ## I found bugs ## This is still a new and evolving project. I heartily welcome feedback. @@ -72,12 +87,6 @@ If you find any issues, please report them on Please include the smallest CSS snippet to describe the issue and the output you expect to see. -## Who are you? ## - -My name is [Zach Moazeni](https://twitter.com/zmoazeni). I work for [an -awesome company](http://www.getharvest.com/). And [we're -hiring!](http://www.getharvest.com/careers) - ## I'm a dev, I can help ## Awesome! Thanks! Here are the steps I ask: diff --git a/csscss.gemspec b/csscss.gemspec index 07a2f31..85304e8 100644 --- a/csscss.gemspec +++ b/csscss.gemspec @@ -19,6 +19,6 @@ Gem::Specification.new do |gem| gem.required_ruby_version = ">= 1.9" - gem.add_dependency "parslet", "~> 1.5" + gem.add_dependency "parslet", ">= 1.6.1", "< 2.0" gem.add_dependency "colorize" end diff --git a/docker-ruby b/docker-ruby new file mode 100755 index 0000000..33ea97f --- /dev/null +++ b/docker-ruby @@ -0,0 +1,3 @@ +#!/bin/bash +VERSION=${VERSION:-latest} +docker run -it --rm -v $(pwd):/app -v csscss_gem_data:/usr/local/bundle -w /app ruby:$VERSION $@ diff --git a/lib/csscss.rb b/lib/csscss.rb index 29076ff..62f3d42 100644 --- a/lib/csscss.rb +++ b/lib/csscss.rb @@ -5,7 +5,6 @@ require "colorize" require "parslet" -require "csscss/parslet_optimizations" require "csscss/version" require "csscss/cli" diff --git a/lib/csscss/cli.rb b/lib/csscss/cli.rb index 9ca13fa..2b8430d 100644 --- a/lib/csscss/cli.rb +++ b/lib/csscss/cli.rb @@ -64,7 +64,7 @@ def execute end def parse(argv) - opts = OptionParser.new do |opts| + options = OptionParser.new do |opts| opts.banner = "Usage: csscss [files..]" opts.version = Csscss::VERSION @@ -98,10 +98,7 @@ def parse(argv) enable_compass if @compass = compass end - opts.on("--compass-with-config config", "Enable compass extensions when parsing sass/scss and pass config file", - "DEPRECATED: use --compass --require path/to/config.rb instead." - ) do |config| - deprecate("Use --compass --require #{config} instead of --compass-with-config #{config}") + opts.on("--compass-with-config config", "Enable compass extensions when parsing sass/scss and pass config file") do |config| @compass = true enable_compass(config) end @@ -131,11 +128,11 @@ def parse(argv) print_help(opts) end end - opts.parse!(argv) + options.parse!(argv) - print_help(opts) if argv.empty? + print_help(options) if argv.empty? rescue OptionParser::ParseError - print_help(opts) + print_help(options) end def print_help(opts) diff --git a/lib/csscss/parser/css.rb b/lib/csscss/parser/css.rb index 7a954c7..d0c6973 100644 --- a/lib/csscss/parser/css.rb +++ b/lib/csscss/parser/css.rb @@ -19,7 +19,7 @@ class Parser < Parslet::Parser rule(:blank_attribute) { str(";") >> space? } - rule(:attribute_value) { (str('/*').absent? >> match["^;}"]) | raw_comment } + rule(:attribute_value) { any_quoted { any } | (str('/*').absent? >> match["^;}"]) | raw_comment } rule(:attribute) { match["^:{}"].repeat(1).as(:property) >> @@ -66,7 +66,8 @@ class Parser < Parslet::Parser str("@") >> match["^{}"].repeat(1) >> str("{") >> - (comment | ruleset).repeat(0) >> + space? >> + (comment | ruleset | nested_ruleset).repeat(1) >> str("}") >> space? ).as(:nested_ruleset) @@ -94,8 +95,8 @@ class Parser < Parslet::Parser end class Transformer < Parslet::Transform - rule(nested_ruleset: sequence(:rulesets)) { - rulesets + rule(nested_ruleset: subtree(:rulesets)) { |context| + context[:rulesets].flatten } rule(import: simple(:import)) { [] } diff --git a/lib/csscss/parslet_optimizations.rb b/lib/csscss/parslet_optimizations.rb deleted file mode 100644 index 32b7130..0000000 --- a/lib/csscss/parslet_optimizations.rb +++ /dev/null @@ -1,77 +0,0 @@ -# These are my multibyte optimizations for parslet. -# More information can be found: -# https://github.com/kschiess/parslet/issues/73 -# https://github.com/kschiess/parslet/pull/74 -# https://github.com/zmoazeni/parslet/tree/optimized-multibyte-parsing - -require 'strscan' -require 'forwardable' - -module Parslet - class Source - extend Forwardable - - def initialize(str) - raise ArgumentError unless str.respond_to?(:to_str) - - @str = StringScanner.new(str) - - @line_cache = LineCache.new - @line_cache.scan_for_line_endings(0, str) - end - - def matches?(pattern) - regexp = pattern.is_a?(String) ? Regexp.new(Regexp.escape(pattern)) : pattern - !@str.match?(regexp).nil? - end - alias match matches? - - def consume(n) - original_pos = @str.pos - slice_str = n.times.map { @str.getch }.join - slice = Parslet::Slice.new( - slice_str, - original_pos, - @line_cache) - - return slice - end - - def chars_left - @str.rest_size - end - - def_delegator :@str, :pos - def pos=(n) - if n > @str.string.bytesize - @str.pos = @str.string.bytesize - else - @str.pos = n - end - end - - - class LineCache - def scan_for_line_endings(start_pos, buf) - return unless buf - - buf = StringScanner.new(buf) - return unless buf.exist?(/\n/) - - ## If we have already read part or all of buf, we already know about - ## line ends in that portion. remove it and correct cur (search index) - if @last_line_end && start_pos < @last_line_end - # Let's not search the range from start_pos to last_line_end again. - buf.pos = @last_line_end - start_pos - end - - ## Scan the string for line endings; store the positions of all endings - ## in @line_ends. - while buf.skip_until(/\n/) - @last_line_end = start_pos + buf.pos - @line_ends << @last_line_end - end - end - end - end -end diff --git a/lib/csscss/reporter.rb b/lib/csscss/reporter.rb index 8509872..96f428c 100644 --- a/lib/csscss/reporter.rb +++ b/lib/csscss/reporter.rb @@ -13,7 +13,7 @@ def report(options = {}) selector_groups = selector_groups.map {|selectors| "{#{maybe_color(selectors, :red, should_color)}}" } last_selector = selector_groups.pop count = declarations.size - io.puts %Q(#{selector_groups.join(", ")} AND #{last_selector} share #{maybe_color(count, :red, should_color)} rule#{"s" if count > 1}) + io.puts %Q(#{selector_groups.join(", ")} AND #{last_selector} share #{maybe_color(count, :red, should_color)} declaration#{"s" if count > 1}) if verbose declarations.each {|dec| io.puts(" - #{maybe_color(dec, :yellow, should_color)}") } end diff --git a/lib/csscss/types.rb b/lib/csscss/types.rb index 32afd3a..3d01f4d 100644 --- a/lib/csscss/types.rb +++ b/lib/csscss/types.rb @@ -1,9 +1,5 @@ module Csscss - class Declaration < Struct.new(:property, :value, :parents) - def self.from_csspool(dec) - new(dec.property.to_s.downcase, dec.expressions.join(" ").downcase) - end - + Declaration = Struct.new(:property, :value, :parents) do def self.from_parser(property, value, clean = true) value = value.to_s property = property.to_s @@ -79,7 +75,7 @@ def normalize_value(value) end end - class Selector < Struct.new(:selectors) + Selector = Struct.new(:selectors) do def self.from_parser(selectors) new(selectors.to_s.strip) end @@ -97,6 +93,5 @@ def inspect end end - class Ruleset < Struct.new(:selectors, :declarations) - end + Ruleset = Struct.new(:selectors, :declarations) end diff --git a/lib/csscss/version.rb b/lib/csscss/version.rb index b122c60..7623093 100644 --- a/lib/csscss/version.rb +++ b/lib/csscss/version.rb @@ -1,3 +1,3 @@ module Csscss - VERSION = "1.3.1" + VERSION = "1.3.3" end diff --git a/test/csscss/parser/common_test.rb b/test/csscss/parser/common_test.rb index 41c4b92..c803bce 100644 --- a/test/csscss/parser/common_test.rb +++ b/test/csscss/parser/common_test.rb @@ -139,6 +139,14 @@ class CommonTest @parser.url.must_parse "url(data:image/svg+xml;base64,IMGDATAGOESHERE4/5/h/1+==)" @parser.url.must_parse "url('data:image/svg+xml;base64,IMGDATAGOESHERE4/5/h/1+==')" end + + it "parses specials characters" do + @parser.between('"', '"') { @parser.symbol("{") }.must_parse '"{"' + @parser.between('"', '"') { @parser.symbol("}") }.must_parse '"}"' + @parser.between('"', '"') { @parser.symbol("%") }.must_parse '"%"' + @parser.double_quoted { @parser.symbol("{") }.must_parse %("{") + @parser.single_quoted { @parser.symbol('{') }.must_parse %('{') + end end end end diff --git a/test/csscss/parser/css_test.rb b/test/csscss/parser/css_test.rb index ad0c45b..68e25d1 100644 --- a/test/csscss/parser/css_test.rb +++ b/test/csscss/parser/css_test.rb @@ -119,6 +119,15 @@ module Css } } + @media only screen { + @-webkit-keyframes webkitSiblingBugfix { + from { position: relative; } + to { position: relative; } + } + + a { position: relative } + } + h1 { outline: 1px; } @@ -127,10 +136,34 @@ module Css trans(css).must_equal([ rs(sel("#foo"), [dec("background-color", "black")]), rs(sel("#bar"), [dec("display", "none")]), + rs(sel("from"), [dec("position", "relative")]), + rs(sel("to"), [dec("position", "relative")]), + rs(sel("a"), [dec("position", "relative")]), rs(sel("h1"), [dec("outline", "1px")]) ]) end + it "recognizes empty @media queries with no spaces" do + css = %$ + @media (min-width: 768px) and (max-width: 979px) {} + $ + + trans(css).must_equal([ + rs(sel("@media (min-width: 768px) and (max-width: 979px)"), []), + ]) + end + + it "recognizes empty @media queries with spaces" do + css = %$ + @media (min-width: 768px) and (max-width: 979px) { + } + $ + + trans(css).must_equal([ + rs(sel("@media (min-width: 768px) and (max-width: 979px)"), []), + ]) + end + it "ignores @import statements" do css = %$ @import "foo.css"; @@ -212,6 +245,47 @@ module Css dec("display", "block")]) ]) end + + it "parses attributes with special characters" do + css = %$ + + #menu a::before { + content: "{"; + left: -6px; + } + + #menu a::after { + content: "}"; + right: -6px; + } + + #menu a::weird { + content: "@"; + up: -2px; + } + + #menu a::after_all { + content: '{'; + right: -6px; + } + + $ + + trans(css).must_equal([ + rs(sel("#menu a::before"), [dec("content", '"{"'), + dec("left", "-6px") + ]), + rs(sel("#menu a::after"), [dec("content", '"}"'), + dec("right", "-6px") + ]), + rs(sel("#menu a::weird"), [dec("content", '"@"'), + dec("up", "-2px") + ]), + rs(sel("#menu a::after_all"), [dec("content", "'{'"), + dec("right", "-6px") + ]) + ]) + end end end end diff --git a/test/csscss/reporter_test.rb b/test/csscss/reporter_test.rb index 17d3607..eb8fcbf 100644 --- a/test/csscss/reporter_test.rb +++ b/test/csscss/reporter_test.rb @@ -12,19 +12,19 @@ module Csscss }) expected =<<-EXPECTED -{.foo} AND {.bar} share 2 rules -{h1, h2}, {.foo} AND {.baz} share 1 rule -{h1, h2} AND {.bar} share 1 rule +{.foo} AND {.bar} share 2 declarations +{h1, h2}, {.foo} AND {.baz} share 1 declaration +{h1, h2} AND {.bar} share 1 declaration EXPECTED reporter.report(color:false).must_equal expected expected =<<-EXPECTED -{.foo} AND {.bar} share 2 rules +{.foo} AND {.bar} share 2 declarations - width: 1px - border: black -{h1, h2}, {.foo} AND {.baz} share 1 rule +{h1, h2}, {.foo} AND {.baz} share 1 declaration - display: none -{h1, h2} AND {.bar} share 1 rule +{h1, h2} AND {.bar} share 1 declaration - position: relative EXPECTED reporter.report(verbose:true, color:false).must_equal expected diff --git a/test/csscss/sass_include_extensions_test.rb b/test/csscss/sass_include_extensions_test.rb index 76ace2e..13f0ea8 100644 --- a/test/csscss/sass_include_extensions_test.rb +++ b/test/csscss/sass_include_extensions_test.rb @@ -61,7 +61,7 @@ module Csscss /* CSSCSS END MIXIN: foo */ } CSS - Sass::Engine.new("@import '#{f.path}'", syntax: :scss, cache: false).render.must_equal(css) + Sass::Engine.new("@import '#{File.basename(f.path)}'", syntax: :scss, cache: false, load_paths: ["/tmp"]).render.must_equal(css) end end end diff --git a/test/just_parse.rb b/test/just_parse.rb index ef9f1f6..47c7249 100644 --- a/test/just_parse.rb +++ b/test/just_parse.rb @@ -1,6 +1,6 @@ #! /usr/bin/env ruby -require "debugger" +require "byebug" require "csscss" raise "need a file name" unless ARGV[0] diff --git a/test/test_helper.rb b/test/test_helper.rb index d0c7ea5..4679d7a 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,11 +1,11 @@ -require "rubygems" -require "bundler/setup" +require 'rubygems' +require 'bundler/setup' -require "minitest/autorun" -require "minitest/rg" -require "debugger" +require 'minitest/autorun' +require 'minitest/rg' +require 'byebug' -require "csscss" +require 'csscss' module TypeHelpers def sel(s)