markaby 0.4 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -73,44 +73,13 @@ This doesn't effect Rails users, but when used in regular Ruby code, it can
73
73
  be a bit disorienting. You are recommended to put your Markaby code in a
74
74
  module where it won't mix with anything.
75
75
 
76
- = A Note About Rails Helpers
77
-
78
- When used in Rails templates, the Rails helper object is passed into
79
- Markaby::Builder. When you call helper methods inside Markaby, the output
80
- from those methods will be output to the stream. This is incredibly
81
- handy, since most Rails helpers output HTML tags.
82
-
83
- head do
84
- javascript_include_tag 'prototype'
85
- autodiscovery_link_tag
86
- end
87
-
88
- However, some methods are designed to give back a String which you can use
89
- elsewhere. Call the <tt>@helpers</tt> object with the method and you'll get
90
- the String back and nothing will be output.
91
-
92
- p "Total is: #{@helper.number_to_human_size @file_bytes}"
93
-
94
- Conversely, you may call instance variables from your controller by using
95
- a method and its value will be returned, nothing will be output.
96
-
97
- # Inside imaginary ProductController
98
- def list
99
- @products = Product.find :all
100
- end
101
-
102
- # Inside app/views/product/list.mab
103
- products.each do |product|
104
- p product.title
105
- end
106
-
107
- = A Quick Tour
76
+ = The Six Steps of Markaby
108
77
 
109
78
  If you dive right into Markaby, it'll probably make good sense, but you're
110
- likely to run into a few kinks. Keep these pointers in mind and everything
111
- will be fine.
79
+ likely to run into a few kinks. Why not review these six steps and commit
80
+ them memory so you can really *know* what you're doing?
112
81
 
113
- == Element Classes
82
+ == 1. Element Classes
114
83
 
115
84
  Element classes may be added by hooking methods onto container elements:
116
85
 
@@ -128,15 +97,15 @@ Which results in:
128
97
  <div class="entryContent">Okay, once again, the idea here is ...</div>
129
98
  </div>
130
99
 
131
- == Element IDs
100
+ == 2. Element IDs
132
101
 
133
102
  IDs may be added by the use of bang methods:
134
103
 
135
- div.page!
136
- div.content!
104
+ div.page! {
105
+ div.content! {
137
106
  h1 "A Short Short Saintly Dog"
138
- end
139
- end
107
+ }
108
+ }
140
109
 
141
110
  Which results in:
142
111
 
@@ -146,29 +115,87 @@ Which results in:
146
115
  </div>
147
116
  </div>
148
117
 
149
- == Markaby assumes XHTML 1.0 Transitional
118
+ == 3. Validate Your XHTML 1.0 Output
150
119
 
151
- Output defaults to XHTML 1.0 Transitional. To do XHTML 1.0 Strict,
152
- try this:
120
+ If you'd like Markaby to help you assemble valid XHTML documents,
121
+ you can use the <tt>xhtml_transitional</tt> or <tt>xhtml_strict</tt>
122
+ methods in place of the normal <tt>html</tt> tag.
153
123
 
154
124
  xhtml_strict do
155
- # innerds
125
+ head { ... }
126
+ body { ... }
156
127
  end
157
128
 
158
- == The <tt>capture</tt> Method
129
+ This will add the XML instruction and the doctype tag to your document.
130
+ Also, a character set meta tag will be placed inside your <tt>head</tt>
131
+ tag.
132
+
133
+ Now, since Markaby knows which doctype you're using, it checks a big
134
+ list of valid tags and attributes before printing anything.
135
+
136
+ >> div :styl => "padding: 10px" do
137
+ >> img :src => "samorost.jpg"
138
+ >> end
139
+ InvalidHtmlError: no such attribute `styl'
140
+
141
+ Markaby will also make sure you don't use the same element ID twice!
142
+
143
+ == 4. Escape or No Escape?
144
+
145
+ Markaby uses a simple convention for escaping stuff: if a string
146
+ is an argument, it gets escaped. If the string is in a block, it
147
+ doesn't.
148
+
149
+ This is handy if you're using something like RedCloth or
150
+ RDoc inside an element. Pass the string back through the block
151
+ and it'll skip out of escaping.
152
+
153
+ div.comment { RedCloth.new(str).to_html }
154
+
155
+ But, if we have some raw text that needs escaping, pass it in
156
+ as an argument:
157
+
158
+ div.comment raw_str
159
+
160
+ One caveat: if you have other tags inside a block, the string
161
+ passed back will be ignored.
159
162
 
160
- Want to catch a block of HTML as a string and play with it a bit?
161
- Use the <tt>capture</tt> method.
163
+ div.comment {
164
+ div.author "_why"
165
+ div.says "Torpedoooooes!"
166
+ "<div>Silence.</div>"
167
+ }
162
168
 
163
- Commonly used to join HTML blocks together:
169
+ The final div above won't appear in the output. You can't mix
170
+ tag modes like that, friend.
171
+
172
+ == 5. Auto-stringification
173
+
174
+ If you end up using any of your Markaby "tags" as a string, the
175
+ tag won't be output. It'll be up to you to add the new string
176
+ back into the HTML output.
177
+
178
+ This means if you call <tt>to_s</tt>, you'll get a string back.
179
+
180
+ div.title { "Rock Bottom" + span(" by Robert Wyatt").to_s }
181
+
182
+ But, when you're adding strings in Ruby, <tt>to_s</tt> happens automatically.
183
+
184
+ div.title { "Rock Bottom" + span(" by Robert Wyatt") }
185
+
186
+ Interpolation works fine.
187
+
188
+ div.title { "Rock Bottom #{span(" by Robert Wyatt")}" }
189
+
190
+ And any other operation you might perform on a string.
164
191
 
165
192
  div.menu! \
166
193
  ['5.gets', 'bits', 'cult', 'inspect', '-h'].map do |category|
167
- capture { link_to category }
194
+ link_to category
168
195
  end.
169
196
  join( " | " )
170
197
 
171
- == The <tt>tag!</tt> Method
198
+ == 6. The <tt>tag!</tt> Method
172
199
 
173
200
  If you need to force a tag at any time, call <tt>tag!</tt> with the
174
201
  tag name followed by the possible arguments and block. The CssProxy
@@ -180,6 +207,45 @@ won't work with this technique.
180
207
  end
181
208
  end
182
209
 
210
+ = A Note About Rails Helpers
211
+
212
+ When used in Rails templates, the Rails helper object is passed into
213
+ Markaby::Builder. When you call helper methods inside Markaby, the output
214
+ from those methods will be output to the stream. This is incredibly
215
+ handy, since most Rails helpers output HTML tags.
216
+
217
+ head do
218
+ javascript_include_tag 'prototype'
219
+ autodiscovery_link_tag
220
+ end
221
+
222
+ However, some methods are designed to give back a String which you can use
223
+ elsewhere. That's okay! Every method returns a Fragment object, which can
224
+ be used as a string.
225
+
226
+ p { "Total is: #{number_to_human_size @file_bytes}" }
227
+
228
+ Also see the Quick Tour above, specifically the stuff about auto-stringification.
229
+
230
+ If for any reason you have trouble with fragments, you can just
231
+ call the <tt>@helpers</tt> object with the method and you'll get
232
+ the String back and nothing will be output.
233
+
234
+ p { "Total is: #{@helper.number_to_human_size @file_bytes}" }
235
+
236
+ Conversely, you may call instance variables from your controller by using
237
+ a method and its value will be returned, nothing will be output.
238
+
239
+ # Inside imaginary ProductController
240
+ def list
241
+ @products = Product.find :all
242
+ end
243
+
244
+ # Inside app/views/product/list.mab
245
+ products.each do |product|
246
+ p product.title
247
+ end
248
+
183
249
  = Credits
184
250
 
185
251
  Markaby is a work of immense hope by Tim Fletcher and why the lucky stiff.
data/Rakefile CHANGED
@@ -7,9 +7,14 @@ require 'tools/rakehelp'
7
7
  require 'fileutils'
8
8
  include FileUtils
9
9
 
10
+ REV = File.read(".svn/entries")[/committed-rev="(\d+)"/, 1] rescue nil
11
+ VERS = ENV['VERSION'] || "0.5" + (REV ? ".#{REV}" : "")
12
+
13
+ task :default => [:package]
14
+
10
15
  setup_tests
11
16
  setup_rdoc ['README', 'CHANGELOG', 'lib/**/*.rb']
12
17
 
13
18
  summary = "Markup as Ruby, write HTML in your native Ruby tongue"
14
19
  test_file = "test/test_markaby.rb"
15
- setup_gem("markaby", "0.4", "Tim Fletcher and _why", summary, ['builder'], test_file)
20
+ setup_gem("markaby", VERS, "Tim Fletcher and _why", summary, [['builder', '>=2.0.0']], test_file)
@@ -19,7 +19,9 @@ $:.unshift File.expand_path(File.dirname(__FILE__))
19
19
  # * Markaby::Template: a class for hooking Markaby into Rails as a
20
20
  # proper templating language.
21
21
  module Markaby
22
- VERSION = '0.4'
22
+ VERSION = '0.5'
23
+
24
+ class InvalidXhtmlError < Exception; end
23
25
  end
24
26
 
25
27
  unless defined?(Builder)
@@ -30,5 +32,4 @@ end
30
32
  require 'markaby/builder'
31
33
  require 'markaby/cssproxy'
32
34
  require 'markaby/metaid'
33
- require 'markaby/tags'
34
35
  require 'markaby/template'
@@ -1,3 +1,5 @@
1
+ require 'markaby/tags'
2
+
1
3
  module Markaby
2
4
  # The Markaby::Builder class is the central gear in the system. When using
3
5
  # from Ruby code, this is the only class you need to instantiate directly.
@@ -19,22 +21,27 @@ module Markaby
19
21
  class Builder
20
22
 
21
23
  @@default = {
22
- :indent => 2,
24
+ :indent => 0,
23
25
  :output_helpers => true,
24
26
  :output_xml_instruction => true,
25
27
  :output_meta_tag => true,
26
- :image_tag_options => { :border => '0', :alt => '' }
28
+ :auto_validation => true,
29
+ :tagset => Markaby::XHTMLTransitional
27
30
  }
28
31
 
29
32
  def self.set(option, value)
30
33
  @@default[option] = value
31
34
  end
32
35
 
33
- XHTMLTransitional = ["-//W3C//DTD XHTML 1.0 Transitional//EN", "DTD/xhtml1-transitional.dtd"]
34
-
35
- XHTMLStrict = ["-//W3C//DTD XHTML 1.0 Strict//EN", "DTD/xhtml1-strict.dtd"]
36
+ def self.ignored_helpers
37
+ @@ignored_helpers ||= []
38
+ end
39
+
40
+ def self.ignore_helpers(*helpers)
41
+ ignored_helpers.concat helpers
42
+ end
36
43
 
37
- attr_accessor :output_helpers
44
+ attr_accessor :output_helpers, :tagset
38
45
 
39
46
  # Create a Markaby builder object. Pass in a hash of variable assignments to
40
47
  # +assigns+ which will be available as instance variables inside tag construction
@@ -52,14 +59,13 @@ module Markaby
52
59
  # }
53
60
  #
54
61
  def initialize(assigns = {}, helpers = nil, &block)
55
- @stream = []
62
+ @streams = [[]]
56
63
  @assigns = assigns
57
- @margin = -1
64
+ @elements = {}
58
65
 
59
- @indent = @@default[:indent]
60
- @output_helpers = @@default[:output_helpers]
61
- @output_meta_tag = @@default[:output_meta_tag]
62
- @output_xml_instruction = @@default[:output_xml_instruction]
66
+ @@default.each do |k, v|
67
+ instance_variable_set("@#{k}", @assigns[k] || v)
68
+ end
63
69
 
64
70
  if helpers.nil?
65
71
  @helpers = nil
@@ -79,18 +85,19 @@ module Markaby
79
85
  end
80
86
  end
81
87
 
82
- @margin += 1
83
- @builder = ::Builder::XmlMarkup.new(:indent => @indent, :margin => @margin, :target => @stream)
88
+ @builder = ::Builder::XmlMarkup.new(:indent => @indent, :target => @streams.last)
89
+ class << @builder
90
+ attr_accessor :target, :level
91
+ end
84
92
 
85
93
  if block
86
- r = instance_eval &block
87
- text(r) if to_s.empty?
94
+ text(capture(&block))
88
95
  end
89
96
  end
90
97
 
91
98
  # Returns a string containing the HTML stream. Internally, the stream is stored as an Array.
92
99
  def to_s
93
- @stream.join
100
+ @streams.last.to_s
94
101
  end
95
102
 
96
103
  # Write a +string+ to the HTML stream without escaping it.
@@ -111,89 +118,111 @@ module Markaby
111
118
  # => "<h1>TITLE</h1>\n<h2>CAPTURE ME</h2>\n"
112
119
  #
113
120
  def capture(&block)
114
- old_stream = @stream.dup
115
- @stream.replace []
116
- str = instance_eval(&block).to_s
117
- str = @stream.join unless @stream.empty?
118
- @stream.replace old_stream
121
+ @streams.push(builder.target = [])
122
+ @builder.level += 1
123
+ str = instance_eval(&block)
124
+ str = @streams.last.join if @streams.last.any?
125
+ @streams.pop
126
+ @builder.level -= 1
127
+ builder.target = @streams.last
119
128
  str
120
129
  end
121
130
 
122
- # Content_for will store the given block in an instance variable for later use
123
- # in another template or in the layout.
124
- #
125
- # The name of the instance variable is content_for_<name> to stay consistent
126
- # with @content_for_layout which is used by ActionView's layouts.
127
- #
128
- # Example:
129
- #
130
- # content_for("header") do
131
- # h1 "Half Shark and Half Lion"
132
- # end
133
- #
134
- # If used several times, the variable will contain all the parts concatenated.
135
- def content_for(name, &block)
136
- eval "@content_for_#{name} = (@content_for_#{name} || '') + capture(&block)"
137
- end
138
-
139
131
  # Create a tag named +tag+. Other than the first argument which is the tag name,
140
132
  # the arguments are the same as the tags implemented via method_missing.
141
133
  def tag!(tag, *args, &block)
134
+ ele_id = nil
135
+ if @auto_validation and @tagset
136
+ if !@tagset.tagset.has_key?(tag)
137
+ raise InvalidXhtmlError, "no element `#{tag}' for #{tagset.doctype}"
138
+ elsif args.last.respond_to?(:to_hash)
139
+ attrs = args.last.to_hash
140
+ attrs.each do |k, v|
141
+ atname = k.to_s.downcase.intern
142
+ unless k =~ /:/ or @tagset.tagset[tag].include? atname
143
+ raise InvalidXhtmlError, "no attribute `#{k}' on #{tag} elements"
144
+ end
145
+ if atname == :id
146
+ ele_id = v.to_s
147
+ if @elements.has_key? ele_id
148
+ raise InvalidXhtmlError, "id `#{ele_id}' already used (id's must be unique)."
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
142
154
  if block
143
155
  str = capture &block
144
156
  block = proc { text(str) }
145
157
  end
146
- @builder.method_missing(tag, *args, &block)
158
+
159
+ f = fragment { @builder.method_missing(tag, *args, &block) }
160
+ @elements[ele_id] = f if ele_id
161
+ f
147
162
  end
148
163
 
149
- # Create XML markup based on the name of the method +sym+. This method is never
150
- # invoked directly, but is called for each markup method in the markup block.
151
- #
152
- # This method is also used to intercept calls to helper methods and instance
164
+ # This method is used to intercept calls to helper methods and instance
153
165
  # variables. Here is the order of interception:
154
166
  #
155
- # * If +sym+ is a recognized HTML tag, the tag is output
156
- # or a CssProxy is returned if no arguments are given.
157
- # * If +sym+ appears to be a self-closing tag, its block
158
- # is ignored, thus outputting a valid self-closing tag.
159
- # * If +sym+ is also the name of an instance variable, the
160
- # value of the instance variable is returned.
161
167
  # * If +sym+ is a helper method, the helper method is called
162
168
  # and output to the stream.
163
- # * Otherwise, +sym+ and its arguments are passed to tag!
169
+ # * If +sym+ is a Builder::XmlMarkup method, it is passed on to the builder object.
170
+ # * If +sym+ is also the name of an instance variable, the
171
+ # value of the instance variable is returned.
172
+ # * If +sym+ has come this far and no +tagset+ is found, +sym+ and its arguments are passed to tag!
173
+ # * If a tagset is found, though, +NoMethodError+ is raised.
174
+ #
175
+ # method_missing used to be the lynchpin in Markaby, but it's no longer used to handle
176
+ # HTML tags. See html_tag for that.
164
177
  def method_missing(sym, *args, &block)
165
- if TAGS.include?(sym) or (FORM_TAGS.include?(sym) and args.empty?)
166
- if args.empty? and block.nil?
167
- return CssProxy.new do |args, block|
168
- if FORM_TAGS.include?(sym) and args.last.respond_to?(:to_hash) and args.last[:id]
169
- args.last[:name] ||= args.last[:id]
170
- end
171
- tag!(sym, *args, &block)
172
- end
173
- end
174
- if args.first.respond_to? :to_hash
175
- block ||= proc{}
176
- end
177
- tag!(sym, *args, &block)
178
- elsif SELF_CLOSING_TAGS.include?(sym)
179
- tag!(sym, *args)
180
- elsif @helpers.respond_to?(sym)
178
+ if @helpers.respond_to?(sym, true) && !self.class.ignored_helpers.include?(sym)
181
179
  r = @helpers.send(sym, *args, &block)
182
- @builder << r if @output_helpers
183
- r
184
- elsif instance_variable_get("@#{sym}")
180
+ if @output_helpers and r.respond_to? :to_str
181
+ fragment { @builder << r }
182
+ else
183
+ r
184
+ end
185
+ elsif ::Builder::XmlMarkup.instance_methods.include?(sym.to_s)
186
+ @builder.__send__(sym, *args, &block)
187
+ elsif instance_variables.include?("@#{sym}")
185
188
  instance_variable_get("@#{sym}")
186
- else
189
+ elsif @tagset.nil?
187
190
  tag!(sym, *args, &block)
191
+ else
192
+ raise NoMethodError, "no such method `#{sym}'"
188
193
  end
189
194
  end
190
195
 
191
- undef_method :p
192
- undef_method :select
196
+ # Every HTML tag method goes through an html_tag call. So, calling <tt>div</tt> is equivalent
197
+ # to calling <tt>html_tag(:div)</tt>. All HTML tags in Markaby's list are given generated wrappers
198
+ # for this method.
199
+ #
200
+ # If the @auto_validation setting is on, this method will check for many common mistakes which
201
+ # could lead to invalid XHTML.
202
+ def html_tag(sym, *args, &block)
203
+ if @auto_validation and @tagset.self_closing.include?(sym) and block
204
+ raise InvalidXhtmlError, "the `\#{sym}' element is self-closing, please remove the block"
205
+ end
206
+ if args.empty? and block.nil? and not NO_PROXY.include?(sym)
207
+ return CssProxy.new do |args, block|
208
+ if @tagset.forms.include?(sym) and args.last.respond_to?(:to_hash) and args.last[:id]
209
+ args.last[:name] ||= args.last[:id]
210
+ end
211
+ tag!(sym, *args, &block)
212
+ end
213
+ end
214
+ if not @tagset.self_closing.include?(sym) and args.first.respond_to?(:to_hash)
215
+ block ||= proc{}
216
+ end
217
+ tag!(sym, *args, &block)
218
+ end
193
219
 
194
- # Builds a image tag. Assumes <tt>:border => '0', :alt => ''</tt>.
195
- def img(opts = {})
196
- tag!(:img, @@default[:image_tag_options].merge(opts))
220
+ XHTMLTransitional.tags.each do |k|
221
+ class_eval %{
222
+ def #{k}(*args, &block)
223
+ html_tag(#{k.inspect}, *args, &block)
224
+ end
225
+ }
197
226
  end
198
227
 
199
228
  # Builds a head tag. Adds a <tt>meta</tt> tag inside with Content-Type
@@ -201,25 +230,59 @@ module Markaby
201
230
  def head(*args, &block)
202
231
  tag!(:head, *args) do
203
232
  tag!(:meta, "http-equiv" => "Content-Type", "content" => "text/html; charset=utf-8") if @output_meta_tag
204
- instance_eval &block
233
+ instance_eval(&block)
205
234
  end
206
235
  end
207
236
 
208
237
  # Builds an html tag. An XML 1.0 instruction and an XHTML 1.0 Transitional doctype
209
238
  # are prepended. Also assumes <tt>:xmlns => "http://www.w3.org/1999/xhtml",
210
- # "xml:lang" => "en", :lang => "en"</tt>.
211
- def html(*doctype, &block)
212
- doctype = XHTMLTransitional if doctype.empty?
213
- @builder.instruct! if @output_xml_instruction
214
- @builder.declare!(:DOCTYPE, :html, :PUBLIC, *doctype)
215
- tag!(:html, :xmlns => "http://www.w3.org/1999/xhtml", "xml:lang" => "en", :lang => "en", &block)
239
+ # :lang => "en"</tt>.
240
+ def xhtml_transitional(&block)
241
+ self.tagset = Markaby::XHTMLTransitional
242
+ xhtml_html &block
216
243
  end
217
- alias_method :xhtml_transitional, :html
218
244
 
219
245
  # Builds an html tag with XHTML 1.0 Strict doctype instead.
220
246
  def xhtml_strict(&block)
221
- html XHTMLStrict, &block
247
+ self.tagset = Markaby::XHTMLStrict
248
+ xhtml_html &block
249
+ end
250
+
251
+ private
252
+
253
+ def xhtml_html(&block)
254
+ instruct! if @output_xml_instruction
255
+ declare!(:DOCTYPE, :html, :PUBLIC, *tagset.doctype)
256
+ tag!(:html, :xmlns => "http://www.w3.org/1999/xhtml", "xml:lang" => "en", :lang => "en", &block)
257
+ end
258
+
259
+ def fragment
260
+ stream = @streams.last
261
+ f1 = stream.length
262
+ yield
263
+ f2 = stream.length - f1
264
+ Fragment.new(stream, f1, f2)
222
265
  end
223
266
 
224
267
  end
268
+
269
+ # Every tag method in Markaby returns a Fragment. If any method gets called on the Fragment,
270
+ # the tag is removed from the Markaby stream and given back as a string. Usually the fragment
271
+ # is never used, though, and the stream stays intact.
272
+ #
273
+ # For a more practical explanation, check out the README.
274
+ class Fragment < ::Builder::BlankSlate
275
+ def initialize(s, a, b)
276
+ @s, @f1, @f2 = s, a, b
277
+ end
278
+ def method_missing(*a)
279
+ unless @str
280
+ @str = @s[@f1, @f2].to_s
281
+ @s[@f1, @f2] = [nil] * @f2
282
+ @str
283
+ end
284
+ @str.send(*a)
285
+ end
286
+ end
287
+
225
288
  end
@@ -12,12 +12,22 @@ module Markaby
12
12
  @blk = blk
13
13
  end
14
14
 
15
+ # Adds attributes to an element, for internal use only. For example, if you
16
+ # want to write a wrapper which sets a bunch of default attributes for a certain
17
+ # tag. Like the default `img' method included with Markaby automatically sets an
18
+ # empty alt attribute.
19
+ def merge!(opts)
20
+ @opts.merge! opts
21
+ self
22
+ end
23
+
15
24
  # Adds attributes to an element. Bang methods set the :id attribute.
16
25
  # Other methods add to the :class attribute. If a block is supplied,
17
26
  # it is executed with a merged hash (@opts + args).
18
27
  def method_missing(id_or_class, *args, &blk)
19
28
  idc = id_or_class.to_s
20
29
  case idc
30
+ when "pass"
21
31
  when /!$/
22
32
  @opts[:id] = $`
23
33
  else
@@ -33,5 +43,11 @@ module Markaby
33
43
  @blk.call(args, blk)
34
44
  end
35
45
  end
46
+
47
+ def to_str
48
+ @blk.call([[@opts]]).to_s
49
+ end
50
+ alias_method :to_s, :to_str
51
+
36
52
  end
37
53
  end
@@ -0,0 +1,46 @@
1
+ module Markaby
2
+
3
+ # Markaby helpers for Rails.
4
+ module ActionControllerHelpers
5
+ # Returns a string of HTML built from the attached +block+. Any +options+ are
6
+ # passed into the render method.
7
+ #
8
+ # Use this method in your controllers to output Markaby directly from inside.
9
+ def render_markaby(options = {}, &block)
10
+ render options.merge({ :text => Builder.new({}, self, &block).to_s })
11
+ end
12
+ end
13
+
14
+ class ActionViewTemplateHandler
15
+ def initialize(action_view)
16
+ @action_view = action_view
17
+ end
18
+ def render(template, local_assigns = {})
19
+ Template.new(template).render(@action_view.assigns.merge(local_assigns), @action_view)
20
+ end
21
+ end
22
+
23
+ class Builder
24
+ # Emulate ERB to satisfy helpers like <tt>form_for</tt>.
25
+ def _erbout; self end
26
+
27
+ # Content_for will store the given block in an instance variable for later use
28
+ # in another template or in the layout.
29
+ #
30
+ # The name of the instance variable is content_for_<name> to stay consistent
31
+ # with @content_for_layout which is used by ActionView's layouts.
32
+ #
33
+ # Example:
34
+ #
35
+ # content_for("header") do
36
+ # h1 "Half Shark and Half Lion"
37
+ # end
38
+ #
39
+ # If used several times, the variable will contain all the parts concatenated.
40
+ def content_for(name, &block)
41
+ @helpers.assigns["content_for_#{name}"] =
42
+ eval("@content_for_#{name} = (@content_for_#{name} || '') + capture(&block)")
43
+ end
44
+ end
45
+
46
+ end
@@ -1,15 +1,165 @@
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
- ]
11
-
12
3
  FORM_TAGS = [ :form, :input, :select, :textarea ]
13
- SELF_CLOSING_TAGS = [ :hr, :br, :link, :meta, :input ]
4
+ SELF_CLOSING_TAGS = [ :base, :meta, :link, :hr, :br, :param, :img, :area, :input, :col ]
5
+ NO_PROXY = [ :hr, :br ]
6
+
7
+ # Common sets of attributes.
8
+ AttrCore = [:id, :class, :style, :title]
9
+ AttrI18n = [:lang, 'xml:lang'.intern, :dir]
10
+ AttrEvents = [:onclick, :ondblclick, :onmousedown, :onmouseup, :onmouseover, :onmousemove,
11
+ :onmouseout, :onkeypress, :onkeydown, :onkeyup]
12
+ AttrFocus = [:accesskey, :tabindex, :onfocus, :onblur]
13
+ AttrHAlign = [:align, :char, :charoff]
14
+ AttrVAlign = [:valign]
15
+ Attrs = AttrCore + AttrI18n + AttrEvents
16
+
17
+ # All the tags and attributes from XHTML 1.0 Strict
18
+ class XHTMLStrict
19
+ class << self
20
+ attr_accessor :tags, :tagset, :forms, :self_closing, :doctype
21
+ end
22
+ @doctype = ["-//W3C//DTD XHTML 1.0 Strict//EN", "DTD/xhtml1-strict.dtd"]
23
+ @tagset = {
24
+ :html => AttrI18n + [:id, :xmlns],
25
+ :head => AttrI18n + [:id, :profile],
26
+ :title => AttrI18n + [:id],
27
+ :base => [:href, :id],
28
+ :meta => AttrI18n + [:id, :http, :name, :content, :scheme, 'http-equiv'.intern],
29
+ :link => Attrs + [:charset, :href, :hreflang, :type, :rel, :rev, :media],
30
+ :style => AttrI18n + [:id, :type, :media, :title, 'xml:space'.intern],
31
+ :script => [:id, :charset, :type, :src, :defer, 'xml:space'.intern],
32
+ :noscript => Attrs,
33
+ :body => Attrs + [:onload, :onunload],
34
+ :div => Attrs,
35
+ :p => Attrs,
36
+ :ul => Attrs,
37
+ :ol => Attrs,
38
+ :li => Attrs,
39
+ :dl => Attrs,
40
+ :dt => Attrs,
41
+ :dd => Attrs,
42
+ :address => Attrs,
43
+ :hr => Attrs,
44
+ :pre => Attrs + ['xml:space'.intern],
45
+ :blockquote => Attrs + [:cite],
46
+ :ins => Attrs + [:cite, :datetime],
47
+ :del => Attrs + [:cite, :datetime],
48
+ :a => Attrs + AttrFocus + [:charset, :type, :name, :href, :hreflang, :rel, :rev, :shape, :coords],
49
+ :span => Attrs,
50
+ :bdo => AttrCore + AttrEvents + [:lang, 'xml:lang'.intern, :dir],
51
+ :br => AttrCore,
52
+ :em => Attrs,
53
+ :strong => Attrs,
54
+ :dfn => Attrs,
55
+ :code => Attrs,
56
+ :samp => Attrs,
57
+ :kbd => Attrs,
58
+ :var => Attrs,
59
+ :cite => Attrs,
60
+ :abbr => Attrs,
61
+ :acronym => Attrs,
62
+ :q => Attrs + [:cite],
63
+ :sub => Attrs,
64
+ :sup => Attrs,
65
+ :tt => Attrs,
66
+ :i => Attrs,
67
+ :b => Attrs,
68
+ :big => Attrs,
69
+ :small => Attrs,
70
+ :object => Attrs + [:declare, :classid, :codebase, :data, :type, :codetype, :archive, :standby, :height, :width, :usemap, :name, :tabindex],
71
+ :param => [:id, :name, :value, :valuetype, :type],
72
+ :img => Attrs + [:src, :alt, :longdesc, :height, :width, :usemap, :ismap],
73
+ :map => AttrI18n + AttrEvents + [:id, :class, :style, :title, :name],
74
+ :area => Attrs + AttrFocus + [:shape, :coords, :href, :nohref, :alt],
75
+ :form => Attrs + [:action, :method, :enctype, :onsubmit, :onreset, :accept, :accept],
76
+ :label => Attrs + [:for, :accesskey, :onfocus, :onblur],
77
+ :input => Attrs + AttrFocus + [:type, :name, :value, :checked, :disabled, :readonly, :size, :maxlength, :src, :alt, :usemap, :onselect, :onchange, :accept],
78
+ :select => Attrs + [:name, :size, :multiple, :disabled, :tabindex, :onfocus, :onblur, :onchange],
79
+ :optgroup => Attrs + [:disabled, :label],
80
+ :option => Attrs + [:selected, :disabled, :label, :value],
81
+ :textarea => Attrs + AttrFocus + [:name, :rows, :cols, :disabled, :readonly, :onselect, :onchange],
82
+ :fieldset => Attrs,
83
+ :legend => Attrs + [:accesskey],
84
+ :button => Attrs + AttrFocus + [:name, :value, :type, :disabled],
85
+ :table => Attrs + [:summary, :width, :border, :frame, :rules, :cellspacing, :cellpadding],
86
+ :caption => Attrs,
87
+ :colgroup => Attrs + AttrHAlign + AttrVAlign + [:span, :width],
88
+ :col => Attrs + AttrHAlign + AttrVAlign + [:span, :width],
89
+ :thead => Attrs + AttrHAlign + AttrVAlign,
90
+ :tfoot => Attrs + AttrHAlign + AttrVAlign,
91
+ :tbody => Attrs + AttrHAlign + AttrVAlign,
92
+ :tr => Attrs + AttrHAlign + AttrVAlign,
93
+ :th => Attrs + AttrHAlign + AttrVAlign + [:abbr, :axis, :headers, :scope, :rowspan, :colspan],
94
+ :td => Attrs + AttrHAlign + AttrVAlign + [:abbr, :axis, :headers, :scope, :rowspan, :colspan],
95
+ :h1 => Attrs,
96
+ :h2 => Attrs,
97
+ :h3 => Attrs,
98
+ :h4 => Attrs,
99
+ :h5 => Attrs,
100
+ :h6 => Attrs
101
+ }
102
+
103
+ @tags = @tagset.keys
104
+ @forms = @tags & FORM_TAGS
105
+ @self_closing = @tags & SELF_CLOSING_TAGS
106
+ end
107
+
108
+ # Additional tags found in XHTML 1.0 Transitional
109
+ class XHTMLTransitional
110
+ class << self
111
+ attr_accessor :tags, :tagset, :forms, :self_closing, :doctype
112
+ end
113
+ @doctype = ["-//W3C//DTD XHTML 1.0 Transitional//EN", "DTD/xhtml1-transitional.dtd"]
114
+ @tagset = XHTMLStrict.tagset.merge \
115
+ :strike => Attrs,
116
+ :center => Attrs,
117
+ :dir => Attrs + [:compact],
118
+ :noframes => Attrs,
119
+ :basefont => [:id, :size, :color, :face],
120
+ :u => Attrs,
121
+ :menu => Attrs + [:compact],
122
+ :iframe => AttrCore + [:longdesc, :name, :src, :frameborder, :marginwidth, :marginheight, :scrolling, :align, :height, :width],
123
+ :font => AttrCore + AttrI18n + [:size, :color, :face],
124
+ :s => Attrs,
125
+ :applet => AttrCore + [:codebase, :archive, :code, :object, :alt, :name, :width, :height, :align, :hspace, :vspace],
126
+ :isindex => AttrCore + AttrI18n + [:prompt]
127
+
128
+ # Additional attributes found in XHTML 1.0 Transitional
129
+ { :script => [:language],
130
+ :a => [:target],
131
+ :td => [:bgcolor, :nowrap, :width, :height],
132
+ :p => [:align],
133
+ :h5 => [:align],
134
+ :h3 => [:align],
135
+ :li => [:type, :value],
136
+ :div => [:align],
137
+ :pre => [:width],
138
+ :body => [:background, :bgcolor, :text, :link, :vlink, :alink],
139
+ :ol => [:type, :compact, :start],
140
+ :h4 => [:align],
141
+ :h2 => [:align],
142
+ :object => [:align, :border, :hspace, :vspace],
143
+ :img => [:name, :align, :border, :hspace, :vspace],
144
+ :link => [:target],
145
+ :legend => [:align],
146
+ :dl => [:compact],
147
+ :input => [:align],
148
+ :h6 => [:align],
149
+ :hr => [:align, :noshade, :size, :width],
150
+ :base => [:target],
151
+ :ul => [:type, :compact],
152
+ :br => [:clear],
153
+ :form => [:name, :target],
154
+ :area => [:target],
155
+ :h1 => [:align]
156
+ }.each do |k, v|
157
+ @tagset[k] += v
158
+ end
159
+
160
+ @tags = @tagset.keys
161
+ @forms = @tags & FORM_TAGS
162
+ @self_closing = @tags & SELF_CLOSING_TAGS
163
+ end
14
164
 
15
165
  end
@@ -5,54 +5,105 @@ module MarkabyTestHelpers
5
5
  def link_to(obj)
6
6
  %{<a href="">#{obj}</a>}
7
7
  end
8
- def pluralize(n, string)
9
- n == 1 ? string : string + "s"
8
+ def pluralize(string)
9
+ string + "s"
10
10
  end
11
11
  module_function :link_to, :pluralize
12
12
  end
13
13
 
14
14
  class MarkabyTest < Test::Unit::TestCase
15
-
16
- def mab(string, assigns = {}, helpers = nil)
17
- Markaby::Template.new(string.to_s).render(assigns, helpers)
15
+
16
+ def mab(*args, &block)
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
 
20
29
  def test_simple
21
- assert_equal "<hr/>\n", mab("hr")
22
- assert_equal "<p>foo</p>\n", mab("p 'foo'")
23
- assert_equal "<p>\nfoo</p>\n", mab("p { 'foo' }")
30
+ assert_equal "<hr/>", mab { hr }
31
+ assert_equal "<p>foo</p>", mab { p 'foo' }
32
+ assert_equal "<p>foo</p>", mab { p { 'foo' } }
24
33
  end
25
34
 
26
35
  def test_classes_and_ids
27
- assert_equal %{<div class="one"></div>\n}, mab("div.one ''")
28
- assert_equal %{<div class="one two"></div>\n}, mab("div.one.two ''")
29
- assert_equal %{<div id="three"></div>\n}, mab("div.three! ''")
36
+ assert_equal %{<div class="one"></div>}, mab { div.one '' }
37
+ assert_equal %{<div class="one two"></div>}, mab { div.one.two '' }
38
+ assert_equal %{<div id="three"></div>}, mab { div.three! '' }
30
39
  end
31
40
 
32
41
  def test_escaping
33
- assert_equal "<h1>Apples &amp; Oranges</h1>\n", mab("h1 'Apples & Oranges'")
34
- assert_equal "<h1>\nApples & Oranges</h1>\n", mab("h1 { 'Apples & Oranges' }")
35
- assert_equal "<h1 class=\"fruits&amp;floots\">Apples</h1>\n", mab("h1 'Apples', :class => 'fruits&floots'")
42
+ assert_equal "<h1>Apples &amp; Oranges</h1>", mab { h1 'Apples & Oranges' }
43
+ assert_equal "<h1>Apples & Oranges</h1>", mab { h1 { 'Apples & Oranges' } }
44
+ assert_equal "<h1 class=\"fruits&amp;floots\">Apples</h1>", mab { h1 'Apples', :class => 'fruits&floots' }
36
45
  end
37
46
 
38
47
  def test_capture
39
- html = "<div>\n<h1>hello world</h1>\n</div>\n"
40
- assert_equal html, mab("div { h1 'hello world' }")
41
- assert_equal html, mab("div { capture { h1 'hello world' } }")
42
- assert mab("capture { h1 'hello world' }").empty?
48
+ builder = Markaby::Builder.new
49
+ assert builder.to_s.empty?
50
+ assert_equal "<h1>TEST</h1>", builder.capture { h1 'TEST' }
51
+ assert builder.to_s.empty?
52
+ assert mab { capture { h1 'hello world' }; nil }.empty?
53
+ assert_equal mab { div { h1 'TEST' } }, mab { div { capture { h1 'TEST' } } }
43
54
  end
44
55
 
45
56
  def test_ivars
46
- html = "<div>\n<h1>Steve</h1>\n<div>\n<h2>Gerald</h2>\n</div>\n<h3>Gerald</h3>\n</div>\n"
47
- assert_equal html, mab("div { @name = 'Steve'; h1 @name; div { @name = 'Gerald'; h2 @name }; h3 @name }")
48
- assert_equal html, mab("div { @name = 'Steve'; h1 @name; self << capture { div { @name = 'Gerald'; h2 @name } }; h3 @name }")
49
- assert_equal html, mab("div { h1 @name; self << capture { div { @name = 'Gerald'; h2 @name } }; h3 @name }",
50
- :name => 'Steve')
57
+ html = "<div><h1>Steve</h1><div><h2>Gerald</h2></div><h3>Gerald</h3></div>"
58
+ assert_equal html, mab { div { @name = 'Steve'; h1 @name; div { @name = 'Gerald'; h2 @name }; h3 @name } }
59
+ assert_equal html, mab { div { @name = 'Steve'; h1 @name; self << capture { div { @name = 'Gerald'; h2 @name } }; h3 @name } }
60
+ assert_equal html, mab(:name => 'Steve') { div { h1 @name; self << capture { div { @name = 'Gerald'; h2 @name } }; h3 @name } }
61
+ end
62
+
63
+ def test_ivars_without_at_symbol
64
+ assert_equal "<h1>Hello World</h1>", mab { @message = 'Hello World'; h1 message }
65
+ end
66
+
67
+ def test_helpers
68
+ Markaby::Builder.ignored_helpers.clear
69
+ assert_equal %{squirrels}, mab({}, MarkabyTestHelpers) { pluralize('squirrel') }
70
+ assert_equal %{<a href="">edit</a>}, mab({}, MarkabyTestHelpers) { link_to('edit') }
71
+ assert mab({}, MarkabyTestHelpers) { @output_helpers = false; link_to('edit'); nil }.empty?
72
+ Markaby::Builder.ignore_helpers :pluralize
73
+ assert_exception(NoMethodError, "no such method `pluralize'", {}, MarkabyTestHelpers) { pluralize('squirrel') }
74
+ end
75
+
76
+ def test_builder_bang_methods
77
+ assert_equal "<?xml version=\"1.0\" encoding=\"UTF-8\"?>", mab { instruct! }
78
+ end
79
+
80
+ def test_fragments
81
+ assert_equal %{<div><h1>Monkeys</h1><h2>Giraffes <small>Miniature</small> and <strong>Large</strong></h2><h3>Donkeys</h3><h4>Parakeet <b><i>Innocent IV</i></b> in Classic Chartreuse</h4></div>},
82
+ mab { div { h1 "Monkeys"; h2 { "Giraffes #{small 'Miniature' } and #{strong 'Large'}" }; h3 "Donkeys"; h4 { "Parakeet #{b { i 'Innocent IV' }} in Classic Chartreuse" } } }
83
+ assert_equal %{<div><h1>Monkeys</h1><h2>Giraffes <strong>Miniature</strong></h2><h3>Donkeys</h3></div>},
84
+ mab { div { h1 "Monkeys"; h2 { "Giraffes #{strong 'Miniature' }" }; h3 "Donkeys" } }
85
+ assert_equal %{<div><h1>Monkeys</h1><h2>Giraffes <small>Miniature</small> and <strong>Large</strong></h2><h3>Donkeys</h3><h4>Parakeet <strong>Large</strong> as well...</h4></div>},
86
+ mab { div { @a = small 'Miniature'; @b = strong 'Large'; h1 "Monkeys"; h2 { "Giraffes #{@a} and #{@b}" }; h3 "Donkeys"; h4 { "Parakeet #{@b} as well..." } } }
87
+ end
88
+
89
+ def test_invalid_xhtml
90
+ assert_exception(NoMethodError, "no such method `dav'") { dav {} }
91
+ assert_exception(Markaby::InvalidXhtmlError, "no attribute `styl' on div elements") { div(:styl => 'ok') {} }
92
+ assert_exception(Markaby::InvalidXhtmlError, "no attribute `class' on tbody elements") { tbody.okay {} }
93
+ end
94
+
95
+ def test_full_doc_transitional
96
+ doc = mab { instruct!; xhtml_transitional { head { title 'OKay' } } }
97
+ assert doc =~ /^<\?xml version="1.0" encoding="UTF-8"\?>/
98
+ assert doc.include?(%{"-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd">})
99
+ assert doc.include?(%{<title>OKay</title>})
51
100
  end
52
101
 
53
- def test_output_helpers
54
- assert_equal %{<a href="">edit</a>}, mab("link_to('edit')", {}, MarkabyTestHelpers)
55
- assert mab("@output_helpers = false; link_to('edit')", {}, MarkabyTestHelpers).empty?
102
+ def test_full_doc_strict
103
+ doc = mab { xhtml_strict { head { title 'OKay' } } }
104
+ assert doc =~ /^<\?xml version="1.0" encoding="UTF-8"\?>/
105
+ assert doc.include?(%{"-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">})
106
+ assert doc.include?(%{<title>OKay</title>})
56
107
  end
57
108
 
58
109
  end
@@ -79,7 +79,7 @@ def setup_gem(pkg_name, pkg_version, author, summary, dependencies, test_file)
79
79
  s.has_rdoc = true
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) +
85
85
  Dir.glob("{bin,doc,test,lib}/**/*") +
@@ -97,4 +97,10 @@ def setup_gem(pkg_name, pkg_version, author, summary, dependencies, test_file)
97
97
  p.gem_spec = spec
98
98
  p.need_tar = true
99
99
  end
100
+
101
+ task :install do
102
+ sh %{rake package}
103
+ sh %{gem install pkg/#{pkg_name}-#{pkg_version}}
104
+ end
105
+
100
106
  end
metadata CHANGED
@@ -1,10 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.8.11
2
+ rubygems_version: 0.9.0.1
3
3
  specification_version: 1
4
4
  name: markaby
5
5
  version: !ruby/object:Gem::Version
6
- version: "0.4"
7
- date: 2006-04-11 00:00:00 -06:00
6
+ version: "0.5"
7
+ date: 2006-10-03 00:00:00 -06:00
8
8
  summary: Markup as Ruby, write HTML in your native Ruby tongue
9
9
  require_paths:
10
10
  - lib
@@ -25,6 +25,7 @@ required_ruby_version: !ruby/object:Gem::Version::Requirement
25
25
  platform: ruby
26
26
  signing_key:
27
27
  cert_chain:
28
+ post_install_message:
28
29
  authors:
29
30
  - Tim Fletcher and _why
30
31
  files:
@@ -34,12 +35,11 @@ files:
34
35
  - test/test_markaby.rb
35
36
  - lib/markaby
36
37
  - lib/markaby.rb
37
- - lib/markaby/helper.rb
38
- - lib/markaby/tags.rb
39
38
  - lib/markaby/metaid.rb
39
+ - lib/markaby/tags.rb
40
40
  - lib/markaby/builder.rb
41
41
  - lib/markaby/cssproxy.rb
42
- - lib/markaby/view.rb
42
+ - lib/markaby/rails.rb
43
43
  - lib/markaby/template.rb
44
44
  - tools/rakehelp.rb
45
45
  test_files:
@@ -60,7 +60,7 @@ dependencies:
60
60
  version_requirement:
61
61
  version_requirements: !ruby/object:Gem::Version::Requirement
62
62
  requirements:
63
- - - ">"
63
+ - - ">="
64
64
  - !ruby/object:Gem::Version
65
- version: 0.0.0
65
+ version: 2.0.0
66
66
  version:
@@ -1,14 +0,0 @@
1
- module Markaby
2
- # Markaby helpers for Rails.
3
- module ActionControllerHelper
4
- # Returns a string of HTML built from the attached +block+. Any +options+ are
5
- # passed into the render method.
6
- #
7
- # Use this method in your controllers to output Markaby directly from inside.
8
- def render_markaby(options = {}, &block)
9
- render options.merge({
10
- :text => Builder.new(nil, self, &block).to_s
11
- })
12
- end
13
- end
14
- end
@@ -1,10 +0,0 @@
1
- module Markaby
2
- class View
3
- def initialize(action_view)
4
- @action_view = action_view
5
- end
6
- def render(template, local_assigns = {})
7
- Template.new(template).render(@action_view.assigns.merge(local_assigns), @action_view)
8
- end
9
- end
10
- end