PageRenderTime 41ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/tools/drillbit/Resources/tests/ruby/markaby/lib/markaby/builder.rb

http://github.com/appcelerator/titanium_desktop
Ruby | 288 lines | 185 code | 30 blank | 73 comment | 22 complexity | 0a3c82f35d726f79801bb54255eef837 MD5 | raw file
Possible License(s): Apache-2.0
  1. require 'markaby/tags'
  2. module Markaby
  3. # The Markaby::Builder class is the central gear in the system. When using
  4. # from Ruby code, this is the only class you need to instantiate directly.
  5. #
  6. # mab = Markaby::Builder.new
  7. # mab.html do
  8. # head { title "Boats.com" }
  9. # body do
  10. # h1 "Boats.com has great deals"
  11. # ul do
  12. # li "$49 for a canoe"
  13. # li "$39 for a raft"
  14. # li "$29 for a huge boot that floats and can fit 5 people"
  15. # end
  16. # end
  17. # end
  18. # puts mab.to_s
  19. #
  20. class Builder
  21. @@default = {
  22. :indent => 0,
  23. :output_helpers => true,
  24. :output_xml_instruction => true,
  25. :output_meta_tag => true,
  26. :auto_validation => true,
  27. :tagset => Markaby::XHTMLTransitional
  28. }
  29. def self.set(option, value)
  30. @@default[option] = value
  31. end
  32. def self.ignored_helpers
  33. @@ignored_helpers ||= []
  34. end
  35. def self.ignore_helpers(*helpers)
  36. ignored_helpers.concat helpers
  37. end
  38. attr_accessor :output_helpers, :tagset
  39. # Create a Markaby builder object. Pass in a hash of variable assignments to
  40. # +assigns+ which will be available as instance variables inside tag construction
  41. # blocks. If an object is passed in to +helpers+, its methods will be available
  42. # from those same blocks.
  43. #
  44. # Pass in a +block+ to new and the block will be evaluated.
  45. #
  46. # mab = Markaby::Builder.new {
  47. # html do
  48. # body do
  49. # h1 "Matching Mole"
  50. # end
  51. # end
  52. # }
  53. #
  54. def initialize(assigns = {}, helpers = nil, &block)
  55. @streams = [[]]
  56. @assigns = assigns
  57. @elements = {}
  58. @@default.each do |k, v|
  59. instance_variable_set("@#{k}", @assigns[k] || v)
  60. end
  61. if helpers.nil?
  62. @helpers = nil
  63. else
  64. @helpers = helpers.dup
  65. for iv in helpers.instance_variables
  66. instance_variable_set(iv, helpers.instance_variable_get(iv))
  67. end
  68. end
  69. unless assigns.nil? || assigns.empty?
  70. for iv, val in assigns
  71. instance_variable_set("@#{iv}", val)
  72. unless @helpers.nil?
  73. @helpers.instance_variable_set("@#{iv}", val)
  74. end
  75. end
  76. end
  77. @builder = ::Builder::XmlMarkup.new(:indent => @indent, :target => @streams.last)
  78. class << @builder
  79. attr_accessor :target, :level
  80. end
  81. if block
  82. text(capture(&block))
  83. end
  84. end
  85. # Returns a string containing the HTML stream. Internally, the stream is stored as an Array.
  86. def to_s
  87. @streams.last.to_s
  88. end
  89. # Write a +string+ to the HTML stream without escaping it.
  90. def text(string)
  91. @builder << "#{string}"
  92. nil
  93. end
  94. alias_method :<<, :text
  95. alias_method :concat, :text
  96. # Emulate ERB to satisfy helpers like <tt>form_for</tt>.
  97. def _erbout; self end
  98. # Captures the HTML code built inside the +block+. This is done by creating a new
  99. # stream for the builder object, running the block and passing back its stream as a string.
  100. #
  101. # >> Markaby::Builder.new.capture { h1 "TEST"; h2 "CAPTURE ME" }
  102. # => "<h1>TITLE</h1>\n<h2>CAPTURE ME</h2>\n"
  103. #
  104. def capture(&block)
  105. @streams.push(builder.target = [])
  106. @builder.level += 1
  107. str = instance_eval(&block)
  108. str = @streams.last.join if @streams.last.any?
  109. @streams.pop
  110. @builder.level -= 1
  111. builder.target = @streams.last
  112. str
  113. end
  114. # Create a tag named +tag+. Other than the first argument which is the tag name,
  115. # the arguments are the same as the tags implemented via method_missing.
  116. def tag!(tag, *args, &block)
  117. ele_id = nil
  118. if @auto_validation and @tagset
  119. if !@tagset.tagset.has_key?(tag)
  120. raise InvalidXhtmlError, "no element `#{tag}' for #{tagset.doctype}"
  121. elsif args.last.respond_to?(:to_hash)
  122. attrs = args.last.to_hash
  123. attrs.each do |k, v|
  124. atname = k.to_s.downcase.intern
  125. unless k =~ /:/ or @tagset.tagset[tag].include? atname
  126. raise InvalidXhtmlError, "no attribute `#{k}' on #{tag} elements"
  127. end
  128. if atname == :id
  129. ele_id = v.to_s
  130. if @elements.has_key? ele_id
  131. raise InvalidXhtmlError, "id `#{ele_id}' already used (id's must be unique)."
  132. end
  133. end
  134. end
  135. end
  136. end
  137. if block
  138. str = capture &block
  139. block = proc { text(str) }
  140. end
  141. f = fragment { @builder.method_missing(tag, *args, &block) }
  142. @elements[ele_id] = f if ele_id
  143. f
  144. end
  145. # This method is used to intercept calls to helper methods and instance
  146. # variables. Here is the order of interception:
  147. #
  148. # * If +sym+ is a helper method, the helper method is called
  149. # and output to the stream.
  150. # * If +sym+ is a Builder::XmlMarkup method, it is passed on to the builder object.
  151. # * If +sym+ is also the name of an instance variable, the
  152. # value of the instance variable is returned.
  153. # * If +sym+ has come this far and no +tagset+ is found, +sym+ and its arguments are passed to tag!
  154. # * If a tagset is found, though, +NoMethodError+ is raised.
  155. #
  156. # method_missing used to be the lynchpin in Markaby, but it's no longer used to handle
  157. # HTML tags. See html_tag for that.
  158. def method_missing(sym, *args, &block)
  159. if @helpers.respond_to?(sym, true) && !self.class.ignored_helpers.include?(sym)
  160. r = @helpers.send(sym, *args, &block)
  161. if @output_helpers and r.respond_to? :to_str
  162. fragment { @builder << r }
  163. else
  164. r
  165. end
  166. elsif ::Builder::XmlMarkup.instance_methods.include?(sym.to_s)
  167. @builder.__send__(sym, *args, &block)
  168. elsif instance_variables.include?("@#{sym}")
  169. instance_variable_get("@#{sym}")
  170. elsif @tagset.nil?
  171. tag!(sym, *args, &block)
  172. else
  173. raise NoMethodError, "no such method `#{sym}'"
  174. end
  175. end
  176. # Every HTML tag method goes through an html_tag call. So, calling <tt>div</tt> is equivalent
  177. # to calling <tt>html_tag(:div)</tt>. All HTML tags in Markaby's list are given generated wrappers
  178. # for this method.
  179. #
  180. # If the @auto_validation setting is on, this method will check for many common mistakes which
  181. # could lead to invalid XHTML.
  182. def html_tag(sym, *args, &block)
  183. if @auto_validation and @tagset.self_closing.include?(sym) and block
  184. raise InvalidXhtmlError, "the `\#{sym}' element is self-closing, please remove the block"
  185. end
  186. if args.empty? and block.nil? and not NO_PROXY.include?(sym)
  187. return CssProxy.new do |args, block|
  188. if @tagset.forms.include?(sym) and args.last.respond_to?(:to_hash) and args.last[:id]
  189. args.last[:name] ||= args.last[:id]
  190. end
  191. tag!(sym, *args, &block)
  192. end
  193. end
  194. if not @tagset.self_closing.include?(sym) and args.first.respond_to?(:to_hash)
  195. block ||= proc{}
  196. end
  197. tag!(sym, *args, &block)
  198. end
  199. XHTMLTransitional.tags.each do |k|
  200. class_eval %{
  201. def #{k}(*args, &block)
  202. html_tag(#{k.inspect}, *args, &block)
  203. end
  204. }
  205. end
  206. # Builds a head tag. Adds a <tt>meta</tt> tag inside with Content-Type
  207. # set to <tt>text/html; charset=utf-8</tt>.
  208. def head(*args, &block)
  209. tag!(:head, *args) do
  210. tag!(:meta, "http-equiv" => "Content-Type", "content" => "text/html; charset=utf-8") if @output_meta_tag
  211. instance_eval(&block)
  212. end
  213. end
  214. # Builds an html tag. An XML 1.0 instruction and an XHTML 1.0 Transitional doctype
  215. # are prepended. Also assumes <tt>:xmlns => "http://www.w3.org/1999/xhtml",
  216. # :lang => "en"</tt>.
  217. def xhtml_transitional(&block)
  218. self.tagset = Markaby::XHTMLTransitional
  219. xhtml_html &block
  220. end
  221. # Builds an html tag with XHTML 1.0 Strict doctype instead.
  222. def xhtml_strict(&block)
  223. self.tagset = Markaby::XHTMLStrict
  224. xhtml_html &block
  225. end
  226. private
  227. def xhtml_html(&block)
  228. instruct! if @output_xml_instruction
  229. declare!(:DOCTYPE, :html, :PUBLIC, *tagset.doctype)
  230. tag!(:html, :xmlns => "http://www.w3.org/1999/xhtml", "xml:lang" => "en", :lang => "en", &block)
  231. end
  232. def fragment
  233. stream = @streams.last
  234. f1 = stream.length
  235. yield
  236. f2 = stream.length - f1
  237. Fragment.new(stream, f1, f2)
  238. end
  239. end
  240. # Every tag method in Markaby returns a Fragment. If any method gets called on the Fragment,
  241. # the tag is removed from the Markaby stream and given back as a string. Usually the fragment
  242. # is never used, though, and the stream stays intact.
  243. #
  244. # For a more practical explanation, check out the README.
  245. class Fragment < ::Builder::BlankSlate
  246. def initialize(s, a, b)
  247. @s, @f1, @f2 = s, a, b
  248. end
  249. def method_missing(*a)
  250. unless @str
  251. @str = @s[@f1, @f2].to_s
  252. @s[@f1, @f2] = [nil] * @f2
  253. @str
  254. end
  255. @str.send(*a)
  256. end
  257. end
  258. end