user2477366
user2477366

Reputation: 41

Recursive indentation of tags in Ruby

I'm trying to format tags using "method_missing", the result I want is shown below.

<foo>\n
    <bar>\n
        <ab/>\n
    </bar>\n
</foo>\n

I believe a running index is required but I'm unclear where best to put it or whether that's the best approach. How would I add indentation?

def method_missing(meth, *args, &block)
    if args.length > 0
        my_other_method(args)
    else
        my_method(meth.to_s, &block)
    end
end

def my_other_method(args)
    "<#{args}/>"
end

def my_method(meth)     
    s = "<#{meth}>\n"
    s << "#{_indentation}"
    s << yield.to_s << "\n"
    s << "#{_indentation}"
    s << "</#{meth}>\n"
end

def _indentation
    ("--" * _level.to_i)    # dashes added to more easily infer spacing
end

def _level
    caller.rindex {|val| val.scan('my_method')} / 3
end

p foo{bar{baz(:a => "b"){}}}

I get this wrong output (not certain whats causing the extra \n)

<foo>\n
--<bar>\n
----<ab>\n
----</bar>\n
\n
--</foo>\n

Upvotes: 3

Views: 155

Answers (1)

Yuri Golobokov
Yuri Golobokov

Reputation: 1965

You can use call stack to get the level of indentation. See caller in my code. I also suggest to isolate your code in some class, so that you can use more tags, because there could be a lot of methods defined in main So, I modified your code a bit:

class TagFormatter
  def method_missing(method_name, *args, &block)
    _tagify method_name, args, &block
  end

  def _tagify(tag_name, args)
    if block_given?
      s = "#{_indentation}<#{tag_name}#{_attrs args}>\n"
      s << (yield || "")
      s << "#{_indentation}</#{tag_name}>\n"
    else
      "#{_indentation}<#{tag_name}#{_attrs args}/>\n"
    end
  end

  def _indentation
    "    " * _level
  end

  def _level
    caller.index { |val| val =~ /instance_eval/ } / 3 - 1
  end

  def _attrs(args)
    args.map { |arg| _to_attr arg }.join if args
  end

  def _to_attr(arg)
    if arg.kind_of? Hash
      arg.map { |k, v| %Q{ #{k}="#{v}"}}
    else
      %Q{ #{arg.to_s}}
    end
  end
end

Example:

> tf = TagFormatter.new
> puts tf.instance_eval 'foo{bar{baz(:a => "b"){}}}'
<foo>
    <bar>
        <baz a="b">
        </baz>
    </bar>
</foo>
> puts tf.instance_eval 'foo{bar{baz(:a => "b")}}' # no block given in baz
<foo>
    <bar>
        <baz a="b"/>
    </bar>
</foo>
> puts tf.instance_eval 'foo{bar{baz("disabled", :readonly, :a=>"b")}}'
<foo>
    <bar>
        <baz disabled readonly a="b"/>
    </bar>
</foo>

Upvotes: 1

Related Questions