From 7d3056b89aa7fc6cedd9c534dca24f6e0109ca02 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Sat, 11 Apr 2015 18:15:37 +0900 Subject: [PATCH] Merge id and class with attributes on runtime Fixes #4. --- lib/hamlit/attribute.rb | 30 ++++++++++-- lib/hamlit/compilers/attributes.rb | 19 ++++++-- lib/hamlit/compilers/old_attribute.rb | 13 ++--- lib/hamlit/compilers/runtime_attribute.rb | 58 +++++++++++++++++++++++ spec/hamlit/engine/new_attribute_spec.rb | 38 +++++++++++++++ spec/hamlit/engine/old_attributes_spec.rb | 48 ++++++++++++++++++- 6 files changed, 188 insertions(+), 18 deletions(-) create mode 100644 lib/hamlit/compilers/runtime_attribute.rb diff --git a/lib/hamlit/attribute.rb b/lib/hamlit/attribute.rb index 244420ce..f7d32d89 100644 --- a/lib/hamlit/attribute.rb +++ b/lib/hamlit/attribute.rb @@ -10,18 +10,18 @@ module Hamlit class Attribute include Concerns::AttributeBuilder - def self.build(quote, attributes) + def self.build(quote, attributes, base = {}) builder = self.new(quote) - builder.build(attributes) + builder.build(attributes, base) end def initialize(quote) @quote = quote end - def build(attributes) + def build(attributes, base) result = '' - flatten_attributes(attributes).each do |key, value| + merge_attributes(base, attributes).each do |key, value| if value == true result += " #{key}" next @@ -32,5 +32,27 @@ module Hamlit end result end + + private + + def merge_attributes(base, target) + result = {} + base = flatten_attributes(base) + target = flatten_attributes(target) + + (base.keys | target.keys).each do |key| + if base[key] && target[key] + case key + when :id + result[key] = [base[key], target[key]].compact.join('_') + else + result[key] = [base[key], target[key]].compact.join(' ') + end + else + result[key] = base[key].nil? ? target[key] : base[key] + end + end + result + end end end diff --git a/lib/hamlit/compilers/attributes.rb b/lib/hamlit/compilers/attributes.rb index 3d9e59a4..2d7b4e99 100644 --- a/lib/hamlit/compilers/attributes.rb +++ b/lib/hamlit/compilers/attributes.rb @@ -1,5 +1,6 @@ require 'hamlit/compilers/new_attribute' require 'hamlit/compilers/old_attribute' +require 'hamlit/compilers/runtime_attribute' require 'hamlit/concerns/escapable' require 'hamlit/concerns/included' @@ -9,6 +10,7 @@ module Hamlit extend Concerns::Included include Compilers::NewAttribute include Compilers::OldAttribute + include Compilers::RuntimeAttribute included do include Concerns::Escapable @@ -21,6 +23,13 @@ module Hamlit attrs = join_ids(attrs) attrs = combine_classes(attrs) attrs = pull_class_first(attrs) + + if has_runtime_attribute?(attrs) && has_attr?(attrs, 'class', 'id') + attrs = merge_runtime_attributes(attrs) + return [:html, :attrs, *escape_attribute_values(attrs)] + end + attrs = attrs.map { |a| compile(a) } + [:html, :attrs, *escape_attribute_values(attrs)] end @@ -72,9 +81,13 @@ module Hamlit [[:html, :attr, 'id', [:multi, *insert_static(values, '_')]]] + (attrs - id_attrs) end - def filter_attrs(attrs, target) - class_attrs = attrs.select do |html, attr, name, content| - name == target + def has_attr?(attrs, *targets) + filter_attrs(attrs, *targets).any? + end + + def filter_attrs(attrs, *targets) + attrs.select do |html, attr, name, content| + targets.include?(name) end end diff --git a/lib/hamlit/compilers/old_attribute.rb b/lib/hamlit/compilers/old_attribute.rb index 2534878b..58aa8ca2 100644 --- a/lib/hamlit/compilers/old_attribute.rb +++ b/lib/hamlit/compilers/old_attribute.rb @@ -1,4 +1,3 @@ -require 'hamlit/attribute' require 'hamlit/concerns/attribute_builder' require 'hamlit/concerns/balanceable' require 'hamlit/concerns/ripperable' @@ -31,7 +30,8 @@ module Hamlit [:html, :attr, key, [:dynamic, value]] end rescue RuntimeBuild - return runtime_build(str) + # Give up static compilation when given string is invalid as ruby hash + [[:runtime, str]] end private @@ -48,7 +48,7 @@ module Hamlit end end - # Give up static copmile when variables are detected. + # Give up static compilation when variables are detected. def assert_static_value!(value) tokens = Ripper.lex(value) tokens.each do |(row, col), type, str| @@ -114,13 +114,6 @@ module Hamlit raise SyntaxError unless type == :on_op && str == '=>' end - def runtime_build(str) - str = str.gsub(/(\A\{|\}\Z)/, '') - quote = options[:attr_quote].inspect - code = "::Hamlit::Attribute.build(#{quote}, #{str})" - [[:dynamic, code]] - end - def split_hash(str) columns = HashParser.assoc_columns(str) columns = reject_nested_columns(str, columns) diff --git a/lib/hamlit/compilers/runtime_attribute.rb b/lib/hamlit/compilers/runtime_attribute.rb new file mode 100644 index 00000000..903bcc6b --- /dev/null +++ b/lib/hamlit/compilers/runtime_attribute.rb @@ -0,0 +1,58 @@ +require 'hamlit/attribute' +require 'hamlit/concerns/string_interpolation' + +# This module compiles :runtime sexp. It is a special version of +# old-style attribute which is built on runtime. +module Hamlit + module Compilers + module RuntimeAttribute + include Concerns::StringInterpolation + + # This is used for compiling only runtime attribute in Compilers::Attribute. + def on_runtime(str) + compile_runtime_attribute(str) + end + + # Given html attrs, merge classes and ids to :dynamic_attribute. + def merge_runtime_attributes(attrs) + merge_targets = filter_attrs(attrs, 'id', 'class') + dynamic_attr = attrs.find { |exp, *args| exp == :runtime } + + attrs -= merge_targets + attrs.delete(dynamic_attr) + + base = decompile_temple_attrs(merge_targets) + [compile_runtime_attribute(dynamic_attr.last, base), *attrs] + end + + private + + def compile_runtime_attribute(str, base = nil) + str = str.gsub(/(\A\{|\}\Z)/, '') + quote = options[:attr_quote].inspect + code = "::Hamlit::Attribute.build(#{[quote, str, base].compact.join(', ')})" + [:dynamic, code] + end + + def has_runtime_attribute?(attrs) + attrs.any? do |exp, *args| + exp == :runtime + end + end + + # Given attrs in temple ast, return an attribute as hash literal. + def decompile_temple_attrs(attrs) + literal = '{' + attrs.each do |html, attr, name, (type, value)| + case type + when :static + literal += ":#{name}=>#{string_literal(value)}," + when :dynamic + literal += ":#{name}=>#{value}," + end + end + literal.gsub(/,\Z/, '') + '}' + end + end + end +end diff --git a/spec/hamlit/engine/new_attribute_spec.rb b/spec/hamlit/engine/new_attribute_spec.rb index 4989539b..a71818cb 100644 --- a/spec/hamlit/engine/new_attribute_spec.rb +++ b/spec/hamlit/engine/new_attribute_spec.rb @@ -41,5 +41,43 @@ describe Hamlit::Engine do HTML end end + + describe 'element class with attribute class' do + it 'does not generate double classes' do + assert_render(<<-HAML, <<-HTML) + .item(class='first') + HAML +
+ HTML + end + + it 'does not generate double classes for a variable' do + assert_render(<<-HAML, <<-HTML) + - val = 'val' + .element(class=val) + HAML +
+ HTML + end + end + + describe 'element id with attribute id' do + it 'concatenates ids with underscore' do + assert_render(<<-HAML, <<-HTML) + #item(id='first') + HAML +
+ HTML + end + + it 'concatenates ids with underscore for a variable' do + assert_render(<<-HAML, <<-HTML) + - val = 'first' + #item(id=val) + HAML +
+ HTML + end + end end end diff --git a/spec/hamlit/engine/old_attributes_spec.rb b/spec/hamlit/engine/old_attributes_spec.rb index 74363bf8..29116fc0 100644 --- a/spec/hamlit/engine/old_attributes_spec.rb +++ b/spec/hamlit/engine/old_attributes_spec.rb @@ -160,7 +160,7 @@ describe Hamlit::Engine do end end - describe 'element class with attributes class' do + describe 'element class with attribute class' do it 'does not generate double classes' do assert_render(<<-HAML, <<-HTML) .item{ class: 'first' } @@ -177,6 +177,52 @@ describe Hamlit::Engine do
HTML end + + it 'does not generate double classes for hash attributes' do + assert_render(<<-HAML, <<-HTML) + - hash = { class: 'val' } + .element{ hash } + HAML +
+ HTML + end + end + + describe 'element id with attribute id' do + it 'does not generate double ids' do + assert_render(<<-HAML, <<-HTML) + #item{ id: 'first' } + HAML +
+ HTML + end + + it 'does not generate double ids for a variable' do + assert_render(<<-HAML, <<-HTML) + - val = 'first' + #item{ id: val } + HAML +
+ HTML + end + + it 'does not generate double ids for hash attributes' do + assert_render(<<-HAML, <<-HTML) + - hash = { id: 'first' } + #item{ hash } + HAML +
+ HTML + end + + it 'does not generate double ids and classes for hash attributes' do + assert_render(<<-HAML, <<-HTML) + - hash = { id: 'first', class: 'foo' } + #item.bar{ hash } + HAML +
+ HTML + end end end end