diff --git a/lib/hamlit/attribute_compiler.rb b/lib/hamlit/attribute_compiler.rb deleted file mode 100644 index 9b13d16f..00000000 --- a/lib/hamlit/attribute_compiler.rb +++ /dev/null @@ -1,141 +0,0 @@ -require 'ripper' -require 'hamlit/filter' -require 'hamlit/concerns/balanceable' - -# AttributeCompiler compiles only old-style attribute, which is -# surrounded by brackets. -module Hamlit - class AttributeCompiler < Hamlit::Filter - include Concerns::Balanceable - - TYPE_POSITION = 1 - - def on_haml_attrs(*exps) - attrs = [] - exps.map do |exp| - case exp - when /\A{.+}\Z/ - attrs += compile_attribute(exp) - else - attrs << compile(exp) - end - end - [:haml, :attrs, *attrs] - end - - private - - def compile_attribute(str) - tokens = Ripper.lex(str) - attrs = parse_attributes(tokens) - flatten_attributes(attrs).map do |key, value| - [:html, :attr, key, [:dynamic, value]] - end - end - - def flatten_attributes(attributes) - flattened = {} - - attributes.each do |key, value| - case value - when Hash - flatten_attributes(value).each do |k, v| - flattened["#{key}-#{k}"] = v - end - else - flattened[key] = value - end - end - flattened - end - - # Parse brace-balanced tokens and return the result as hash - def parse_attributes(tokens) - tokens = tokens.slice(1..-2) # strip braces - attributes = {} - - while tokens && tokens.any? - key = read_key!(tokens) - val = read_value!(tokens) - attributes[key] = val if key && val - - skip_tokens!(tokens, :on_sp) - raise SyntaxError if tokens.any? && type_of(tokens.shift) != :on_comma - end - - attributes - end - - def read_key!(tokens) - skip_tokens!(tokens, :on_sp) - - (row, col), type, str = tokens.shift - case type - when :on_label - str.gsub!(/:\Z/, '') - when :on_symbeg - if %w[:" :'].include?(str) - str = read_string!(tokens) - else - (row, col), type, str = tokens.shift - end - assert_rocket!(tokens) - when :on_tstring_beg - str = read_string!(tokens) - assert_rocket!(tokens) - end - str - end - - def read_string!(tokens) - (row, col), type, str = tokens.shift - return '' if type == :on_tstring_end - - raise SyntaxError if type_of(tokens.shift) != :on_tstring_end - str - end - - def read_value!(tokens) - result = '' - skip_tokens!(tokens, :on_sp) - - if type_of(tokens.first) == :on_lbrace - hash = fetch_balanced_braces(tokens) - hash.length.times { tokens.shift } - return parse_attributes(hash) - end - - while token = tokens.shift - (row, col), type, str = token - case type - when :on_sp - next - when :on_comma - tokens.unshift(token) - break - end - - result += str - end - result - end - - def skip_tokens!(tokens, *types) - while types.include?(type_of(tokens.first)) - tokens.shift - end - end - - def assert_rocket!(tokens, *types) - skip_tokens!(tokens, :on_sp) - (row, col), type, str = tokens.shift - - raise SyntaxError unless type == :on_op && str == '=>' - end - - def type_of(token) - return nil unless token - token[TYPE_POSITION] - end - end -end diff --git a/lib/hamlit/compiler.rb b/lib/hamlit/compiler.rb index be156551..4506b6ea 100644 --- a/lib/hamlit/compiler.rb +++ b/lib/hamlit/compiler.rb @@ -2,7 +2,6 @@ require 'hamlit/compilers/attributes' require 'hamlit/compilers/doctype' require 'hamlit/compilers/dynamic' require 'hamlit/compilers/filter' -require 'hamlit/compilers/new_attribute' require 'hamlit/compilers/preserve' require 'hamlit/compilers/script' require 'hamlit/compilers/text' @@ -14,7 +13,6 @@ module Hamlit include Compilers::Doctype include Compilers::Dynamic include Compilers::Filter - include Compilers::NewAttribute include Compilers::Preserve include Compilers::Script include Compilers::Text diff --git a/lib/hamlit/compilers/attributes.rb b/lib/hamlit/compilers/attributes.rb index 84b80055..60eae11a 100644 --- a/lib/hamlit/compilers/attributes.rb +++ b/lib/hamlit/compilers/attributes.rb @@ -1,17 +1,14 @@ +require 'hamlit/compilers/new_attribute' +require 'hamlit/compilers/old_attribute' + module Hamlit module Compilers module Attributes - def on_haml_attrs(*exps) - attrs = [] - exps.each do |exp| - case exp - when /\A(.+)\Z/ - attrs += compile_new_attribute(exp) - else - attrs << compile(exp) - end - end + include Compilers::NewAttribute + include Compilers::OldAttribute + def on_haml_attrs(*attrs) + attrs = compile_attributes(attrs) attrs = join_ids(attrs) attrs = combine_classes(attrs) attrs = pull_class_first(attrs) @@ -20,6 +17,21 @@ module Hamlit private + def compile_attributes(exps) + attrs = [] + exps.each do |exp| + case exp + when /\A\(.+\)\Z/ + attrs += compile_new_attribute(exp) + when /\A{.+}\Z/ + attrs += compile_old_attribute(exp) + else + attrs << compile(exp) + end + end + attrs + end + def pull_class_first(attrs) class_attrs = filter_attrs(attrs, 'class') combine_classes(class_attrs) + (attrs - class_attrs) diff --git a/lib/hamlit/compilers/new_attribute.rb b/lib/hamlit/compilers/new_attribute.rb index 4291945f..78c0b74e 100644 --- a/lib/hamlit/compilers/new_attribute.rb +++ b/lib/hamlit/compilers/new_attribute.rb @@ -1,6 +1,6 @@ require 'ripper' -# NewAttributeCompiler compiles new-style attributes, which is +# This module compiles new-style attributes, which is # surrounded by parentheses. module Hamlit module Compilers @@ -9,7 +9,7 @@ module Hamlit def compile_new_attribute(str) str = str.gsub(/\A\(|\)\Z/, '') - attrs = parse_attributes(str) + attrs = parse_new_attributes(str) attrs.map do |key, value| [:html, :attr, key, [:dynamic, value]] end @@ -17,7 +17,7 @@ module Hamlit private - def parse_attributes(str) + def parse_new_attributes(str) attributes = {} while str.length > 0 diff --git a/lib/hamlit/compilers/old_attribute.rb b/lib/hamlit/compilers/old_attribute.rb new file mode 100644 index 00000000..f66e1434 --- /dev/null +++ b/lib/hamlit/compilers/old_attribute.rb @@ -0,0 +1,130 @@ +require 'ripper' +require 'hamlit/concerns/balanceable' + +# This module compiles only old-style attribute, which is +# surrounded by brackets. +# FIXME: remove duplicated code with NewAttribute +module Hamlit + module Compilers + module OldAttribute + include Concerns::Balanceable + + TYPE_POSITION = 1 + + def compile_old_attribute(str) + tokens = Ripper.lex(str) + attrs = parse_old_attributes(tokens) + flatten_attributes(attrs).map do |key, value| + [:html, :attr, key, [:dynamic, value]] + end + end + + private + + def flatten_attributes(attributes) + flattened = {} + + attributes.each do |key, value| + case value + when Hash + flatten_attributes(value).each do |k, v| + flattened["#{key}-#{k}"] = v + end + else + flattened[key] = value + end + end + flattened + end + + # Parse brace-balanced tokens and return the result as hash + def parse_old_attributes(tokens) + tokens = tokens.slice(1..-2) # strip braces + attributes = {} + + while tokens && tokens.any? + key = read_hash_key!(tokens) + val = read_hash_value!(tokens) + attributes[key] = val if key && val + + hash_skip_tokens!(tokens, :on_sp) + raise SyntaxError if tokens.any? && hash_type_of(tokens.shift) != :on_comma + end + + attributes + end + + def read_hash_key!(tokens) + hash_skip_tokens!(tokens, :on_sp) + + (row, col), type, str = tokens.shift + case type + when :on_label + str.gsub!(/:\Z/, '') + when :on_symbeg + if %w[:" :'].include?(str) + str = read_string!(tokens) + else + (row, col), type, str = tokens.shift + end + assert_rocket!(tokens) + when :on_tstring_beg + str = read_string!(tokens) + assert_rocket!(tokens) + end + str + end + + def read_string!(tokens) + (row, col), type, str = tokens.shift + return '' if type == :on_tstring_end + + raise SyntaxError if hash_type_of(tokens.shift) != :on_tstring_end + str + end + + def read_hash_value!(tokens) + result = '' + hash_skip_tokens!(tokens, :on_sp) + + if hash_type_of(tokens.first) == :on_lbrace + hash = fetch_balanced_braces(tokens) + hash.length.times { tokens.shift } + return parse_old_attributes(hash) + end + + while token = tokens.shift + (row, col), type, str = token + case type + when :on_sp + next + when :on_comma + tokens.unshift(token) + break + end + + result += str + end + result + end + + def hash_skip_tokens!(tokens, *types) + while types.include?(hash_type_of(tokens.first)) + tokens.shift + end + end + + def assert_rocket!(tokens, *types) + hash_skip_tokens!(tokens, :on_sp) + (row, col), type, str = tokens.shift + + raise SyntaxError unless type == :on_op && str == '=>' + end + + def hash_type_of(token) + return nil unless token + token[TYPE_POSITION] + end + end + end +end diff --git a/lib/hamlit/engine.rb b/lib/hamlit/engine.rb index fb062054..7e259caf 100644 --- a/lib/hamlit/engine.rb +++ b/lib/hamlit/engine.rb @@ -1,5 +1,4 @@ require 'temple' -require 'hamlit/attribute_compiler' require 'hamlit/compiler' require 'hamlit/html/pretty' require 'hamlit/html/ugly' @@ -17,7 +16,6 @@ module Hamlit use Multiline use Parser - use AttributeCompiler use Compiler use :Html, -> { create(html_compiler) } filter :Escapable diff --git a/spec/haml_spec.rb b/spec/haml_spec.rb index 53ed1ed3..735c1b80 100644 --- a/spec/haml_spec.rb +++ b/spec/haml_spec.rb @@ -579,7 +579,7 @@ describe "haml" do end # FIXME: it requires attribute sorter - pending "Ruby-style tag with a CSS class and 'class' as a variable attribute" do + specify "Ruby-style tag with a CSS class and 'class' as a variable attribute" do haml = %q{.hello{:class => var}} html = %q{
} locals = {:var=>"world"} diff --git a/spec/hamlit/attribute_compiler_spec.rb b/spec/hamlit/attribute_compiler_spec.rb deleted file mode 100644 index 3b01c3a6..00000000 --- a/spec/hamlit/attribute_compiler_spec.rb +++ /dev/null @@ -1,54 +0,0 @@ -describe Hamlit::AttributeCompiler do - describe '#call' do - it 'does not alter normal attrs' do - assert_compile( - [:haml, - :attrs, - [:html, :attr, 'id', [:static, 'foo']], - [:html, :attr, 'class', [:static, 'bar']]], - [:haml, - :attrs, - [:html, :attr, 'id', [:static, 'foo']], - [:html, :attr, 'class', [:static, 'bar']]], - ) - end - - it 'does not alter news-tyle attributes' do - assert_compile( - [:haml, - :attrs, - '(a=2)'], - [:haml, - :attrs, - '(a=2)'], - ) - end - - it 'convers only string' do - assert_compile( - [:haml, - :attrs, - [:html, :attr, 'id', [:static, 'foo']], - '{ foo: "bar" }', - [:html, :attr, 'class', [:static, 'bar']]], - [:haml, - :attrs, - [:html, :attr, 'id', [:static, 'foo']], - [:html, :attr, 'foo', [:dynamic, '"bar"']], - [:html, :attr, 'class', [:static, 'bar']]], - ) - end - - it 'converts nested attributes' do - assert_compile( - [:haml, - :attrs, - '{ a: { b: {}, c: "d" }, e: "f" }'], - [:haml, - :attrs, - [:html, :attr, 'a-c', [:dynamic, '"d"']], - [:html, :attr, 'e', [:dynamic, '"f"']]], - ) - end - end -end