markaby 0.4 → 0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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