module Ass class Parser OPEN_BRACE = " {\n" CLOSING_BRACE = "}\n" AT = /^ *@/ COMMENT = /^ *\/\*(.*)(?:\*\/)?$/ ASS_COMMENT = /&\/\// BLANK = /^ *$/ # TODO: Single \n char as well, update test.ass WORD = /([\w]+)/ HYPHENATED_WORD = /([\w\-]+)/ MIXIN = /^ *\+#{WORD}/ SELECTOR = /^([#\w\-\.,:\[\]\~\* ]+) *$/ # TODO: review CSS specification EQUALS = / *= */ CONSTANT = /:#{WORD}/ CONSTANT_VALUE = /([#\w]+)/ CONSTANT_WITH_VALUE = /^ *#{CONSTANT}#{EQUALS}#{CONSTANT_VALUE}/ PROPERTY = /^ *#{HYPHENATED_WORD}/ attr_accessor :ass, :constants, :css alias :to_s :css def initialize ass = '' @ass, @css, @constants = ass, '', {} end def parse! mixins = Hash.new '' in_block, in_mixin = false, false last_selector = '' lines = @ass.split "\n" lines.each do |line| case line when COMMENT @css << "/* #{$1.strip} */\n" when MIXIN if in_block next if in_mixin @css << mixins[$1] else in_mixin, in_block = $1, true end when CONSTANT_WITH_VALUE @constants[$1.to_sym] = $2 when BLANK if in_block in_block = false next if in_mixin @css << CLOSING_BRACE end when AT @css << line << "\n" else if not in_block and line.match SELECTOR in_block = true if nested_selector?($1, last_selector) next if in_mixin selector = splice_selectors last_selector, $1.strip @css << selector << OPEN_BRACE else in_mixin = false selector = $1.strip @css << selector << OPEN_BRACE end last_selector = selector elsif line.match PROPERTY if mixin = in_mixin mixins[mixin] << format_property(line) else @css << format_property(line) end end end end self end def splice_selectors selector_a, selector_b selector_b[0,0] = ' ' unless selector_b[0,1] == ':' selector_a.strip.gsub(/([^,]+),/, "\\1#{selector_b},") << selector_b end def nested_selector? selector_a, selector_b indents_in(selector_a) > indents_in(selector_b) end def indents_in string string.match(/^( *)/).captures.first.length / 2 rescue 0 end def format_property line line.gsub! PROPERTY, ' \1:' line.gsub! CONSTANT do |constant| constant[0,1] = '' @constants[constant.to_sym] || 'undefined_constant' end line << ";\n" end def self.parse ass new(ass).parse!.css end def self.parse_file file new(File.read(file)).parse!.css end end end