Changeset 60
- Timestamp:
- 05/29/2006 05:19:14 (3 years ago)
- Location:
- trunk
- Files:
-
- 7 modified
-
README (modified) (2 diffs)
-
Rakefile (modified) (1 diff)
-
lib/markaby.rb (modified) (1 diff)
-
lib/markaby/builder.rb (modified) (11 diffs)
-
lib/markaby/tags.rb (modified) (1 diff)
-
test/test_markaby.rb (modified) (2 diffs)
-
tools/rakehelp.rb (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
trunk/README
r20 r60 133 133 IDs may be added by the use of bang methods: 134 134 135 div.page! 136 div.content! 135 div.page! { 136 div.content! { 137 137 h1 "A Short Short Saintly Dog" 138 end139 end138 } 139 } 140 140 141 141 Which results in: … … 156 156 end 157 157 158 == The <tt>capture</tt> Method 159 160 Want to catch a block of HTML as a string and play with it a bit? 161 Use the <tt>capture</tt> method. 162 163 Commonly used to join HTML blocks together: 158 == Markaby prevents invalid HTML 159 160 Since Markaby knows which doctype you're using, it checks a big 161 list of valid tags and attributes before printing anything. 162 163 >> div :styl => "padding: 10px" do 164 >> img :src => "samorost.jpg" 165 >> end 166 InvalidHtmlError: no such attribute `styl' 167 168 == Auto-stringification 169 170 If you end up using any of your Markaby "tags" as a string, the 171 tag won't be output. It'll be up to you to add the new string 172 back into the HTML output. 173 174 This means if you call <tt>to_s</tt>, you'll get a string back. 175 176 div.title { "Rock Bottom" + span(" by Robert Wyatt").to_s } 177 178 But, when you're adding strings in Ruby, <tt>to_s</tt> happens automatically. 179 180 div.title { "Rock Bottom" + span(" by Robert Wyatt") } 181 182 Interpolation works fine. 183 184 div.title { "Rock Bottom #{span(" by Robert Wyatt")}" } 185 186 And any other operation you might perform on a string. 164 187 165 188 div.menu! \ 166 189 ['5.gets', 'bits', 'cult', 'inspect', '-h'].map do |category| 167 capture { link_to category }190 link_to category 168 191 end. 169 192 join( " | " ) -
trunk/Rakefile
r29 r60 13 13 summary = "Markup as Ruby, write HTML in your native Ruby tongue" 14 14 test_file = "test/test_markaby.rb" 15 setup_gem("markaby", "0. 4", "Tim Fletcher and _why", summary, ['builder'], test_file)15 setup_gem("markaby", "0.5", "Tim Fletcher and _why", summary, [['builder', '>=2.0.0']], test_file) -
trunk/lib/markaby.rb
r30 r60 21 21 module Markaby 22 22 VERSION = '0.4' 23 24 class InvalidXhtmlError < Exception; end 23 25 end 24 26 -
trunk/lib/markaby/builder.rb
r50 r60 52 52 # 53 53 def initialize(assigns = {}, helpers = nil, &block) 54 @stream = []54 @streams = [[]] 55 55 @assigns = assigns 56 56 @margin = -1 … … 59 59 @output_helpers = @@default[:output_helpers] 60 60 @output_meta_tag = @@default[:output_meta_tag] 61 62 use_tagset Markaby::XHTMLTransitionalTags 61 63 62 64 if helpers … … 77 79 78 80 @margin += 1 79 @builder = ::Builder::XmlMarkup.new(:indent => @indent, :margin => @margin, :target => @stream) 81 @builder = ::Builder::XmlMarkup.new(:indent => @indent, :margin => @margin, :target => @streams.last) 82 83 def @builder.target=(io) 84 @target = io 85 end 80 86 81 87 instance_eval(&block) if block … … 84 90 # Returns a string containing the HTML stream. Internally, the stream is stored as an Array. 85 91 def to_s 86 @stream .join92 @streams.last.join 87 93 end 88 94 … … 105 111 # 106 112 def capture(&block) 107 old_stream = @stream.dup108 @stream.replace []109 str = instance_eval(&block).to_s110 str = to_s unless @stream.empty?111 @stream.replace old_stream113 @streams.push(builder.target = []) 114 str = instance_eval(&block) 115 str = to_s unless @streams.last.compact.empty? 116 @streams.pop 117 builder.target = @streams.last 112 118 str 113 119 end … … 133 139 # the arguments are the same as the tags implemented via method_missing. 134 140 def tag!(tag, *args, &block) 141 if @tagset 142 if !@tagset.has_key?(tag) 143 raise InvalidXhtmlError, "no element `#{tag}' for #{doctype}" 144 elsif args.last.respond_to?(:to_hash) 145 attrs = args.last.to_hash 146 attrs.each do |k, v| 147 unless k =~ /:/ or @tagset[tag].include? k.to_s.downcase.intern 148 raise InvalidXhtmlError, "no attribute `#{k}' on #{tag} elements" 149 end 150 end 151 end 152 end 135 153 if block 136 154 str = capture &block 137 155 block = proc { text(str) } 138 156 end 139 @builder.method_missing(tag, *args, &block)157 fragment { @builder.method_missing(tag, *args, &block) } 140 158 end 141 159 … … 146 164 # variables. Here is the order of interception: 147 165 # 166 # * If +sym+ is a helper method, the helper method is called 167 # and output to the stream. 148 168 # * If +sym+ is a recognized HTML tag, the tag is output 149 169 # or a CssProxy is returned if no arguments are given. … … 152 172 # * If +sym+ is also the name of an instance variable, the 153 173 # value of the instance variable is returned. 154 # * If +sym+ is a helper method, the helper method is called155 # and output to the stream.156 174 # * Otherwise, +sym+ and its arguments are passed to tag! 157 175 def method_missing(sym, *args, &block) 158 176 if @helpers.respond_to?(sym) 159 177 r = @helpers.send(sym, *args, &block) 160 @builder << r if @output_helpers 161 r 178 @output_helpers ? fragment { @builder << r } : r 162 179 elsif ::Builder::XmlMarkup.instance_methods.include?(sym.to_s) 163 180 @builder.__send__(sym, *args, &block) 164 elsif TAGS.include?(sym) or (FORM_TAGS.include?(sym) and args.empty?) 165 if args.empty? and block.nil? 181 elsif @tagset and @tagset_tags.include?(sym) 182 if @tagset_self_closing.include?(sym) and block 183 raise InvalidXhtmlError, "the `#{sym}' element is self-closing, please remove the block" 184 end 185 if args.empty? and block.nil? and not NO_PROXY.include?(sym) 166 186 return CssProxy.new do |args, block| 167 if FORM_TAGS.include?(sym) and args.last.respond_to?(:to_hash) and args.last[:id]187 if @tagset_forms.include?(sym) and args.last.respond_to?(:to_hash) and args.last[:id] 168 188 args.last[:name] ||= args.last[:id] 169 189 end … … 171 191 end 172 192 end 193 if not @tagset_self_closing.include?(sym) and args.first.respond_to?(:to_hash) 194 block ||= proc{} 195 end 173 196 tag!(sym, *args, &block) 174 elsif SELF_CLOSING_TAGS.include?(sym)175 tag!(sym, *args)176 elsif value = instance_variable_get("@#{sym}")177 value197 elsif instance_variable_get("@#{sym}") 198 instance_variable_get("@#{sym}") 199 elsif @tagset.nil? 200 tag!(sym, *args, &block) 178 201 else 179 tag!(sym, *args, &block)202 raise NoMethodError, "no such method `#{sym}'" 180 203 end 181 204 end … … 197 220 end 198 221 222 def use_tagset(tagset) 223 return if @tagset == tagset 224 @tagset = tagset 225 @tagset_tags = tagset.keys 226 @tagset_forms = @tagset_tags & FORM_TAGS 227 @tagset_self_closing = @tagset_tags & SELF_CLOSING_TAGS 228 end 229 199 230 # Builds an html tag. An XML 1.0 instruction and an XHTML 1.0 Transitional doctype 200 231 # are prepended. Also assumes <tt>:xmlns => "http://www.w3.org/1999/xhtml", 201 # "xml:lang" => "en",:lang => "en"</tt>.232 # :lang => "en"</tt>. 202 233 def html(*doctype, &block) 203 doctype = XHTMLTransitional if doctype.empty? 234 if doctype.empty? 235 doctype = XHTMLTransitional 236 use_tagset Markaby::XHTMLTransitionalTags 237 end 204 238 declare!(:DOCTYPE, :html, :PUBLIC, *doctype) 205 239 tag!(:html, :xmlns => "http://www.w3.org/1999/xhtml", "xml:lang" => "en", :lang => "en", &block) … … 209 243 # Builds an html tag with XHTML 1.0 Strict doctype instead. 210 244 def xhtml_strict(&block) 245 use_tagset Markaby::XHTMLStrictTags 211 246 instruct! 212 247 html *XHTMLStrict, &block 213 248 end 214 249 250 private 251 252 def fragment 253 stream = @streams.last 254 f1 = stream.length 255 yield 256 f2 = stream.length - f1 257 Fragment.new(stream, f1, f2) 258 end 259 215 260 end 261 262 class Fragment < ::Builder::BlankSlate 263 def initialize(s, a, b) 264 @s, @f1, @f2 = s, a, b 265 end 266 def method_missing(*a) 267 unless @str 268 @str = @s[@f1, @f2].to_s 269 @s[@f1, @f2] = [nil] * @f2 270 @str 271 end 272 @str.send(*a) 273 end 274 end 275 216 276 end -
trunk/lib/markaby/tags.rb
r27 r60 1 1 module Markaby 2 2 3 TAGS = [ 4 :a, :abbr, :acronym, :span, :b, :caption, :del, :cite, :code, :col, 5 :colgroup, :dd, :dfn, :dt, :em, :fieldset, :i, :img, :ins, :kbd, :p, 6 :label, :legend, :li, :optgroup, :option, :select, :small, :span, :strong, 7 :sub, :sup, :tbody, :td, :textarea, :thead, :title, :th, :tr, :tfoot, 8 :tt, :address, :blockquote, :body, :div, :dl, :form, :h1, :h2, :h3, :head, 9 :noscript, :object, :ol, :pre, :q, :samp, :script, :style, :table, :ul 10 ] 3 # Common sets of attributes. 4 AttrCustom = [:id, :class, :style] 5 AttrEvents = [:onclick, :ondblclick, :onmousedown, :onmouseup, :onmouseover, :onmousemove, 6 :onmouseout, :onkeypress, :onkeydown, :onkeyup] 7 AttrAnno = [:title, :lang, :dir] 8 Attrs = AttrCustom + AttrEvents + AttrAnno 9 10 # Very basic rules from the XHTML 1.0 Strict DTD. 11 XHTMLStrictTags = { 12 :pre => AttrCustom + AttrAnno + [:space], 13 :em => Attrs, 14 :code => Attrs, 15 :h2 => Attrs, 16 :h3 => Attrs, 17 :h1 => Attrs, 18 :h6 => Attrs, 19 :dl => Attrs, 20 :h4 => Attrs, 21 :h5 => Attrs, 22 :area => Attrs + [:accesskey, :tabindex, :onfocus, :onblur, :shape, :coords, :href, :nohref, :alt], 23 :meta => [:lang, :dir, :id, :name, :content, :scheme, "http-equiv".intern], 24 :table => [], 25 :dfn => Attrs, 26 :label => Attrs + [:for, :accesskey, :onfocus, :onblur], 27 :select => Attrs + [:name, :size, :multiple, :disabled, :tabindex, :onfocus, :onblur, :onchange], 28 :noscript => Attrs, 29 :style => [:lang, :dir, :id, :type, :media, :title, :space], 30 :strong => Attrs, 31 :span => Attrs, 32 :sub => Attrs, 33 :img => Attrs + [:src, :alt, :longdesc, :height, :width, :usemap, :ismap], 34 :title => [:lang, :dir, :id], 35 :bdo => AttrCustom + [:title] + AttrEvents + [:lang, :dir], 36 :tr => [], 37 :tbody => [], 38 :param => [:id, :name, :value, :valuetype, :type], 39 :li => Attrs, 40 :acronym => Attrs, 41 :html => [:lang, :dir, :id, :xmlns], 42 :caption => [], 43 :tfoot => [], 44 :th => [], 45 :sup => Attrs, 46 :var => Attrs, 47 :input => Attrs + [:accesskey, :tabindex, :onfocus, :onblur, :type, :name, :value, :checked, :disabled, :readonly, :size, :maxlength, :src, :alt, :usemap, :onselect, :onchange, :accept], 48 :td => Attrs + [:summary, :width, :border, :frame, :rules, :cellspacing, :cellpadding, :span, :align, :char, :charoff, :valign, :abbr, :axis, :headers, :scope, :rowspan, :colspan], 49 :samp => Attrs, 50 :cite => Attrs, 51 :thead => [], 52 :body => Attrs + [:onload, :onunload], 53 :map => AttrCustom + [:lang, :dir, ] + AttrEvents + [:title, :name], 54 :head => [:lang, :dir, :id, :profile], 55 :blockquote => Attrs + [:cite], 56 :fieldset => Attrs, 57 :option => Attrs + [:selected, :disabled, :label, :value], 58 :form => Attrs + [:action, :method, :enctype, :onsubmit, :onreset, :accept, 'accept-charset'], 59 :hr => Attrs, 60 :big => Attrs, 61 :dd => Attrs, 62 :object => Attrs + [:declare, :classid, :codebase, :data, :type, :codetype, :archive, :standby, :height, :width, :usemap, :name, :tabindex], 63 :base => [:href, :id], 64 :link => Attrs + [:charset, :href, :hreflang, :type, :rel, :rev, :media], 65 :kbd => Attrs, 66 :br => AttrCustom + [:title], 67 :address => Attrs, 68 :optgroup => Attrs + [:disabled, :label], 69 :dt => Attrs, 70 :ins => Attrs + [:cite, :datetime], 71 :b => Attrs, 72 :legend => Attrs + [:accesskey], 73 :abbr => Attrs, 74 :a => Attrs + [:accesskey, :tabindex, :onfocus, :onblur, :charset, :type, :name, :href, :hreflang, :rel, :rev, :shape, :coords], 75 :ol => Attrs, 76 :textarea => Attrs + [:accesskey, :tabindex, :onfocus, :onblur, :name, :rows, :cols, :disabled, :readonly, :onselect, :onchange], 77 :colgroup => [], 78 :i => Attrs, 79 :button => Attrs + [:accesskey, :tabindex, :onfocus, :onblur, :name, :value, :type, :disabled], 80 :script => [:id, :charset, :type, :src, :defer, :space], 81 :col => [], 82 :q => Attrs + [:cite], 83 :p => Attrs, 84 :del => Attrs + [:cite, :datetime], 85 :small => Attrs, 86 :div => Attrs, 87 :tt => Attrs, 88 :ul => Attrs 89 } 90 91 # Additional tags found in XHTML 1.0 Transitional 92 XHTMLTransitionalTags = XHTMLStrictTags.merge \ 93 :strike => Attrs, 94 :center => Attrs, 95 :dir => Attrs + [:compact], 96 :noframes => Attrs, 97 :basefont => [:id, :size, :color, :face], 98 :u => Attrs, 99 :menu => Attrs + [:compact], 100 :iframe => AttrCustom + [:title, :longdesc, :name, :src, :frameborder, :marginwidth, :marginheight, :scrolling, :align, :height, :width], 101 :font => AttrCustom + AttrAnno + [:size, :color, :face], 102 :s => Attrs, 103 :applet => AttrCustom + [:title, :codebase, :archive, :code, :object, :alt, :name, :width, :height, :align, :hspace, :vspace], 104 :isindex => AttrCustom + AttrAnno + [:prompt] 105 106 # Additional attributes found in XHTML 1.0 Transitional 107 { :script => [:language], 108 :a => [:target], 109 :td => [:bgcolor, :nowrap, :height], 110 :p => [:align], 111 :h5 => [:align], 112 :h3 => [:align], 113 :li => [:type, :value], 114 :div => [:align], 115 :pre => AttrEvents + [:width], 116 :body => [:background, :bgcolor, :text, :link, :vlink, :alink], 117 :ol => [:type, :compact, :start], 118 :h4 => [:align], 119 :h2 => [:align], 120 :object => [:align, :border, :hspace, :vspace], 121 :img => [:name, :align, :border, :hspace, :vspace], 122 :link => [:target], 123 :legend => [:align], 124 :dl => [:compact], 125 :input => [:align], 126 :h6 => [:align], 127 :hr => [:align, :noshade, :size, :width], 128 :base => [:target], 129 :ul => [:type, :compact], 130 :br => [:clear], 131 :form => [:name, :target], 132 :area => [:target], 133 :h1 => [:align] 134 }.each do |k, v| 135 XHTMLTransitionalTags[k] += v 136 end 11 137 12 138 FORM_TAGS = [ :form, :input, :select, :textarea ] 13 139 SELF_CLOSING_TAGS = [ :hr, :br, :link, :meta, :input ] 140 NO_PROXY = [ :hr, :br ] 14 141 15 142 end -
trunk/test/test_markaby.rb
r49 r60 13 13 14 14 class MarkabyTest < Test::Unit::TestCase 15 15 16 16 def mab(*args, &block) 17 17 Markaby::Builder.new(*args, &block).to_s 18 end 19 20 def assert_exception(exclass, exmsg, *mab_args, &block) 21 begin 22 mab(*mab_args, &block) 23 rescue Exception => e 24 assert_equal exclass, e.class 25 assert_equal exmsg, e.message 26 end 18 27 end 19 28 … … 65 74 end 66 75 76 def test_fragments 77 assert_equal %{<div>\n<h1>Monkeys</h1>\n<h2>\nGiraffes <small>Miniature</small>\n and <strong>Large</strong>\n</h2>\n<h3>Donkeys</h3>\n<h4>\nParakeet <b>\n<i>Innocent IV</i>\n</b>\n in Classic Chartreuse</h4>\n</div>\n}, 78 mab { div { h1 "Monkeys"; h2 { "Giraffes #{small 'Miniature' } and #{strong 'Large'}" }; h3 "Donkeys"; h4 { "Parakeet #{b { i 'Innocent IV' }} in Classic Chartreuse" } } } 79 assert_equal %{<div>\n<h1>Monkeys</h1>\n<h2>\nGiraffes <strong>Miniature</strong>\n</h2>\n<h3>Donkeys</h3>\n</div>\n}, 80 mab { div { h1 "Monkeys"; h2 { "Giraffes #{strong 'Miniature' }" }; h3 "Donkeys" } } 81 assert_equal %{<div>\n<h1>Monkeys</h1>\n<h2>\nGiraffes <small>Miniature</small>\n and <strong>Large</strong>\n</h2>\n<h3>Donkeys</h3>\n<h4>\nParakeet <strong>Large</strong>\n as well...</h4>\n</div>\n}, 82 mab { div { @a = small 'Miniature'; @b = strong 'Large'; h1 "Monkeys"; h2 { "Giraffes #{@a} and #{@b}" }; h3 "Donkeys"; h4 { "Parakeet #{@b} as well..." } } } 83 end 84 85 def test_invalid_xhtml 86 assert_exception(NoMethodError, "no such method `dav'") { dav {} } 87 assert_exception(Markaby::InvalidXhtmlError, "no attribute `styl' on div elements") { div(:styl => 'ok') {} } 88 assert_exception(Markaby::InvalidXhtmlError, "no attribute `class' on tbody elements") { tbody.okay {} } 89 end 90 91 def test_full_doc_transitional 92 doc = %{<?xml version=\"1.0\" encoding=\"UTF-8\"?> 93 <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"DTD/xhtml1-transitional.dtd\"> 94 <html xml:lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\"> 95 <head>\n<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\"/>\n<title>OKay</title>\n</head>\n</html>\n} 96 assert_equal doc, mab { instruct!; html { head { title 'OKay' } } } 97 end 98 99 def test_full_doc_transitional 100 doc = %{<?xml version=\"1.0\" encoding=\"UTF-8\"?> 101 <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"DTD/xhtml1-strict.dtd\"> 102 <html lang=\"en\" xml:lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"> 103 <head>\n<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\"/>\n<title>OKay</title>\n</head>\n</html>\n} 104 assert_equal doc, mab { xhtml_strict { head { title 'OKay' } } } 105 end 106 67 107 end -
trunk/tools/rakehelp.rb
r22 r60 80 80 s.extra_rdoc_files = [ "README" ] 81 81 dependencies.each do |dep| 82 s.add_dependency( dep)82 s.add_dependency(*dep) 83 83 end 84 84 s.files = %w(README Rakefile setup.rb) +