# = buildingblock.rb
#
# == Copyright (c) 2006 Thomas Sawyer
#
# Ruby License
#
# This module is free software. You may use, modify, and/or redistribute this
# software under the same terms as Ruby.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.
#
# == Authors and Contributors
#
# * Thomas Sawyer
# CREDIT Thomas Sawyer
# Author:: Thomas Sawyer
# Copyright:: Copyright (c) 2007 Thomas Sawyer
# License:: Ruby License
# = BuildingBlock
#
# Build content programatically with Ruby and Ruby's blocks.
#
# require 'facets'
# require 'xmlhelper'
#
# builder = BuildingBlocks.new(XMLHelper, :element)
#
# doc = builder.html do
#
# head do
# title "Test"
# end
#
# body do
# i "Hello"
# br
# text "Test"
# text "Hey"
# end
#
# end
#
# _produces_
#
#
TestHello
TestHey
#
# All calls within the block are routed via the Helper Module's constructor method
# (#element in the above example) unless they are defined by the helper module, in which
# case they are sent to the helper module directly. The results of these invocations are
# appended to the output buffer. To prevent this, prefix the method with 'call_'.
#
# Sometimes keywords can get in the way of a construction. In these cases you can
# ensure use of constructor method by calling the special #build! command. You can
# also add verbatium text to the output via the #<< operator. All of Ruby's built-in
# keywords (eg. 'while', 'if', 'until', etc.) and the following methods are treated as
# keywords.
#
# method_missing
# initialize
# inspect
# to_str
# to_s
# respond_to?
# singleton_method_undefined
#
# This work was of course inspired by many great minds, and represents a concise and simple
# means of accomplishing this pattern of design, which is unique to Ruby.
class BuildingBlock
ESCAPE = [
'singleton_method_undefined',
'method_missing',
'respond_to?',
'initialize',
'inspect',
'to_str',
'to_s',
'<<',
'build!'
] # 'to_ary', 'p' ]
def initialize(helper_module, constructor_method, output_buffer=nil)
@module = helper_module
@constructor = constructor_method
@instance_eval = method(:instance_eval)
@method = {}
meths = []
#meths.concat singleton_methods
meths.concat public_methods
meths.concat protected_methods
meths.concat private_methods
meths.each do |m|
@method[m.to_sym] = method(m)
end
class << self
escape = ESCAPE
meths = []
#meths.concat singleton_methods
meths.concat public_instance_methods
meths.concat protected_instance_methods
meths.concat private_instance_methods
meths.each do |m|
undef_method(m) unless m =~ /^__/ or escape.include?(m)
end
end
@stack = []
@out = output_buffer || ''
end
def method_missing(s, *a, &b)
s = s.to_s
if b
@stack << @out
@out = ''
@instance_eval.call(&b)
out = @out
@out = @stack.pop
a.unshift(out)
end
if s =~ /^call_/
m = s[5..-1].to_sym
@module.send(m, *a, &b).to_s
elsif @module.respond_to?(s) #o =~ /^build_/
@out << @module.send(s, *a, &b).to_s
else
s = s.chomp('?') if s[-1,1] == '?'
@out << @module.send(@constructor, s, *a).to_s
end
end
def to_s() @out end
def to_str() @out end
def <<(s)
@out << s.to_s
end
# If creating and XML/HTML builder, you'll want to alias this to tag!.
def build!(m, *a)
@out << @module.send(@constructor, m, *a).to_s
end
# Could improve.
def inspect
r = super
i = r.index(',')
return r[0...i] + ">"
end
end
# _____ _
# |_ _|__ ___| |_
# | |/ _ \/ __| __|
# | | __/\__ \ |_
# |_|\___||___/\__|
#
=begin test
require 'test/unit'
class TestBuildingBlock < Test::Unit::TestCase
module M
extend self
def m(n,*m) ; "#{n}{#{m}}"; end
def t(n) ; "#{n}"; end
end
def test_01
build = BuildingBlock.new(M, :m)
build.html do
head do
title "Test"
end
body do
i "Hello"
build! :not
t "Test"
t "Hey"
end
end
r = "html{head{title{Test}}body{i{Hello}not{}TestHey}}"
assert_equal( r, build.to_s )
end
end
=end