keynote 0.2.0pre1 → 0.2.0pre2
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.
- checksums.yaml +15 -0
- data/.editorconfig +14 -0
- data/.gitignore +0 -1
- data/.travis.yml +16 -0
- data/Appraisals +6 -9
- data/CHANGELOG.md +10 -3
- data/README.md +2 -1
- data/Rakefile +4 -0
- data/gemfiles/rails30.gemfile +7 -0
- data/gemfiles/rails30.gemfile.lock +114 -0
- data/gemfiles/rails31.gemfile +7 -0
- data/gemfiles/rails31.gemfile.lock +124 -0
- data/gemfiles/rails32.gemfile +7 -0
- data/gemfiles/rails32.gemfile.lock +123 -0
- data/gemfiles/rails4.gemfile +9 -0
- data/gemfiles/rails4.gemfile.lock +120 -0
- data/keynote.gemspec +4 -1
- data/lib/keynote.rb +1 -0
- data/lib/keynote/inline.rb +207 -0
- data/lib/keynote/presenter.rb +31 -0
- data/lib/keynote/version.rb +1 -1
- data/spec/benchmarks.rb +72 -0
- data/spec/helper.rb +2 -11
- data/spec/inline_spec.rb +147 -0
- data/spec/presenter_spec.rb +52 -3
- metadata +61 -34
@@ -0,0 +1,120 @@
|
|
1
|
+
PATH
|
2
|
+
remote: /Users/rfitz/src/keynote
|
3
|
+
specs:
|
4
|
+
keynote (0.2.0pre1)
|
5
|
+
rails (>= 3.0.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
actionmailer (4.0.0.rc1)
|
11
|
+
actionpack (= 4.0.0.rc1)
|
12
|
+
mail (~> 2.5.3)
|
13
|
+
actionpack (4.0.0.rc1)
|
14
|
+
activesupport (= 4.0.0.rc1)
|
15
|
+
builder (~> 3.1.0)
|
16
|
+
erubis (~> 2.7.0)
|
17
|
+
rack (~> 1.5.2)
|
18
|
+
rack-test (~> 0.6.2)
|
19
|
+
activemodel (4.0.0.rc1)
|
20
|
+
activesupport (= 4.0.0.rc1)
|
21
|
+
builder (~> 3.1.0)
|
22
|
+
activerecord (4.0.0.rc1)
|
23
|
+
activemodel (= 4.0.0.rc1)
|
24
|
+
activerecord-deprecated_finders (~> 1.0.2)
|
25
|
+
activesupport (= 4.0.0.rc1)
|
26
|
+
arel (~> 4.0.0)
|
27
|
+
activerecord-deprecated_finders (1.0.2)
|
28
|
+
activesupport (4.0.0.rc1)
|
29
|
+
i18n (~> 0.6, >= 0.6.4)
|
30
|
+
minitest (~> 4.2)
|
31
|
+
multi_json (~> 1.3)
|
32
|
+
thread_safe (~> 0.1)
|
33
|
+
tzinfo (~> 0.3.37)
|
34
|
+
appraisal (0.5.2)
|
35
|
+
bundler
|
36
|
+
rake
|
37
|
+
arel (4.0.0)
|
38
|
+
atomic (1.1.8)
|
39
|
+
builder (3.1.4)
|
40
|
+
coderay (1.0.9)
|
41
|
+
erubis (2.7.0)
|
42
|
+
haml (4.0.2)
|
43
|
+
tilt
|
44
|
+
hike (1.2.2)
|
45
|
+
i18n (0.6.4)
|
46
|
+
journey (1.0.4)
|
47
|
+
mail (2.5.3)
|
48
|
+
i18n (>= 0.4.0)
|
49
|
+
mime-types (~> 1.16)
|
50
|
+
treetop (~> 1.4.8)
|
51
|
+
metaclass (0.0.1)
|
52
|
+
method_source (0.8.1)
|
53
|
+
mime-types (1.23)
|
54
|
+
minitest (4.7.4)
|
55
|
+
mocha (0.13.3)
|
56
|
+
metaclass (~> 0.0.1)
|
57
|
+
multi_json (1.7.2)
|
58
|
+
polyglot (0.3.3)
|
59
|
+
pry (0.9.12.1)
|
60
|
+
coderay (~> 1.0.5)
|
61
|
+
method_source (~> 0.8)
|
62
|
+
slop (~> 3.4)
|
63
|
+
rack (1.5.2)
|
64
|
+
rack-test (0.6.2)
|
65
|
+
rack (>= 1.0)
|
66
|
+
rails (4.0.0.rc1)
|
67
|
+
actionmailer (= 4.0.0.rc1)
|
68
|
+
actionpack (= 4.0.0.rc1)
|
69
|
+
activerecord (= 4.0.0.rc1)
|
70
|
+
activesupport (= 4.0.0.rc1)
|
71
|
+
bundler (>= 1.3.0, < 2.0)
|
72
|
+
railties (= 4.0.0.rc1)
|
73
|
+
sprockets-rails (~> 2.0.0.rc4)
|
74
|
+
railties (4.0.0.rc1)
|
75
|
+
actionpack (= 4.0.0.rc1)
|
76
|
+
activesupport (= 4.0.0.rc1)
|
77
|
+
rake (>= 0.8.7)
|
78
|
+
thor (>= 0.18.1, < 2.0)
|
79
|
+
rake (10.0.4)
|
80
|
+
redcarpet (2.2.2)
|
81
|
+
slim (1.0.1)
|
82
|
+
temple (~> 0.3.0)
|
83
|
+
tilt (~> 1.2)
|
84
|
+
slop (3.4.4)
|
85
|
+
sprockets (2.9.3)
|
86
|
+
hike (~> 1.2)
|
87
|
+
multi_json (~> 1.0)
|
88
|
+
rack (~> 1.0)
|
89
|
+
tilt (~> 1.1, != 1.3.0)
|
90
|
+
sprockets-rails (2.0.0.rc4)
|
91
|
+
actionpack (>= 3.0)
|
92
|
+
activesupport (>= 3.0)
|
93
|
+
sprockets (~> 2.8)
|
94
|
+
temple (0.3.5)
|
95
|
+
thor (0.18.1)
|
96
|
+
thread_safe (0.1.0)
|
97
|
+
atomic
|
98
|
+
tilt (1.4.0)
|
99
|
+
treetop (1.4.12)
|
100
|
+
polyglot
|
101
|
+
polyglot (>= 0.3.1)
|
102
|
+
tzinfo (0.3.37)
|
103
|
+
yard (0.8.6.1)
|
104
|
+
|
105
|
+
PLATFORMS
|
106
|
+
ruby
|
107
|
+
|
108
|
+
DEPENDENCIES
|
109
|
+
activerecord-deprecated_finders (~> 1.0.2)
|
110
|
+
appraisal
|
111
|
+
haml
|
112
|
+
journey (~> 1.0.4)
|
113
|
+
keynote!
|
114
|
+
minitest
|
115
|
+
mocha (~> 0.13.3)
|
116
|
+
pry
|
117
|
+
rails (= 4.0.0rc1)
|
118
|
+
redcarpet
|
119
|
+
slim
|
120
|
+
yard
|
data/keynote.gemspec
CHANGED
@@ -23,8 +23,11 @@ Gem::Specification.new do |gem|
|
|
23
23
|
|
24
24
|
gem.add_development_dependency 'appraisal'
|
25
25
|
gem.add_development_dependency 'minitest'
|
26
|
-
gem.add_development_dependency 'mocha'
|
26
|
+
gem.add_development_dependency 'mocha', '~> 0.13.3'
|
27
27
|
gem.add_development_dependency 'pry'
|
28
28
|
gem.add_development_dependency 'redcarpet'
|
29
29
|
gem.add_development_dependency 'yard'
|
30
|
+
|
31
|
+
gem.add_development_dependency 'slim'
|
32
|
+
gem.add_development_dependency 'haml'
|
30
33
|
end
|
data/lib/keynote.rb
CHANGED
@@ -0,0 +1,207 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require "thread"
|
4
|
+
|
5
|
+
module Keynote
|
6
|
+
# The `Inline` mixin lets you write inline templates as comments inside the
|
7
|
+
# body of a presenter method. You can use any template language supported by
|
8
|
+
# Rails.
|
9
|
+
#
|
10
|
+
# ## The `inline` method
|
11
|
+
#
|
12
|
+
# First, you have to declare what template languages you want to use by
|
13
|
+
# calling the {Keynote::Inline#inline} method on a presenter class:
|
14
|
+
#
|
15
|
+
# class MyPresenter < Keynote::Presenter
|
16
|
+
# presents :user, :account
|
17
|
+
# inline :haml
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# This defines a `#haml` instance method on the `MyPresenter` class.
|
21
|
+
#
|
22
|
+
# If you want to make inline templates available to all of your presenters,
|
23
|
+
# you can add an initializer like this to your application:
|
24
|
+
#
|
25
|
+
# class Keynote::Presenter
|
26
|
+
# inline :erb, :haml, :slim
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# This will add `#erb`, `#haml`, and `#slim` instance methods to all of your
|
30
|
+
# presenters.
|
31
|
+
#
|
32
|
+
# ## Basic usage
|
33
|
+
#
|
34
|
+
# After defining one or more instance methods by calling `inline`, you can
|
35
|
+
# generate HTML by calling one of those methods and immediately following it
|
36
|
+
# with a block of comments containing your template:
|
37
|
+
#
|
38
|
+
# def link
|
39
|
+
# erb
|
40
|
+
# # <%= link_to user_url(current_user) do %>
|
41
|
+
# # <%= image_tag("image1.jpg") %>
|
42
|
+
# # <%= image_tag("image2.jpg") %>
|
43
|
+
# # <% end %>
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# Calling this method renders the ERB template, including passing the calls
|
47
|
+
# to `link_to`, `user_url`, `current_user`, and `image_tag` back to the
|
48
|
+
# presenter object (and then to the view).
|
49
|
+
#
|
50
|
+
# ## Passing variables
|
51
|
+
#
|
52
|
+
# There are a couple of different ways to pass local variables into an inline
|
53
|
+
# template. The easiest is to pass the `binding` object into the template
|
54
|
+
# method, giving access to all local variables:
|
55
|
+
#
|
56
|
+
# def local_binding
|
57
|
+
# x = 1
|
58
|
+
# y = 2
|
59
|
+
#
|
60
|
+
# erb binding
|
61
|
+
# # <%= x + y %>
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# You can also pass a hash of variable names and values instead:
|
65
|
+
#
|
66
|
+
# def local_binding
|
67
|
+
# erb x: 1, y: 2
|
68
|
+
# # <%= x + y %>
|
69
|
+
# end
|
70
|
+
module Inline
|
71
|
+
# For each template format given as a parameter, add an instance method
|
72
|
+
# that can be called to render an inline template in that format. Any
|
73
|
+
# file extension supported by Rails is a valid parameter.
|
74
|
+
# @example
|
75
|
+
# class UserPresenter < Keynote::Presenter
|
76
|
+
# presents :user
|
77
|
+
# inline :haml
|
78
|
+
#
|
79
|
+
# def header
|
80
|
+
# full_name = "#{user.first_name} #{user.last_name}"
|
81
|
+
#
|
82
|
+
# haml binding
|
83
|
+
# # div#header
|
84
|
+
# # h1= full_name
|
85
|
+
# # h3= user.most_recent_status
|
86
|
+
# end
|
87
|
+
# end
|
88
|
+
def inline(*formats)
|
89
|
+
require "action_view"
|
90
|
+
require "action_view/context"
|
91
|
+
|
92
|
+
Array(formats).each do |format|
|
93
|
+
define_method format do |locals = {}|
|
94
|
+
Renderer.new(self, locals, caller(1)[0], format).render
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# @private
|
100
|
+
class Renderer
|
101
|
+
def initialize(presenter, locals, caller_line, format)
|
102
|
+
@presenter = presenter
|
103
|
+
@locals = extract_locals(locals)
|
104
|
+
@template = Cache.fetch(*parse_caller(caller_line), format, @locals)
|
105
|
+
end
|
106
|
+
|
107
|
+
def render
|
108
|
+
@template.render(@presenter, @locals)
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def extract_locals(locals)
|
114
|
+
return locals unless locals.is_a?(Binding)
|
115
|
+
|
116
|
+
Hash[locals.eval("local_variables").map do |local|
|
117
|
+
[local, locals.eval(local.to_s)]
|
118
|
+
end]
|
119
|
+
end
|
120
|
+
|
121
|
+
def parse_caller(caller_line)
|
122
|
+
file, rest = caller_line.split ":", 2
|
123
|
+
line, _ = rest.split " ", 2
|
124
|
+
|
125
|
+
[file.strip, line.to_i]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# @private
|
130
|
+
class Cache
|
131
|
+
COMMENTED_LINE = /^\s*#(.*)$/
|
132
|
+
|
133
|
+
def self.fetch(source_file, line, format, locals)
|
134
|
+
instance = (Thread.current[:_keynote_template_cache] ||= Cache.new)
|
135
|
+
instance.fetch(source_file, line, format, locals)
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.reset
|
139
|
+
Thread.current[:_keynote_template_cache] = nil
|
140
|
+
end
|
141
|
+
|
142
|
+
def initialize
|
143
|
+
@cache = {}
|
144
|
+
end
|
145
|
+
|
146
|
+
def fetch(source_file, line, format, locals)
|
147
|
+
local_names = locals.keys.sort
|
148
|
+
cache_key = ["#{source_file}:#{line}", *local_names].freeze
|
149
|
+
new_mtime = File.mtime(source_file).to_f
|
150
|
+
|
151
|
+
template, mtime = @cache[cache_key]
|
152
|
+
|
153
|
+
if new_mtime != mtime
|
154
|
+
source = read_template(source_file, line)
|
155
|
+
|
156
|
+
template = ActionView::Template.new(source, cache_key[0],
|
157
|
+
handler_for_format(format), locals: local_names)
|
158
|
+
|
159
|
+
@cache[cache_key] = [template, new_mtime]
|
160
|
+
end
|
161
|
+
|
162
|
+
template
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
|
167
|
+
def read_template(source_file, line)
|
168
|
+
result = ""
|
169
|
+
|
170
|
+
File.foreach(source_file).drop(line).each do |line|
|
171
|
+
if line =~ COMMENTED_LINE
|
172
|
+
result << $1
|
173
|
+
else
|
174
|
+
break
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
unindent result
|
179
|
+
end
|
180
|
+
|
181
|
+
# Borrowed from Pry, which borrowed it from Python.
|
182
|
+
def unindent(text, left_padding = 0)
|
183
|
+
margin = text.scan(/^[ \t]*(?=[^ \t\n])/).inject do |current_margin, next_indent|
|
184
|
+
if next_indent.start_with?(current_margin)
|
185
|
+
current_margin
|
186
|
+
elsif current_margin.start_with?(next_indent)
|
187
|
+
next_indent
|
188
|
+
else
|
189
|
+
""
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
text.gsub(/^#{margin}/, ' ' * left_padding)
|
194
|
+
end
|
195
|
+
|
196
|
+
if Rails::VERSION::MAJOR == 3 && Rails::VERSION::MINOR == 0
|
197
|
+
def handler_for_format(format)
|
198
|
+
ActionView::Template.handler_class_for_extension(format.to_s)
|
199
|
+
end
|
200
|
+
else
|
201
|
+
def handler_for_format(format)
|
202
|
+
ActionView::Template.handler_for_extension(format.to_s)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
data/lib/keynote/presenter.rb
CHANGED
@@ -9,6 +9,8 @@ module Keynote
|
|
9
9
|
include Keynote::Rumble
|
10
10
|
|
11
11
|
class << self
|
12
|
+
attr_writer :object_names
|
13
|
+
|
12
14
|
# Define the names and number of the objects presented by this class.
|
13
15
|
# This replaces the default one-parameter constructor with one that takes
|
14
16
|
# an extra parameter for each presented object.
|
@@ -29,6 +31,8 @@ module Keynote
|
|
29
31
|
# presenter.author # == @some_user
|
30
32
|
#
|
31
33
|
def presents(*objects)
|
34
|
+
self.object_names = objects.dup
|
35
|
+
|
32
36
|
objects.unshift :view
|
33
37
|
attr_reader *objects
|
34
38
|
|
@@ -47,6 +51,14 @@ module Keynote
|
|
47
51
|
def use_html_5_tags
|
48
52
|
Rumble.use_html_5_tags(self)
|
49
53
|
end
|
54
|
+
|
55
|
+
# List the object names this presenter wraps. The default is an empty
|
56
|
+
# array; calling `presents :foo, :bar` in the presenter's class body will
|
57
|
+
# cause `object_names` to return `[:foo, :bar]`.
|
58
|
+
# @return [Array<Symbol>]
|
59
|
+
def object_names
|
60
|
+
@object_names ||= []
|
61
|
+
end
|
50
62
|
end
|
51
63
|
|
52
64
|
# @private (used by Keynote::Cache to keep the view context up-to-date)
|
@@ -67,6 +79,18 @@ module Keynote
|
|
67
79
|
end
|
68
80
|
alias k present
|
69
81
|
|
82
|
+
# @private
|
83
|
+
def inspect
|
84
|
+
objects = self.class.object_names
|
85
|
+
render = proc { |name| "#{name}: #{send(name).inspect}" }
|
86
|
+
|
87
|
+
if objects.any?
|
88
|
+
"#<#{self.class} #{objects.map(&render).join(", ")}>"
|
89
|
+
else
|
90
|
+
"#<#{self.class}>"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
70
94
|
# @private
|
71
95
|
def respond_to_missing?(method_name, include_private = true)
|
72
96
|
@view.respond_to?(method_name, true)
|
@@ -89,6 +113,13 @@ module Keynote
|
|
89
113
|
end
|
90
114
|
end
|
91
115
|
|
116
|
+
# @private
|
117
|
+
# We have to make a logger method available so that ActionView::Template
|
118
|
+
# can safely treat a presenter as a view object.
|
119
|
+
def logger
|
120
|
+
Rails.logger
|
121
|
+
end
|
122
|
+
|
92
123
|
private
|
93
124
|
|
94
125
|
# We have to explicitly proxy `#capture` because ActiveSupport creates a
|
data/lib/keynote/version.rb
CHANGED
data/spec/benchmarks.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require "rails"
|
4
|
+
require "action_controller/railtie"
|
5
|
+
require "action_mailer/railtie"
|
6
|
+
require "rails/test_unit/railtie"
|
7
|
+
require "keynote"
|
8
|
+
require "benchmark"
|
9
|
+
|
10
|
+
class MyPresenter < Keynote::Presenter
|
11
|
+
inline :erb
|
12
|
+
|
13
|
+
def my_string
|
14
|
+
"a" + "b" + "c"
|
15
|
+
end
|
16
|
+
|
17
|
+
def rumble
|
18
|
+
a_local = 1000
|
19
|
+
|
20
|
+
build_html do
|
21
|
+
div.foobar.baz! do
|
22
|
+
p { my_string }
|
23
|
+
p { a_local }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def erb_hash
|
29
|
+
a_local = 1000
|
30
|
+
|
31
|
+
erb a_local: a_local
|
32
|
+
# <div class="foobar" id="baz">
|
33
|
+
# <p><%= my_string %></p>
|
34
|
+
# <p><%= a_local %></p>
|
35
|
+
# </div>
|
36
|
+
end
|
37
|
+
|
38
|
+
def erb_binding
|
39
|
+
a_local = 1000
|
40
|
+
|
41
|
+
erb binding
|
42
|
+
# <div class="foobar" id="baz">
|
43
|
+
# <p><%= my_string %></p>
|
44
|
+
# <p><%= a_local %></p>
|
45
|
+
# </div>
|
46
|
+
end
|
47
|
+
|
48
|
+
def raw_erb_template
|
49
|
+
source = %{
|
50
|
+
<div class="foobar" id="baz">
|
51
|
+
<p><%= my_string %></p>
|
52
|
+
<p><%= a_local %></p>
|
53
|
+
</div>
|
54
|
+
}
|
55
|
+
template = ActionView::Template.new(
|
56
|
+
source, "raw_erb_template",
|
57
|
+
ActionView::Template.handler_for_extension(:erb),
|
58
|
+
locals: [:a_local]
|
59
|
+
)
|
60
|
+
TESTS.times { template.render(self, a_local: 1000) }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
TESTS = 1_000
|
65
|
+
presenter = MyPresenter.new(:view)
|
66
|
+
|
67
|
+
Benchmark.bmbm do |results|
|
68
|
+
results.report("rumble") { TESTS.times { presenter.rumble } }
|
69
|
+
results.report("erb_hash") { TESTS.times { presenter.erb_hash } }
|
70
|
+
results.report("erb_binding") { TESTS.times { presenter.erb_binding } }
|
71
|
+
results.report("raw_erb_template") { presenter.raw_erb_template }
|
72
|
+
end
|