diff --git a/.gitignore b/.gitignore index 6e92f57..3835d79 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ tags +*.gem diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8bf431a --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2010 by Andrew Timberlake + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.rdoc b/README.rdoc new file mode 100644 index 0000000..711c35e --- /dev/null +++ b/README.rdoc @@ -0,0 +1,11 @@ +=CSS + +A Ruby gem that allows the parsing and creation of css files. + +==Quick Start + + style = "body { background: #FFF url('image.png') no-repeat; }" + css = CSS::Parser.new.parse(style) + puts css['body'].background.color.to_s #=> #FFF + css['body'].background.position = 'top center' + puts css['body'].to_s #=> 'background: #FFF url('image.png') top center no-repeat' diff --git a/Rakefile b/Rakefile index a53570e..719f625 100644 --- a/Rakefile +++ b/Rakefile @@ -11,7 +11,7 @@ require 'rake/rdoctask' require 'rake/testtask' $LOAD_PATH.unshift('lib') -require 'css-parser' +require 'css' desc 'Run code coverage' task :coverage do |t| diff --git a/css.gemspec b/css.gemspec index 6e3f246..9a327c1 100644 --- a/css.gemspec +++ b/css.gemspec @@ -14,7 +14,6 @@ Gem::Specification.new do |s| ] s.files = [ "LICENSE", - "VERSION", ] + Dir['lib/**/*.rb'] s.homepage = %q{http://github.com/andrewtimberlake/css} s.rdoc_options = ["--charset=UTF-8"] diff --git a/lib/css/parser.rb b/lib/css/parser.rb index e30ac66..d673099 100644 --- a/lib/css/parser.rb +++ b/lib/css/parser.rb @@ -1,59 +1,88 @@ module CSS class Parser - def parse(file_name) - ruleset = RuleSet.new + # Parse a css style by string, file or file name + # + # ==Examples + # parser = CSS::Parser.new + # css = parser.parse("body { background: #FFF }") + # + # css = parser.parse("/home/andrew/style.css") + # + # File.open("style.css") do |file| + # css = parser.parse(file) + # end + def parse(file_file_name_or_style_string) + css_style = file_file_name_or_style_string + reset + lineno = 1 + if css_style.respond_to?(:size) && css_style.size < 255 && File.exists?(css_style) + File.open(css_style) do |file| + file.each_line do |line| + parse_line(line, lineno) + lineno += 1 + end + end + elsif css_style.respond_to?(:each_line) + css_style.each_line do |line| + parse_line(line, lineno) + lineno += 1 + end + end - state = :selector - previous_state = nil - previous_char = nil - buffer = [] - selector = nil + @ruleset + end - File.open(file_name) do |file| - file.each do |line| - char_count = 1 - line.each_char do |char| - buffer << char unless state == :comment + private + def reset + @ruleset = RuleSet.new - case char - when '{' - if state == :selector - buffer.pop - selector = buffer.join.strip - buffer.clear - state = :ruleset - else - unless state == :comment - raise CSSError.new(file.lineno, char_count, "Unexpected '{'") - end - end - when '}' - unless state == :comment - buffer.pop - ruleset << Rule.new(selector, buffer.join.strip) - state = :selector - buffer.clear - end - when '/' - if previous_char == '*' - buffer.pop - state = previous_state - end - when '*' - if previous_char == '/' - buffer.pop - previous_state = state - state = :comment + @state = :selector + @previous_state = nil + @previous_char = nil + @buffer = [] + @selector = nil + end + + def parse_line(line, lineno) + char_count = 1 + line.each_char do |char| + @buffer << char unless @state == :comment + + case char + when '{' + if @state == :selector + @buffer.pop + @selector = @buffer.join.strip + @buffer.clear + @state = :ruleset + else + unless @state == :comment + raise CSSError.new(lineno, char_count, "Unexpected '{'") end end - - previous_char = char unless char == ' ' - char_count += 1 + when '}' + unless @state == :comment + @buffer.pop + @ruleset << Rule.new(@selector, @buffer.join.strip) + @state = :selector + @buffer.clear + end + when '/' + if @previous_char == '*' + @buffer.pop + @state = @previous_state + end + when '*' + if @previous_char == '/' + @buffer.pop + @previous_state = @state + @state = :comment + end end + + @previous_char = char unless char == ' ' + char_count += 1 end end - - ruleset - end end end diff --git a/lib/css/rule.rb b/lib/css/rule.rb index e4983bc..97ea93d 100644 --- a/lib/css/rule.rb +++ b/lib/css/rule.rb @@ -1,4 +1,6 @@ # Shorthand conversions based on guide by Dustin Diaz - http://www.dustindiaz.com/css-shorthand/ +require 'set' + module CSS class Rule include Colors @@ -8,7 +10,7 @@ class Rule def initialize(selector, rule_text) @selector = selector - @properties = Set.new + @properties = ::Set.new @rules = {} parse_rules(@properties, @rules, rule_text) diff --git a/spec/lib/css/parser_spec.rb b/spec/lib/css/parser_spec.rb index 99dc619..d27c5ac 100644 --- a/spec/lib/css/parser_spec.rb +++ b/spec/lib/css/parser_spec.rb @@ -18,6 +18,41 @@ module CSS end end + context "parsing a CSS string" do + let(:style) { "body { background: #FFF url('image.png') no-repeat; color: #333 }" } + let(:css) { Parser.new.parse(style) } + + it "should return the body background color" do + css['body'].backgroundColor.should == '#FFF' + end + end + + context "parsing an open file" do + let(:css) do + css = nil + parser = Parser.new + File.open(fixture('style.css')) do |file| + css = parser.parse(file) + end + css + end + + it "should return the body background color" do + css['body'].color.should == '#333333' + end + end + + context "parsing two styles one after each other" do + it "should be able to parse both successfully" do + parser = Parser.new + css1 = parser.parse("body { color: #333 }") + css2 = parser.parse("body { color: #FFF }") + + css1['body'].color.should == '#333' + css2['body'].color.should == '#FFF' + end + end + context "A failing parse" do let(:error) do error = nil