mab-cj 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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
+