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