class String # see if string has any content def blank?; self.length.zero?; end # strip space after :, remove newlines, replace multiple spaces with only one space, remove comments def strip_space! replace self.gsub(/:\s*/, ':').gsub(/\n/, '').gsub(/\s+/, ' ').gsub(/(\/\*).*?(\*\/)/, '') end # remove newlines, insert space after comma, replace two spaces with one space after comma def strip_selector_space! replace self.gsub(/(\n)/, '').gsub(',', ', ').gsub(', ', ', ') end # remove leading whitespace, remove end whitespace def strip_side_space! replace self.gsub(/^\s+/, '').gsub(/\s+$/, $/) end end class NilClass def blank?; true; end end module Blueprint # Strips out most whitespace and can return a hash or string of parsed data class CSSParser attr_accessor :namespace attr_reader :css_output, :raw_data # ==== Options # * css_string String of CSS data # * options # * :namespace -- Namespace to use when generating output def initialize(css_string = "", options = {}) @raw_data = css_string @namespace = options[:namespace] || "" compress(@raw_data) end # returns string of CSS which can be saved to a file or otherwise def to_s @css_output end # returns a hash of all CSS data passed # # ==== Options # * data -- CSS string; defaults to string passed into the constructor def parse(data = nil) data ||= @raw_data # wrapper array holding hashes of css tags/rules css_out = [] # clear initial spaces data.strip_side_space!.strip_space! # split on end of assignments data.split('}').each_with_index do |assignments, index| # split again to separate tags from rules tags, styles = assignments.split('{').map{|a| a.strip_side_space!} unless styles.blank? # clean up tags and apply namespaces as needed tags.strip_selector_space! tags.gsub!(/\./, ".#{namespace}") unless namespace.blank? # split on semicolon to iterate through each rule rules = [] styles.split(';').each do |key_val_pair| unless key_val_pair.nil? # split by property/val and append to rules array with correct declaration property, value = key_val_pair.split(':').map{|kv| kv.strip_side_space!} break unless property && value rules << "#{property}:#{value};" end end # now keeps track of index as hashes don't keep track of position (which will be fixed in Ruby 1.9) css_out << {:tags => tags, :rules => rules.to_s, :idx => index} unless tags.blank? || rules.to_s.blank? end end css_out end private def compress(data) @css_output = "" parse(data).flatten.sort_by {|i| i[:idx]}.each do |line| @css_output += "#{line[:tags]} {#{line[:rules]}}\n" end end end end RAILS_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..')) CSS_PATH = RAILS_ROOT + "/public/stylesheets" Dir.glob(CSS_PATH + '/*.dev.css').each do |source| source_code = '' target_path = source.gsub(/\.dev\.css$/, '.css') File.read(source).scan(/import[\s'"]+(.+?)[\s'"]+/mix).each do |file| File.readlines("#{CSS_PATH}/#{file}").each do |line| source_code << line.gsub(/\.\.\/\.\.\/\.\.\//, '../') end end File.open(target_path, 'w') do |file| file.write Blueprint::CSSParser.new(source_code).to_s end end