mab 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data/COPYING +19 -0
  2. data/Gemfile +10 -0
  3. data/Gemfile.lock +12 -0
  4. data/README.md +224 -0
  5. data/Rakefile +8 -0
  6. data/bench.rb +37 -0
  7. data/lib/mab/mixin.rb +58 -42
  8. data/lib/mab/version.rb +1 -1
  9. data/mab-0.0.1.gem +0 -0
  10. data/mab.gemspec +20 -0
  11. data/markaby/CHANGELOG.rdoc +106 -0
  12. data/markaby/Markaby.gemspec +109 -0
  13. data/markaby/README.rdoc +317 -0
  14. data/markaby/Rakefile +78 -0
  15. data/markaby/VERSION +1 -0
  16. data/markaby/garlic.rb +29 -0
  17. data/markaby/init.rb +6 -0
  18. data/markaby/lib/markaby.rb +30 -0
  19. data/markaby/lib/markaby/builder.rb +314 -0
  20. data/markaby/lib/markaby/builder_tags.rb +64 -0
  21. data/markaby/lib/markaby/cssproxy.rb +55 -0
  22. data/markaby/lib/markaby/kernel_method.rb +7 -0
  23. data/markaby/lib/markaby/rails.rb +75 -0
  24. data/markaby/lib/markaby/rails/current.rb +85 -0
  25. data/markaby/lib/markaby/rails/deprecated.rb +124 -0
  26. data/markaby/lib/markaby/rails/rails_builder.rb +91 -0
  27. data/markaby/lib/markaby/sinatra.rb +18 -0
  28. data/markaby/lib/markaby/tags.rb +200 -0
  29. data/markaby/lib/markaby/tilt.rb +8 -0
  30. data/markaby/spec/markaby/builder_spec.rb +118 -0
  31. data/markaby/spec/markaby/css_proxy_test.rb +47 -0
  32. data/markaby/spec/markaby/fragment_test.rb +10 -0
  33. data/markaby/spec/markaby/markaby_other_static.mab +1 -0
  34. data/markaby/spec/markaby/markaby_spec.rb +184 -0
  35. data/markaby/spec/markaby/markaby_test.rb +251 -0
  36. data/markaby/spec/markaby/rails/spec_helper.rb +29 -0
  37. data/markaby/spec/markaby/rails/views/layouts/layout.mab +11 -0
  38. data/markaby/spec/markaby/rails/views/markaby/_a_partial.mab +3 -0
  39. data/markaby/spec/markaby/rails/views/markaby/_form_for_with_body_in_erb.erb +1 -0
  40. data/markaby/spec/markaby/rails/views/markaby/_partial_child_with_locals.mab +1 -0
  41. data/markaby/spec/markaby/rails/views/markaby/access_to_helpers.mab +1 -0
  42. data/markaby/spec/markaby/rails/views/markaby/broken.mab +7 -0
  43. data/markaby/spec/markaby/rails/views/markaby/commented_out_template.mab +1 -0
  44. data/markaby/spec/markaby/rails/views/markaby/correct_template_values.mab +5 -0
  45. data/markaby/spec/markaby/rails/views/markaby/double_output.mab +8 -0
  46. data/markaby/spec/markaby/rails/views/markaby/form_for.mab +2 -0
  47. data/markaby/spec/markaby/rails/views/markaby/form_for_with_fields.mab +3 -0
  48. data/markaby/spec/markaby/rails/views/markaby/form_for_with_multiple_fields.mab +4 -0
  49. data/markaby/spec/markaby/rails/views/markaby/no_values_passed.mab +3 -0
  50. data/markaby/spec/markaby/rails/views/markaby/partial_parent.mab +1 -0
  51. data/markaby/spec/markaby/rails/views/markaby/partial_parent_with_locals.mab +7 -0
  52. data/markaby/spec/markaby/rails/views/markaby/render_erb_without_explicit_render_call.erb +1 -0
  53. data/markaby/spec/markaby/rails/views/markaby/render_explicit_but_empty_markaby_layout.mab +0 -0
  54. data/markaby/spec/markaby/rails/views/markaby/render_mab_without_explicit_render_call.mab +3 -0
  55. data/markaby/spec/markaby/rails/views/markaby/render_with_ivar.mab +3 -0
  56. data/markaby/spec/markaby/rails/views/markaby/renders_erb.rhtml +1 -0
  57. data/markaby/spec/markaby/rails/views/markaby/renders_form_for_with_erb_body.mab +3 -0
  58. data/markaby/spec/markaby/rails/views/markaby/routes.mab +1 -0
  59. data/markaby/spec/markaby/rails/views/markaby/yielding.mab +3 -0
  60. data/markaby/spec/markaby/rails/views/markaby/yielding_content_for_with_block_helper.mab +5 -0
  61. data/markaby/spec/markaby/rails/views/markaby/yielding_two.mab +7 -0
  62. data/markaby/spec/markaby/rails/views/markaby/yielding_with_content_for.mab +3 -0
  63. data/markaby/spec/markaby/rails_test.rb +380 -0
  64. data/markaby/spec/markaby/rails_version_spec.rb +37 -0
  65. data/markaby/spec/markaby/sinatra/app.rb +49 -0
  66. data/markaby/spec/markaby/sinatra/sinatra_spec.rb +67 -0
  67. data/markaby/spec/markaby/sinatra/views/helpers.mab +1 -0
  68. data/markaby/spec/markaby/sinatra/views/layout.mab +7 -0
  69. data/markaby/spec/markaby/sinatra/views/markaby_template.mab +0 -0
  70. data/markaby/spec/markaby/sinatra/views/scope_instance_variables.mab +3 -0
  71. data/markaby/spec/markaby/sinatra/views/simple_html.mab +4 -0
  72. data/markaby/spec/markaby/sinatra/views/variables.mab +3 -0
  73. data/markaby/spec/markaby/tilt/erb.erb +1 -0
  74. data/markaby/spec/markaby/tilt/locals.mab +1 -0
  75. data/markaby/spec/markaby/tilt/markaby.mab +1 -0
  76. data/markaby/spec/markaby/tilt/markaby_other_static.mab +1 -0
  77. data/markaby/spec/markaby/tilt/render_twice.mab +1 -0
  78. data/markaby/spec/markaby/tilt/scope.mab +1 -0
  79. data/markaby/spec/markaby/tilt/yielding.mab +2 -0
  80. data/markaby/spec/markaby/tilt_spec.rb +75 -0
  81. data/markaby/spec/spec.opts +2 -0
  82. data/markaby/spec/spec_helper.rb +44 -0
  83. data/test/helper.rb +4 -0
  84. data/test/test_mab_builder.rb +56 -0
  85. data/test/test_mab_indentation.rb +66 -0
  86. data/test/test_mab_kernel_method.rb +10 -0
  87. data/test/test_mab_mixin.rb +246 -0
  88. metadata +92 -6
data/COPYING ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2012 Magnus Holm <judofyr@gmail.com>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to
5
+ deal in the Software without restriction, including without limitation the
6
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ sell copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
+
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source :rubygems
2
+
3
+ group :development do
4
+ gem "rake"
5
+ end
6
+
7
+ group :test do
8
+ gem "minitest"
9
+ end
10
+
@@ -0,0 +1,12 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ minitest (2.10.0)
5
+ rake (0.9.2.2)
6
+
7
+ PLATFORMS
8
+ ruby
9
+
10
+ DEPENDENCIES
11
+ minitest
12
+ rake
@@ -0,0 +1,224 @@
1
+ Mab (Markup as Ruby)
2
+ ====================
3
+
4
+ Mab let's you write HTML in plain Ruby:
5
+
6
+ ```ruby
7
+ doctype!
8
+ html do
9
+ head do
10
+ link :rel => 'stylesheet', :href => 'style.css'
11
+ script :src => 'jquery.js'
12
+ end
13
+ body :id => :frontpage do
14
+ h1 'Hello World', :class => :main
15
+ end
16
+ end
17
+ ```
18
+
19
+
20
+ Syntax
21
+ ------
22
+
23
+ ### 1. Tags and Attributes
24
+
25
+ There are four basic forms:
26
+
27
+ ```ruby
28
+ tagname(content)
29
+
30
+ tagname(content, attributes)
31
+
32
+ tagname do
33
+ content
34
+ end
35
+
36
+ tagname(attributes) do
37
+ content
38
+ end
39
+ ```
40
+
41
+ Example:
42
+
43
+ ```ruby
44
+ doctype!
45
+ html do
46
+ head do
47
+ link :rel => 'stylesheet', :href => 'style.css'
48
+ script :src => 'jquery.js'
49
+ end
50
+ body :id => :frontpage do
51
+ h1 'Hello World', :class => :main
52
+ end
53
+ end
54
+ ```
55
+
56
+ Which results in:
57
+
58
+ ```html
59
+ <!DOCTYPE html>
60
+ <html>
61
+ <head>
62
+ <link rel="stylesheet" href="style.css">
63
+ <script src="jquery"></script>
64
+ </head>
65
+ <body id="frontpage">
66
+ <h1 class="main">Hello World</h1>
67
+ </body>
68
+ </html>
69
+ ```
70
+
71
+ Notice how Mab knows that script tag must have content, so although you didn't
72
+ specify anything it closed the tag for you.
73
+
74
+ ### 2. Element Classes and IDs
75
+
76
+ You can easily add classes and IDs by hooking methods onto the container:
77
+
78
+ ```ruby
79
+ body.frontpage! do
80
+ h1.main 'Hello World'
81
+ end
82
+ ```
83
+
84
+ Which results in:
85
+
86
+ ```html
87
+ <body id="frontpage">
88
+ <h1 class="main">Hello World</h1>
89
+ </body>
90
+ ```
91
+
92
+ You can mix and match as you'd like (`div.klass.klass1.id!`), but you can only
93
+ provide content and attributes on the *last* call:
94
+
95
+ ```ruby
96
+ # This is not valid:
97
+ form(:action => :post).world do
98
+ input
99
+ end
100
+
101
+ # But this is:
102
+ form.world(:action => :post) do
103
+ input
104
+ end
105
+ ```
106
+
107
+ ### 3. Escape or Not Escape
108
+
109
+ Mab uses a very simple convention for escaping: Strings as *arguments* gets
110
+ escaped, strings in *blocks* don't:
111
+
112
+ ```ruby
113
+ div.comment "<script>alert(1)</script>"
114
+ # <div class="comment">&lt;script&gt;alert(1)&lt;/script&gt;</div>
115
+
116
+ div.comment { "I <strong>love</strong> you" }
117
+ # <div class="comment">I <strong>love</strong> you</div>
118
+ ```
119
+
120
+ Be aware that Mab ignores the string in a block if there's other tags there:
121
+
122
+ ```ruby
123
+ div.comment do
124
+ div.author "BitPuffin"
125
+ "<p>Silence!</p>"
126
+ end
127
+ ```
128
+
129
+ The p tag above won't appear in the output.
130
+
131
+ ### 4. Text
132
+
133
+ Sometimes you need to insert plain text:
134
+
135
+ ```ruby
136
+ p.author do
137
+ text 'Written by '
138
+ a 'Bluebie', :href => 'http://creativepony.com/'
139
+ end
140
+ ```
141
+
142
+ Which results in:
143
+
144
+ ```html
145
+ <p class="author">
146
+ Written by
147
+ <a href="http://creativepony.com/">Bluebie</a>
148
+ </p>
149
+ ```
150
+
151
+ There's also `text!` which doesn't escape:
152
+
153
+ ```ruby
154
+ p.author do
155
+ text! '<strong>Written</strong> by'
156
+ a 'Bluebie', :href => 'http://creativepony.com/'
157
+ ```
158
+
159
+
160
+ Invoking Mab
161
+ ------------
162
+
163
+ Using #mab:
164
+
165
+ ```ruby
166
+ require 'mab/kernel_method'
167
+
168
+ str = mab do
169
+ doctype!
170
+ html do
171
+ # ...
172
+ end
173
+ end
174
+ ```
175
+
176
+ Using Mab::Builder (or Mab::PrettyBuilder if you want indentation):
177
+
178
+ ```ruby
179
+ class Person
180
+ attr_reader :name
181
+
182
+ def initialize(name)
183
+ @name = name
184
+ end
185
+
186
+ def awesome?; true end
187
+ end
188
+
189
+ # Assign instance variables:
190
+ Mab::Builder.new(:person => Person.new('BitBuffin')) do
191
+ if @person.awesome?
192
+ h1 @person.name
193
+ else
194
+ p @person.name
195
+ end
196
+ end.to_s
197
+
198
+ # Use helper (methods and instance variables will be available):
199
+ Mab::Builder.new({}, Person.new('BitPuffin')) do
200
+ if awesome?
201
+ h1 @name
202
+ else
203
+ p @name
204
+ end
205
+ end.to_s
206
+ ```
207
+
208
+ Extending an object (*advanced usage*):
209
+
210
+ ```ruby
211
+ r = Object.new
212
+ r.extend Mab::Mixin::HTML5
213
+ r.extend Mab::Indentation
214
+
215
+ r.mab do
216
+ doctype!
217
+ html do
218
+ # ...
219
+ end
220
+ end
221
+
222
+ ```
223
+
224
+
@@ -0,0 +1,8 @@
1
+ require 'rake/testtask'
2
+
3
+ task :default => :test
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << 'test'
7
+ end
8
+
@@ -0,0 +1,37 @@
1
+ $: << 'lib'
2
+
3
+ TMPL = <<-'EOF'
4
+ div.menu! do
5
+ [1, 2, 3, 4].map { |n| a("Section #{n}", :href => "/section/#{n}") }.join ' | '
6
+ end
7
+ EOF
8
+
9
+ require 'mab'
10
+
11
+ class MabExample
12
+ include Mab::Mixin::XHTML5
13
+
14
+ class_eval "def content() #{TMPL} end"
15
+
16
+ def to_html
17
+ mab { content }
18
+ end
19
+ end
20
+
21
+ require 'erector'
22
+
23
+ class ErectorExample < Erector::Widget
24
+ class_eval "def content() #{TMPL} end"
25
+ end
26
+
27
+ puts MabExample.new.to_html
28
+ puts ErectorExample.new.to_html
29
+
30
+ require 'benchmark/ips'
31
+
32
+ Benchmark.ips do |x|
33
+ x.report('Mab') { MabExample.new.to_html }
34
+ x.report('Erector') { ErectorExample.new.to_html }
35
+ end
36
+
37
+
@@ -4,9 +4,8 @@ module Mab
4
4
  module Mixin
5
5
  class Error < StandardError; end
6
6
  class Tag
7
- attr_accessor :name, :content, :block
7
+ attr_accessor :name, :content, :attributes, :block, :has_content
8
8
  attr_reader :options, :context, :instance
9
- attr_writer :attributes, :pos
10
9
 
11
10
  def initialize(name, options, context, instance = nil)
12
11
  @name = name
@@ -14,57 +13,71 @@ module Mab
14
13
  @context = context
15
14
  @instance = instance
16
15
  @done = false
17
- @content = false
16
+
17
+ @content = nil
18
+ @has_content = nil
19
+
20
+ @attributes = {}
21
+
18
22
  @pos = @context.size
19
23
  end
20
24
 
21
- def attributes
22
- @attributes ||= {}
25
+ def block
26
+ return @block unless block_given?
27
+ current = @block
28
+ @block = proc { yield current }
23
29
  end
24
30
 
25
- def merge_attributes(attrs)
26
- if defined?(@attributes)
31
+ def merge_attributes(*args)
32
+ args.each do |attrs|
27
33
  @attributes.merge!(attrs)
28
- else
29
- @attributes = attrs
30
34
  end
31
35
  end
32
36
 
33
- def method_missing(name, content = nil, attrs = nil, &blk)
37
+ def method_missing(name, *args, &blk)
34
38
  name = name.to_s
35
39
 
36
40
  if name[-1] == ?!
37
- attributes[:id] = name[0..-2]
41
+ @attributes[:id] = name[0..-2]
38
42
  else
39
- if attributes.has_key?(:class)
40
- attributes[:class] += " #{name}"
43
+ if @attributes.has_key?(:class)
44
+ @attributes[:class] += " #{name}"
41
45
  else
42
- attributes[:class] = name
46
+ @attributes[:class] = name
43
47
  end
44
48
  end
45
49
 
46
- insert(content, attrs, &blk)
50
+ insert(*args, &blk)
47
51
  end
48
52
 
49
- def insert(content = nil, attrs = nil, &blk)
53
+ def insert(*args, &blk)
50
54
  raise Error, "This tag is already closed" if @done
51
55
 
52
- if content.is_a?(Hash)
53
- attrs = content
54
- content = nil
56
+ if !args.empty? && !args[0].is_a?(Hash)
57
+ content = args.shift
58
+ raise Error, "Tag doesn't allow content" if @has_content == false
59
+ @has_content = true
55
60
  end
56
61
 
57
- merge_attributes(attrs) if attrs
62
+ if content
63
+ @content = CGI.escapeHTML(content.to_s)
64
+ @done = true
65
+ end
66
+
67
+ if !args.empty?
68
+ merge_attributes(*args)
69
+ @done = true
70
+ end
58
71
 
59
72
  if block_given?
73
+ raise Error, "Tag doesn't allow content" if @has_content == false
74
+ @has_content = true
60
75
  @block = blk
61
76
  @done = true
62
- elsif content
63
- content = content.to_s
64
- @content = CGI.escapeHTML(content)
65
- @done = !content.empty?
66
- elsif attrs
67
- @done = true
77
+ end
78
+
79
+ if @content && @block
80
+ raise Error, "Both content and block is not allowed"
68
81
  end
69
82
 
70
83
  @instance.mab_done(self) if @done
@@ -76,7 +89,8 @@ module Mab
76
89
  if before >= @context.children
77
90
  @content = res.to_s
78
91
  else
79
- @content = nil
92
+ # Turn the node into just an opening tag.
93
+ @has_content = false
80
94
  @instance.mab_insert("</#{@name}>")
81
95
  end
82
96
  end
@@ -102,9 +116,10 @@ module Mab
102
116
  @context[@pos] = nil
103
117
  @context.children -= 1
104
118
  end
119
+
105
120
  res = "<#{@name}#{attrs_to_s}"
106
- res << (@options[:xml] && @content == false ? ' />' : '>')
107
- res << "#{@content}</#{@name}>" if @content
121
+ res << (@options[:xml] && !@block && !@has_content ? ' />' : '>')
122
+ res << "#{@content}</#{@name}>" if @has_content
108
123
  res
109
124
  end
110
125
  end
@@ -133,11 +148,15 @@ module Mab
133
148
  end
134
149
  end
135
150
 
136
- def tag!(name, content = nil, attrs = nil, &blk)
151
+ def mab_tag(name)
137
152
  ctx = @mab_context || raise(Error, "Tags can only be written within a `mab { }`-block")
138
153
  tag = Tag.new(name, mab_options, ctx, self)
139
154
  mab_insert(tag)
140
- tag.insert(content, attrs, &blk)
155
+ tag
156
+ end
157
+
158
+ def tag!(name, *args, &blk)
159
+ mab_tag(name).insert(*args, &blk)
141
160
  end
142
161
 
143
162
  def text!(str)
@@ -180,12 +199,10 @@ module Mab
180
199
  module HTMLDefiners
181
200
  def define_tag(meth, tag)
182
201
  class_eval <<-EOF
183
- def #{meth}(content = "", attrs = nil, &blk)
184
- if content.is_a?(Hash)
185
- attrs = content
186
- content = ""
187
- end
188
- tag!(:#{tag}, content, attrs, &blk)
202
+ def #{meth}(*args, &blk)
203
+ tag = mab_tag(:#{tag})
204
+ tag.has_content = true
205
+ tag.insert(*args, &blk)
189
206
  end
190
207
  EOF
191
208
  end
@@ -198,11 +215,10 @@ module Mab
198
215
 
199
216
  def define_empty_tag(meth, tag)
200
217
  class_eval <<-EOF
201
- def #{meth}(attrs = nil)
202
- if (!attrs.nil? && !attrs.is_a?(Hash)) || block_given?
203
- raise Error, "#{meth} doesn't allow content"
204
- end
205
- tag!(:#{tag}, attrs)
218
+ def #{meth}(*args, &blk)
219
+ tag = mab_tag(:#{tag})
220
+ tag.has_content = false
221
+ tag.insert(*args, &blk)
206
222
  end
207
223
  EOF
208
224
  end