mab-cj 0.0.4

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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +19 -0
  3. data/Gemfile +14 -0
  4. data/README.md +224 -0
  5. data/Rakefile +16 -0
  6. data/lib/mab.rb +6 -0
  7. data/lib/mab/builder.rb +45 -0
  8. data/lib/mab/indentation.rb +33 -0
  9. data/lib/mab/kernel_method.rb +6 -0
  10. data/lib/mab/mixin.rb +262 -0
  11. data/lib/mab/rails.rb +20 -0
  12. data/lib/mab/tilt.rb +42 -0
  13. data/lib/mab/version.rb +4 -0
  14. data/mab.gemspec +20 -0
  15. data/test/helper.rb +4 -0
  16. data/test/rails/Rakefile +7 -0
  17. data/test/rails/app/assets/images/rails.png +0 -0
  18. data/test/rails/app/assets/javascripts/application.js +15 -0
  19. data/test/rails/app/assets/stylesheets/application.css +13 -0
  20. data/test/rails/app/controllers/application_controller.rb +17 -0
  21. data/test/rails/app/helpers/application_helper.rb +2 -0
  22. data/test/rails/app/views/application/content_for.html.mab +6 -0
  23. data/test/rails/app/views/application/normal.html.mab +2 -0
  24. data/test/rails/app/views/application/variables.html.mab +2 -0
  25. data/test/rails/app/views/layouts/application.html.mab +15 -0
  26. data/test/rails/config.ru +4 -0
  27. data/test/rails/config/application.rb +54 -0
  28. data/test/rails/config/boot.rb +6 -0
  29. data/test/rails/config/environment.rb +5 -0
  30. data/test/rails/config/environments/development.rb +27 -0
  31. data/test/rails/config/environments/production.rb +67 -0
  32. data/test/rails/config/environments/test.rb +29 -0
  33. data/test/rails/config/initializers/backtrace_silencers.rb +7 -0
  34. data/test/rails/config/initializers/inflections.rb +15 -0
  35. data/test/rails/config/initializers/mime_types.rb +5 -0
  36. data/test/rails/config/initializers/secret_token.rb +7 -0
  37. data/test/rails/config/initializers/session_store.rb +8 -0
  38. data/test/rails/config/initializers/wrap_parameters.rb +14 -0
  39. data/test/rails/config/locales/en.yml +5 -0
  40. data/test/rails/config/routes.rb +3 -0
  41. data/test/rails/script/rails +6 -0
  42. data/test/rails/test/helper.rb +7 -0
  43. data/test/rails/test/test_mab.rb +40 -0
  44. data/test/test_mab_builder.rb +56 -0
  45. data/test/test_mab_indentation.rb +66 -0
  46. data/test/test_mab_kernel_method.rb +10 -0
  47. data/test/test_mab_mixin.rb +250 -0
  48. metadata +118 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6b56014736fcafd7c7f040d47c1b3d6ee14cfa27
4
+ data.tar.gz: 4d6e5b38d9532ef9dab04f6c2b945332ad345f6f
5
+ SHA512:
6
+ metadata.gz: 7b5a2d08f46788cdda2061f7201fea7f4aae7067143e9bc35d714339b431e3033580c97222245d1fc5f81e6eb8e61168e924d231eac4d243c1500293336016f6
7
+ data.tar.gz: 3350aa5ddb6ad086ef32b691c21741592e44f0a8a568de64d4ca1db2f45bf1e1f6457d5d8a2756c8b54f62734ff0949ac4c4c50644eaa24e7e2320f625a975e6
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,14 @@
1
+ source 'https://rubygems.org/'
2
+
3
+ group :development do
4
+ gem "rake"
5
+ end
6
+
7
+ group :test do
8
+ gem "minitest"
9
+ end
10
+
11
+ if ENV['RAILS']
12
+ gem 'rails', '= 3.2.12'
13
+ end
14
+
@@ -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,16 @@
1
+ require 'rake/testtask'
2
+
3
+ task :default => :test
4
+ task :test => 'test:core'
5
+
6
+ namespace 'test' do
7
+ Rake::TestTask.new('core') do |t|
8
+ t.libs << 'lib' << 'test'
9
+ end
10
+
11
+ Rake::TestTask.new('rails') do |t|
12
+ t.libs << 'lib'
13
+ t.test_files = FileList['test/rails/test/test_*.rb']
14
+ end
15
+ end
16
+
@@ -0,0 +1,6 @@
1
+ require 'mab/version'
2
+ require 'mab/mixin'
3
+ require 'mab/indentation'
4
+ require 'mab/builder'
5
+ require 'mab/rails' if defined?(::ActionView)
6
+ require 'mab/tilt' if defined?(::Tilt)
@@ -0,0 +1,45 @@
1
+ module Mab
2
+ class SimpleBuilder
3
+ include Mixin
4
+
5
+ def initialize(assigns = {}, helper = nil, &blk)
6
+ @_helper = helper
7
+ @_result = []
8
+
9
+ assigns.each do |key, value|
10
+ instance_variable_set(:"@#{key}", value)
11
+ end
12
+
13
+ if helper
14
+ helper.instance_variables.each do |var|
15
+ instance_variable_set(var, helper.instance_variable_get(var))
16
+ end
17
+ end
18
+
19
+ capture(&blk) if blk
20
+ end
21
+
22
+ def capture(&blk)
23
+ @_result << mab(&blk)
24
+ end
25
+
26
+ def to_s; @_result.join end
27
+
28
+ def method_missing(name, *args, &blk)
29
+ if @_helper && @_helper.respond_to?(name, true)
30
+ @_helper.send(name, *args, &blk)
31
+ else
32
+ super
33
+ end
34
+ end
35
+ end
36
+
37
+ class Builder < SimpleBuilder
38
+ include HTML5
39
+ end
40
+
41
+ class PrettyBuilder < Builder
42
+ include Indentation
43
+ end
44
+ end
45
+
@@ -0,0 +1,33 @@
1
+ module Mab
2
+ module Indentation
3
+ def mab_insert(str)
4
+ if i = @mab_context.options[:indentation]
5
+ super([$/ + " " * i, str])
6
+ else
7
+ @mab_context.options[:indentation] = 0
8
+ super
9
+ end
10
+ end
11
+
12
+ def mab_done(tag)
13
+ if blk = tag._block
14
+ tag._block = proc do
15
+ begin
16
+ @mab_context.options[:indentation] += 1
17
+ blk.call
18
+ ensure
19
+ @mab_context.options[:indentation] -= 1
20
+ end
21
+ end
22
+ end
23
+ super
24
+ end
25
+
26
+ def reindent!(str)
27
+ str.split(/\r?\n/).each do |s|
28
+ text! s
29
+ end
30
+ end
31
+ end
32
+ end
33
+
@@ -0,0 +1,6 @@
1
+ require 'mab'
2
+
3
+ def mab(&blk)
4
+ Mab::PrettyBuilder.new({}, self, &blk).to_s
5
+ end
6
+
@@ -0,0 +1,262 @@
1
+ require 'cgi'
2
+
3
+ module Mab
4
+ module Mixin
5
+ class Error < StandardError; end
6
+ class Tag
7
+ attr_accessor :_name, :_content, :_attributes, :_block, :_has_content
8
+ attr_reader :_options, :_context, :_instance
9
+
10
+ def initialize(name, options, context, instance = nil)
11
+ @_name = name
12
+ @_options = options
13
+ @_context = context
14
+ @_instance = instance
15
+ @_done = false
16
+
17
+ @_content = nil
18
+ @_has_content = nil
19
+
20
+ @_attributes = {}
21
+
22
+ @_pos = @_context.size
23
+ end
24
+
25
+ def _block
26
+ return @_block unless block_given?
27
+ current = @_block
28
+ @_block = proc { yield current }
29
+ end
30
+
31
+ def _merge_attributes(*args)
32
+ args.each do |attrs|
33
+ @_attributes.merge!(attrs)
34
+ end
35
+ end
36
+
37
+ def method_missing(name, *args, &blk)
38
+ name = name.to_s
39
+
40
+ if name[-1] == ?!
41
+ @_attributes[:id] = name[0..-2]
42
+ else
43
+ if @_attributes.has_key?(:class)
44
+ @_attributes[:class] += " #{name}"
45
+ else
46
+ @_attributes[:class] = name
47
+ end
48
+ end
49
+
50
+ _insert(*args, &blk)
51
+ end
52
+
53
+ def _insert(*args, &blk)
54
+ raise Error, "This tag is already closed" if @_done
55
+
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
60
+ end
61
+
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
71
+
72
+ if block_given?
73
+ raise Error, "Tag doesn't allow content" if @_has_content == false
74
+ @_has_content = true
75
+ @_block = blk
76
+ @_done = true
77
+ end
78
+
79
+ if @_content && @_block
80
+ raise Error, "Both content and _block is not allowed"
81
+ end
82
+
83
+ @_instance.mab_done(self) if @_done
84
+
85
+ if @_block
86
+ before = @_context.children
87
+ res = @_block.call
88
+
89
+ if before >= @_context.children
90
+ @_content = res.to_s
91
+ else
92
+ # Turn the node into just an opening tag.
93
+ @_has_content = false
94
+ @_instance.mab_insert("</#{@_name}>")
95
+ end
96
+ end
97
+
98
+ self
99
+ end
100
+
101
+ def to_ary() nil end
102
+ def to_str() to_s end
103
+
104
+ def _attrs_to_s
105
+ @_attributes.inject("") do |res, (name, value)|
106
+ if value
107
+ value = (value == true) ? name : CGI.escapeHTML(value.to_s)
108
+ res << " #{name}=\"#{value}\""
109
+ end
110
+ res
111
+ end
112
+ end
113
+
114
+ def to_s
115
+ if !@_context.joining? && @_context[@_pos]
116
+ @_context[@_pos] = nil
117
+ @_context.children -= 1
118
+ end
119
+
120
+ res = "<#{@_name}#{_attrs_to_s}"
121
+ res << (@_options[:xml] && !@_block && !@_has_content ? ' />' : '>')
122
+ res << "#{@_content}</#{@_name}>" if @_has_content
123
+ res
124
+ end
125
+ end
126
+
127
+ class Context < Array
128
+ attr_accessor :children, :options
129
+
130
+ def initialize
131
+ @children = 0
132
+ @joining = false
133
+ @options = {}
134
+ end
135
+
136
+ def <<(str)
137
+ @children += 1
138
+ super(str)
139
+ end
140
+
141
+ def join(*)
142
+ @joining = true
143
+ super
144
+ end
145
+
146
+ def joining?
147
+ @joining
148
+ end
149
+ end
150
+
151
+ def mab_tag(name)
152
+ ctx = @mab_context || raise(Error, "Tags can only be written within a `mab { }`-block")
153
+ tag = Tag.new(name, mab_options, ctx, self)
154
+ mab_insert(tag)
155
+ tag
156
+ end
157
+
158
+ def tag!(name, *args, &blk)
159
+ mab_tag(name)._insert(*args, &blk)
160
+ end
161
+
162
+ def text!(str)
163
+ mab_insert(str)
164
+ end
165
+
166
+ def text(str)
167
+ text! CGI.escapeHTML(str.to_s)
168
+ end
169
+
170
+ def mab(&blk)
171
+ prev = defined?(@mab_context) && @mab_context
172
+ ctx = @mab_context = Context.new
173
+ res = instance_eval(&blk)
174
+ ctx.empty? ? res : ctx.join
175
+ ensure
176
+ @mab_context = prev
177
+ end
178
+
179
+ def mab_insert(tag)
180
+ ctx = @mab_context || raise(Error, 'mab { }-block required')
181
+ ctx << tag
182
+ end
183
+
184
+ def mab_done(tag)
185
+ end
186
+
187
+ def mab_options
188
+ @mab_options ||= {}
189
+ end
190
+
191
+ module XML
192
+ include Mixin
193
+
194
+ def mab_options
195
+ @mab_options ||= super.update(:xml => true)
196
+ end
197
+ end
198
+
199
+ module HTMLDefiners
200
+ def define_tag(meth, tag)
201
+ class_eval <<-EOF
202
+ def #{meth}(*args, &blk)
203
+ tag = mab_tag(:#{tag})
204
+ tag._has_content = true
205
+ tag._insert(*args, &blk)
206
+ end
207
+ EOF
208
+ end
209
+
210
+ def define_tags(*tags)
211
+ tags.flatten.each do |tag|
212
+ define_tag(tag, tag)
213
+ end
214
+ end
215
+
216
+ def define_empty_tag(meth, tag)
217
+ class_eval <<-EOF
218
+ def #{meth}(*args, &blk)
219
+ tag = mab_tag(:#{tag})
220
+ tag._has_content = false
221
+ tag._insert(*args, &blk)
222
+ end
223
+ EOF
224
+ end
225
+
226
+ def define_empty_tags(*tags)
227
+ tags.flatten.each do |tag|
228
+ define_empty_tag(tag, tag)
229
+ end
230
+ end
231
+ end
232
+
233
+ module HTML5
234
+ extend HTMLDefiners
235
+ include Mixin
236
+
237
+ define_tags %w[a abbr acronym address applet article aside audio b
238
+ basefont bdi bdo big blockquote body button canvas caption
239
+ center cite code colgroup datalist dd del details dfn dir div dl
240
+ dt em fieldset figcaption figure font footer form frame frameset
241
+ h1 h2 h3 h4 h5 h6 head header hgroup html i iframe ins kbd label
242
+ legend li link map mark math menu meter nav noframes noscript
243
+ object ol optgroup option output p pre progress q rp rt ruby s
244
+ samp script section select small span strike strong style sub
245
+ summary sup svg table tbody td textarea tfoot th thead time
246
+ title tr tt u ul var video xmp]
247
+
248
+ define_empty_tags %w[base link meta hr br wbr img embed param source
249
+ track area col input keygen command]
250
+
251
+ def doctype!
252
+ text! '<!DOCTYPE html>'
253
+ end
254
+ end
255
+
256
+ module XHTML5
257
+ include HTML5
258
+ include XML
259
+ end
260
+ end
261
+ end
262
+