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.
- data/.gitignore +19 -0
- data/.yardopts +4 -0
- data/Appraisals +20 -0
- data/Gemfile +4 -0
- data/LICENSE +27 -0
- data/README.md +8 -0
- data/Rakefile +17 -0
- data/keynote.gemspec +26 -0
- data/lib/generators/presenter_generator.rb +70 -0
- data/lib/generators/templates/keynote_mini_test_spec.rb +11 -0
- data/lib/generators/templates/keynote_mini_test_unit.rb +11 -0
- data/lib/generators/templates/keynote_presenter.rb +7 -0
- data/lib/generators/templates/keynote_rspec.rb +5 -0
- data/lib/generators/templates/keynote_test_unit.rb +11 -0
- data/lib/keynote.rb +65 -0
- data/lib/keynote/cache.rb +53 -0
- data/lib/keynote/controller.rb +14 -0
- data/lib/keynote/helper.rb +14 -0
- data/lib/keynote/presenter.rb +90 -0
- data/lib/keynote/railtie.rb +40 -0
- data/lib/keynote/rumble.rb +369 -0
- data/lib/keynote/testing/minitest_rails.rake +2 -0
- data/lib/keynote/testing/minitest_rails.rb +22 -0
- data/lib/keynote/testing/rspec.rb +16 -0
- data/lib/keynote/testing/test_unit.rb +6 -0
- data/lib/keynote/version.rb +6 -0
- data/spec/generator_spec.rb +106 -0
- data/spec/helper.rb +84 -0
- data/spec/keynote_spec.rb +98 -0
- data/spec/presenter_spec.rb +116 -0
- data/spec/railtie_spec.rb +32 -0
- data/spec/rumble_spec.rb +201 -0
- data/spec/test_case_spec.rb +12 -0
- metadata +204 -0
@@ -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,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
|