keynote 0.0.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of keynote might be problematic. Click here for more details.

@@ -0,0 +1,40 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'rails/railtie'
4
+
5
+ module Keynote
6
+ # @private
7
+ class Railtie < Rails::Railtie
8
+ config.after_initialize do |app|
9
+ app.config.paths.add 'app/presenters', :eager_load => true
10
+
11
+ if defined?(RSpec::Rails) && RSpec.respond_to?(:configure)
12
+ require 'keynote/testing/rspec'
13
+ end
14
+
15
+ if defined?(MiniTest::Rails)
16
+ require 'keynote/testing/minitest_rails'
17
+ end
18
+
19
+ require "keynote/testing/test_unit"
20
+ end
21
+
22
+ ActiveSupport.on_load(:action_view) do
23
+ include Keynote::Helper
24
+ end
25
+
26
+ ActiveSupport.on_load(:action_controller) do
27
+ include Keynote::Controller
28
+ end
29
+
30
+ ActiveSupport.on_load(:action_mailer) do
31
+ include Keynote::Controller
32
+ end
33
+
34
+ rake_tasks do
35
+ if defined?(MiniTest::Rails)
36
+ load File.expand_path("../testing/minitest_rails.rake", __FILE__)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,369 @@
1
+ # encoding: UTF-8
2
+
3
+ module Keynote
4
+ # HTML markup in Ruby.
5
+ #
6
+ # To invoke Rumble, call the `html` method in a presenter.
7
+ #
8
+ # ## 1. Syntax
9
+ #
10
+ # There are four basic forms:
11
+ #
12
+ # ```ruby
13
+ # tagname(content)
14
+ #
15
+ # tagname(content, attributes)
16
+ #
17
+ # tagname do
18
+ # content
19
+ # end
20
+ #
21
+ # tagname(attributes) do
22
+ # content
23
+ # end
24
+ # ```
25
+ #
26
+ # Example:
27
+ #
28
+ # ``` ruby
29
+ # html do
30
+ # div :id => :content do
31
+ # h1 'Hello World', :class => :main
32
+ # end
33
+ # end
34
+ # ```
35
+ #
36
+ # ``` html
37
+ # <div id="content">
38
+ # <h1 class="main">Hello World</h1>
39
+ # </div>
40
+ # ```
41
+ #
42
+ # ## 2. Element classes and IDs
43
+ #
44
+ # You can easily add classes and IDs by hooking methods onto the container:
45
+ #
46
+ # ``` ruby
47
+ # div.content! do
48
+ # h1.main 'Hello World'
49
+ # end
50
+ # ```
51
+ #
52
+ # You can mix and match as you'd like (`div.klass.klass1.id!`), but you can
53
+ # only provide content and attributes on the *last* call:
54
+ #
55
+ # ``` ruby
56
+ # # This is not valid:
57
+ # form(:action => :post).world do
58
+ # input
59
+ # end
60
+ #
61
+ # # But this is:
62
+ # form.world(:action => :post) do
63
+ # input
64
+ # end
65
+ # ```
66
+ #
67
+ # ## 3. Text
68
+ #
69
+ # Sometimes you need to insert plain text:
70
+ #
71
+ # ```ruby
72
+ # p.author do
73
+ # text 'Written by '
74
+ # a 'Bluebie', :href => 'http://creativepony.com/'
75
+ # br
76
+ # text link_to 'Home', '/'
77
+ # end
78
+ # ```
79
+ #
80
+ # ``` html
81
+ # <p class="author">
82
+ # Written by
83
+ # <a href="http://creativepony.com/">Bluebie</a>
84
+ # <br>
85
+ # <a href="/">Home</a>
86
+ # </p>
87
+ # ```
88
+ #
89
+ # You can also insert literal text by returning it from a block (or passing
90
+ # it as a parameter to the non-block form of a tag method):
91
+ #
92
+ # ``` ruby
93
+ # p.author do
94
+ # link_to 'Home', '/'
95
+ # end
96
+ # ```
97
+ #
98
+ # ``` html
99
+ # <p class="author">
100
+ # <a href="/">Home</a>
101
+ # </p>
102
+ # ```
103
+ #
104
+ # Be aware that Rumble ignores the string in a block if there's other tags
105
+ # there:
106
+ #
107
+ # ``` ruby
108
+ # div.comment do
109
+ # div.author "BitPuffin"
110
+ # "<p>Silence!</p>"
111
+ # end
112
+ # ```
113
+ #
114
+ # ``` html
115
+ # <div class="comment">
116
+ # <div class="author">BitPuffin</div>
117
+ # </div>
118
+ # ```
119
+ #
120
+ # ## 4. Escaping
121
+ #
122
+ # The version of Rumble that's embedded in Keynote follows normal Rails
123
+ # escaping rules. When text enters Rumble (by returning it from a block,
124
+ # passing it as a parameter to a tag method, or using the `text` method),
125
+ # it's escaped if and only if `html_safe?` returns false. That means that
126
+ # Rails helpers generally don't need special treatment, but strings need to
127
+ # have `html_safe` called on them to avoid escaping.
128
+ #
129
+ # ## 5. In practice
130
+ #
131
+ # ``` ruby
132
+ # class ArticlePresenter < Keynote::Presenter
133
+ # presents :article
134
+ #
135
+ # def published_at
136
+ # html do
137
+ # div.published_at do
138
+ # span.date publication_date
139
+ # span.time publication_time
140
+ # end
141
+ # end
142
+ # end
143
+ #
144
+ # def publication_date
145
+ # article.published_at.strftime("%A, %B %e").squeeze(" ")
146
+ # end
147
+ #
148
+ # def publication_time
149
+ # article.published_at.strftime("%l:%M%p").delete(" ")
150
+ # end
151
+ # end
152
+ # ```
153
+ #
154
+ # @author Rumble is (c) 2011 Magnus Holm (https://github.com/judofyr).
155
+ # @author Documentation mostly borrowed from Mab, (c) 2012 Magnus Holm.
156
+ # @see https://github.com/judofyr/rumble
157
+ # @see https://github.com/camping/mab
158
+ module Rumble
159
+ # A class for exceptions raised by Rumble.
160
+ class Error < StandardError
161
+ end
162
+
163
+ # A basic set of commonly-used HTML tags. These are included as methods
164
+ # on all presenters by default.
165
+ BASIC = %w[a b br button del div em form h1 h2 h3 h4 h5 h6 hr i img input
166
+ label li link ol optgroup option p pre script select span strong sub sup
167
+ table tbody td textarea tfoot th thead time tr ul]
168
+
169
+ # A more complete set of HTML5 tags. You can use these by calling
170
+ # `Keynote::Rumble.use_html5_tags(self)` in a presenter's class body.
171
+ COMPLETE = %w[a abbr acronym address applet area article aside audio b base
172
+ basefont bdo big blockquote body br button canvas caption center cite
173
+ code col colgroup command datalist dd del details dfn dir div dl dt em
174
+ embed fieldset figcaption figure font footer form frame frameset h1
175
+ h6 head header hgroup hr i iframe img input ins keygen kbd label legend
176
+ li link map mark menu meta meter nav noframes noscript object ol optgroup
177
+ option output p param pre progress q rp rt ruby s samp script section
178
+ select small source span strike strong style sub summary sup table tbody
179
+ td textarea tfoot th thead time title tr tt u ul var video wbr xmp]
180
+
181
+ # @private
182
+ SELFCLOSING = %w[base meta link hr br param img area input col frame]
183
+
184
+ # @private
185
+ def self.included(base)
186
+ define_tags(base, BASIC)
187
+ end
188
+
189
+ # @private
190
+ def self.define_tags(base, tags)
191
+ tags.each do |tag|
192
+ sc = SELFCLOSING.include?(tag).inspect
193
+
194
+ base.class_eval <<-RUBY
195
+ def #{tag}(*args, &blk) # def a(*args, &blk)
196
+ rumble_tag :#{tag}, #{sc}, *args, &blk # rumble_tag :a, false, *args, &blk
197
+ end # end
198
+ RUBY
199
+ end
200
+ end
201
+
202
+ # We need our own copy of this, the normal Rails html_escape helper, so
203
+ # that we can access it from inside Tag objects.
204
+ # @private
205
+ def self.html_escape(s)
206
+ s = s.to_s
207
+ if s.html_safe?
208
+ s
209
+ else
210
+ s.gsub(/[&"'><]/, ERB::Util::HTML_ESCAPE).html_safe
211
+ end
212
+ end
213
+
214
+ # @private
215
+ class Context < Array
216
+ def to_s
217
+ join.html_safe
218
+ end
219
+ end
220
+
221
+ # @private
222
+ class Tag
223
+ def initialize(context, instance, name, sc)
224
+ @context = context
225
+ @instance = instance
226
+ @name = name
227
+ @sc = sc
228
+ end
229
+
230
+ def attributes
231
+ @attributes ||= {}
232
+ end
233
+
234
+ def merge_attributes(attrs)
235
+ if defined?(@attributes)
236
+ @attributes.merge!(attrs)
237
+ else
238
+ @attributes = attrs
239
+ end
240
+ end
241
+
242
+ def method_missing(name, content = nil, attrs = nil, &blk)
243
+ name = name.to_s
244
+
245
+ if name[-1] == ?!
246
+ attributes[:id] = name[0..-2]
247
+ else
248
+ if attributes.has_key?(:class)
249
+ attributes[:class] += " #{name}"
250
+ else
251
+ attributes[:class] = name
252
+ end
253
+ end
254
+
255
+ insert(content, attrs, &blk)
256
+ end
257
+
258
+ def insert(content = nil, attrs = nil, &blk)
259
+ raise Error, "This tag is already closed" if @done
260
+
261
+ if content.is_a?(Hash)
262
+ attrs = content
263
+ content = nil
264
+ end
265
+
266
+ merge_attributes(attrs) if attrs
267
+
268
+ if block_given?
269
+ raise Error, "`#{@name}` is not allowed to have content" if @sc
270
+ @done = :block
271
+ before = @context.size
272
+ res = yield
273
+ @content = Rumble.html_escape(res) if @context.size == before
274
+ @context << "</#{@name}>"
275
+ elsif content
276
+ raise Error, "`#{@name}` is not allowed to have content" if @sc
277
+ @done = true
278
+ @content = Rumble.html_escape(content)
279
+ elsif attrs
280
+ @done = true
281
+ end
282
+
283
+ self
284
+ rescue
285
+ @instance.rumble_cleanup
286
+ raise $!
287
+ end
288
+
289
+ def to_ary; nil end
290
+ def to_str; to_s end
291
+
292
+ def html_safe?
293
+ true
294
+ end
295
+
296
+ def to_s
297
+ if @instance.rumble_context.eql?(@context)
298
+ @instance.rumble_cleanup
299
+ @context.to_s
300
+ else
301
+ @result ||= begin
302
+ res = "<#{@name}#{attrs_to_s}>"
303
+ res << @content if @content
304
+ res << "</#{@name}>" if !@sc && @done != :block
305
+ res.html_safe
306
+ end
307
+ end
308
+ end
309
+
310
+ def inspect; to_s.inspect end
311
+
312
+ def attrs_to_s
313
+ attributes.inject("") do |res, (name, value)|
314
+ if value
315
+ value = (value == true) ? name : Rumble.html_escape(value)
316
+ res << " #{name}=\"#{value}\""
317
+ end
318
+ res
319
+ end
320
+ end
321
+ end
322
+
323
+ # Generate HTML using Rumble tag methods. If tag methods are called
324
+ # outside an `html` block, they'll raise an exception.
325
+ def html
326
+ ctx = @rumble_context
327
+ @rumble_context = Context.new
328
+ yield
329
+ rumble_cleanup(ctx).to_s
330
+ end
331
+
332
+ # Generate a text node. This is helpful in situations where an element
333
+ # contains both text and markup.
334
+ def text(str = nil, &blk)
335
+ str = Rumble.html_escape(str || blk.call)
336
+
337
+ if @rumble_context
338
+ @rumble_context << str
339
+ else
340
+ str
341
+ end
342
+ end
343
+
344
+ # @private
345
+ def rumble_context
346
+ @rumble_context
347
+ end
348
+
349
+ # @private
350
+ def rumble_cleanup(value = nil)
351
+ @rumble_context
352
+ ensure
353
+ @rumble_context = value
354
+ end
355
+
356
+ private
357
+
358
+ def rumble_tag(name, sc, content = nil, attrs = nil, &blk)
359
+ if !@rumble_context
360
+ raise Rumble::Error, "Must enclose tags in `rumble { ... }` block"
361
+ end
362
+
363
+ context = @rumble_context
364
+ tag = Tag.new(context, self, name, sc)
365
+ context << tag
366
+ tag.insert(content, attrs, &blk)
367
+ end
368
+ end
369
+ end
@@ -0,0 +1,2 @@
1
+ # Add the presenters folder to the default test run
2
+ MINITEST_TASKS << 'presenters'
@@ -0,0 +1,22 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'minitest/rails/action_view'
4
+
5
+ module Keynote
6
+ module MiniTest
7
+ class TestCase < ::MiniTest::Rails::ActionView::TestCase
8
+ # describe SomePresenter do
9
+ register_spec_type(self) do |desc|
10
+ desc < Keynote::Presenter if desc.is_a?(Class)
11
+ end
12
+
13
+ # describe "SomePresenter" do
14
+ register_spec_type(/presenter( ?test)?\z/i, self)
15
+
16
+ # Don't try to include any particular helper, since we're not testing one
17
+ def self.include_helper_modules!
18
+ include _helpers
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: UTF-8
2
+
3
+ module Keynote
4
+ module ExampleGroup
5
+ def self.included(base)
6
+ base.send :include, RSpec::Rails::ViewExampleGroup
7
+ base.metadata[:type] = :presenter
8
+ end
9
+ end
10
+ end
11
+
12
+ RSpec.configure do |config|
13
+ config.include Keynote::ExampleGroup,
14
+ :type => :presenter,
15
+ :example_group => {:file_path => %r/spec.presenters/}
16
+ end