module ExpressTemplates class Component attr_accessor :children INDENT = ' ' def initialize(*children_or_options) @children = [] @options = {}.with_indifferent_access _process(*children_or_options) end def self.macro_name @macro_name ||= to_s.split('::').last.underscore end def macro_name ; self.class.macro_name end def html_options @options.each_pair.map do |name, value| %Q(#{name}=\\"#{value}\\") end.join(" ") end def start_tag "<#{macro_name}#{html_options.empty? ? '' : ' '+html_options}>" end def close_tag "" end def add_css_class(css_class) @options['class'] ||= '' @options['class'] = (@options['class'].split + [css_class]).join(" ") end def method_missing(name, *args) add_css_class(name) _process(*args) unless args.empty? return self end def compile ruby_fragments = @children.map do |child| if child.respond_to?(:compile) child.compile else %Q("#{child}") end end ruby_fragments.unshift %Q("#{start_tag}") ruby_fragments.push %Q("#{close_tag}") ruby_fragments.reject {|frag| frag.empty? }.join("+") end def to_template(depth = 0) template_fragments = @children.map do |child| if child.kind_of?(ExpressTemplates::Component) child.to_template(depth+1) else child end end indent = INDENT*(depth+1) macro_name + _blockify(template_fragments.join("\n#{indent}"), depth) end private def _indent(code) code.split("\n").map {|line| INDENT + line }.join("\n") end def _blockify(code, depth) indent = INDENT*depth code.empty? ? code : " {\n#{_indent(code)}\n}\n" end def _process(*children_or_options) children_or_options.each do |child_or_option| if child_or_option.kind_of?(Hash) @options.merge!(child_or_option) else @children << child_or_option end end end end end