liquor 0.1.1 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -9
- data/Gemfile +7 -0
- data/Guardfile +11 -0
- data/MIT-LICENSE +6 -2
- data/README.md +4 -122
- data/Rakefile +20 -23
- data/doc/language-spec.html +768 -0
- data/doc/language-spec.md +698 -0
- data/lib/liquor.rb +39 -68
- data/lib/liquor/ast_tools.rb +28 -0
- data/lib/liquor/compiler.rb +110 -0
- data/lib/liquor/context.rb +76 -254
- data/lib/liquor/diagnostics.rb +151 -0
- data/lib/liquor/drop/drop.rb +168 -0
- data/lib/liquor/drop/drop_delegation.rb +24 -0
- data/lib/liquor/drop/drop_scope.rb +118 -0
- data/lib/liquor/drop/dropable.rb +17 -0
- data/lib/liquor/emitter.rb +313 -0
- data/lib/liquor/extensions/kaminari.rb +14 -0
- data/lib/liquor/extensions/pagination.rb +235 -0
- data/lib/liquor/extensions/rails.rb +97 -0
- data/lib/liquor/extensions/thinking_sphinx.rb +14 -0
- data/lib/liquor/extensions/tire.rb +30 -0
- data/lib/liquor/external.rb +79 -0
- data/lib/liquor/function.rb +94 -0
- data/lib/liquor/grammar/lexer.rb +1223 -0
- data/lib/liquor/grammar/lexer.rl +297 -0
- data/lib/liquor/grammar/parser.racc +288 -0
- data/lib/liquor/grammar/parser.rb +885 -0
- data/lib/liquor/library.rb +41 -0
- data/lib/liquor/manager.rb +146 -0
- data/lib/liquor/runtime.rb +167 -0
- data/lib/liquor/stdlib/builtin_functions.rb +315 -0
- data/lib/liquor/stdlib/builtin_tags.rb +228 -0
- data/lib/liquor/stdlib/html_truncater.rb +162 -0
- data/lib/liquor/stdlib/partial_tags.rb +76 -0
- data/lib/liquor/tag.rb +83 -14
- data/lib/liquor/version.rb +1 -1
- data/liquor.gemspec +29 -6
- data/spec/builtins_spec.rb +264 -0
- data/spec/compiler_spec.rb +136 -0
- data/spec/context_spec.rb +49 -0
- data/spec/drop_delegation_spec.rb +21 -0
- data/spec/drop_spec.rb +222 -0
- data/spec/errors_spec.rb +40 -0
- data/spec/external_spec.rb +207 -0
- data/spec/function_spec.rb +80 -0
- data/spec/lexer_spec.rb +173 -0
- data/spec/library_spec.rb +18 -0
- data/spec/manager_spec.rb +84 -0
- data/spec/parser_spec.rb +381 -0
- data/spec/partials_spec.rb +74 -0
- data/spec/runtime_spec.rb +97 -0
- data/spec/spec_helper.rb +94 -0
- data/spec/tag_spec.rb +7 -0
- metadata +216 -173
- data/AUTHORS +0 -2
- data/CHANGELOG +0 -48
- data/Gemfile.lock +0 -91
- data/History.txt +0 -44
- data/LICENSE +0 -23
- data/example/server/example_servlet.rb +0 -37
- data/example/server/liquid_servlet.rb +0 -28
- data/example/server/liquor_servlet.rb +0 -28
- data/example/server/server.rb +0 -12
- data/example/server/templates/index.liquid +0 -6
- data/example/server/templates/index.liquor +0 -6
- data/example/server/templates/products.liquid +0 -45
- data/example/server/templates/products.liquor +0 -45
- data/init.rb +0 -8
- data/lib/extras/liquid_view.rb +0 -51
- data/lib/extras/liquor_view.rb +0 -51
- data/lib/liquor/block.rb +0 -101
- data/lib/liquor/condition.rb +0 -120
- data/lib/liquor/document.rb +0 -17
- data/lib/liquor/drop.rb +0 -256
- data/lib/liquor/errors.rb +0 -11
- data/lib/liquor/extensions.rb +0 -72
- data/lib/liquor/file_system.rb +0 -62
- data/lib/liquor/htmltags.rb +0 -74
- data/lib/liquor/module_ex.rb +0 -60
- data/lib/liquor/standardfilters.rb +0 -315
- data/lib/liquor/strainer.rb +0 -58
- data/lib/liquor/tags/assign.rb +0 -33
- data/lib/liquor/tags/capture.rb +0 -35
- data/lib/liquor/tags/case.rb +0 -83
- data/lib/liquor/tags/comment.rb +0 -9
- data/lib/liquor/tags/content_for.rb +0 -54
- data/lib/liquor/tags/cycle.rb +0 -59
- data/lib/liquor/tags/for.rb +0 -136
- data/lib/liquor/tags/if.rb +0 -80
- data/lib/liquor/tags/ifchanged.rb +0 -20
- data/lib/liquor/tags/include.rb +0 -56
- data/lib/liquor/tags/unless.rb +0 -33
- data/lib/liquor/tags/yield.rb +0 -49
- data/lib/liquor/template.rb +0 -181
- data/lib/liquor/variable.rb +0 -52
- data/performance/shopify.rb +0 -92
- data/performance/shopify/comment_form.rb +0 -33
- data/performance/shopify/database.rb +0 -45
- data/performance/shopify/json_filter.rb +0 -7
- data/performance/shopify/liquid.rb +0 -18
- data/performance/shopify/liquor.rb +0 -18
- data/performance/shopify/money_filter.rb +0 -18
- data/performance/shopify/paginate.rb +0 -93
- data/performance/shopify/shop_filter.rb +0 -98
- data/performance/shopify/tag_filter.rb +0 -25
- data/performance/shopify/vision.database.yml +0 -945
- data/performance/shopify/weight_filter.rb +0 -11
- data/performance/tests/dropify/article.liquid +0 -74
- data/performance/tests/dropify/blog.liquid +0 -33
- data/performance/tests/dropify/cart.liquid +0 -66
- data/performance/tests/dropify/collection.liquid +0 -22
- data/performance/tests/dropify/index.liquid +0 -47
- data/performance/tests/dropify/page.liquid +0 -8
- data/performance/tests/dropify/product.liquid +0 -68
- data/performance/tests/dropify/theme.liquid +0 -105
- data/performance/tests/ripen/article.liquid +0 -74
- data/performance/tests/ripen/blog.liquid +0 -13
- data/performance/tests/ripen/cart.liquid +0 -54
- data/performance/tests/ripen/collection.liquid +0 -29
- data/performance/tests/ripen/index.liquid +0 -32
- data/performance/tests/ripen/page.liquid +0 -4
- data/performance/tests/ripen/product.liquid +0 -75
- data/performance/tests/ripen/theme.liquid +0 -85
- data/performance/tests/tribble/404.liquid +0 -56
- data/performance/tests/tribble/article.liquid +0 -98
- data/performance/tests/tribble/blog.liquid +0 -41
- data/performance/tests/tribble/cart.liquid +0 -134
- data/performance/tests/tribble/collection.liquid +0 -70
- data/performance/tests/tribble/index.liquid +0 -94
- data/performance/tests/tribble/page.liquid +0 -56
- data/performance/tests/tribble/product.liquid +0 -116
- data/performance/tests/tribble/search.liquid +0 -51
- data/performance/tests/tribble/theme.liquid +0 -90
- data/performance/tests/vogue/article.liquid +0 -66
- data/performance/tests/vogue/blog.liquid +0 -32
- data/performance/tests/vogue/cart.liquid +0 -58
- data/performance/tests/vogue/collection.liquid +0 -19
- data/performance/tests/vogue/index.liquid +0 -22
- data/performance/tests/vogue/page.liquid +0 -3
- data/performance/tests/vogue/product.liquid +0 -62
- data/performance/tests/vogue/theme.liquid +0 -122
- data/test/assign_test.rb +0 -11
- data/test/block_test.rb +0 -58
- data/test/capture_test.rb +0 -41
- data/test/condition_test.rb +0 -115
- data/test/content_for_test.rb +0 -15
- data/test/context_test.rb +0 -479
- data/test/drop_test.rb +0 -162
- data/test/error_handling_test.rb +0 -89
- data/test/extra/breakpoint.rb +0 -547
- data/test/extra/caller.rb +0 -80
- data/test/file_system_test.rb +0 -30
- data/test/filter_test.rb +0 -147
- data/test/helper.rb +0 -24
- data/test/html_tag_test.rb +0 -31
- data/test/if_else_test.rb +0 -139
- data/test/include_tag_test.rb +0 -129
- data/test/module_ex_test.rb +0 -89
- data/test/output_test.rb +0 -121
- data/test/parsing_quirks_test.rb +0 -54
- data/test/regexp_test.rb +0 -45
- data/test/security_test.rb +0 -41
- data/test/standard_filter_test.rb +0 -170
- data/test/standard_tag_test.rb +0 -405
- data/test/statements_test.rb +0 -137
- data/test/strainer_test.rb +0 -27
- data/test/template_test.rb +0 -82
- data/test/test_helper.rb +0 -28
- data/test/unless_else_test.rb +0 -27
- data/test/variable_test.rb +0 -173
- data/test/yield_test.rb +0 -24
@@ -0,0 +1,17 @@
|
|
1
|
+
module Liquor
|
2
|
+
module Dropable
|
3
|
+
module ClassMethods
|
4
|
+
def to_drop
|
5
|
+
DropDelegation.wrap_scope(self)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.included(klass)
|
10
|
+
klass.extend ClassMethods
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_drop
|
14
|
+
DropDelegation.wrap_element(self)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,313 @@
|
|
1
|
+
module Liquor
|
2
|
+
class Emitter
|
3
|
+
include ASTTools
|
4
|
+
|
5
|
+
attr_reader :compiler, :context
|
6
|
+
|
7
|
+
def initialize(context)
|
8
|
+
@context = context
|
9
|
+
@compiler = context.compiler
|
10
|
+
@buffer = ""
|
11
|
+
@current_capture_var = ""
|
12
|
+
end
|
13
|
+
|
14
|
+
def env
|
15
|
+
'_env'
|
16
|
+
end
|
17
|
+
|
18
|
+
def buf
|
19
|
+
"_buf#{@current_capture_var}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def storage
|
23
|
+
"_storage"
|
24
|
+
end
|
25
|
+
|
26
|
+
def ident(node)
|
27
|
+
name, = nvalue(node)
|
28
|
+
|
29
|
+
case @context.type name
|
30
|
+
when :builtin
|
31
|
+
case name
|
32
|
+
when 'null'; 'nil'
|
33
|
+
when 'true'; 'true'
|
34
|
+
when 'false'; 'false'
|
35
|
+
end
|
36
|
+
when :variable
|
37
|
+
@context.access name, nloc(node)
|
38
|
+
when :function
|
39
|
+
raise NameError.new("using function `#{name}' as a variable", nloc(node))
|
40
|
+
when :free
|
41
|
+
raise NameError.new("identifier `#{name}' is not bound", nloc(node))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
OPERATORS = {
|
46
|
+
:uminus => '-', :not => '!',
|
47
|
+
:mul => '*', :div => '/', :mod => '%',
|
48
|
+
:plus => '+', :minus => '-',
|
49
|
+
:eq => '==', :neq => '!=',
|
50
|
+
:lt => '<', :leq => '<=',
|
51
|
+
:gt => '>', :geq => '>=',
|
52
|
+
:and => '&&', :or => '||',
|
53
|
+
}
|
54
|
+
|
55
|
+
def expr(node)
|
56
|
+
case ntype(node)
|
57
|
+
when :ident
|
58
|
+
ident(node)
|
59
|
+
when :integer
|
60
|
+
integer(node)
|
61
|
+
when :string
|
62
|
+
string(node)
|
63
|
+
when :tuple
|
64
|
+
tuple(node)
|
65
|
+
when :plus
|
66
|
+
plus(node)
|
67
|
+
when :mul, :div, :mod, :minus,
|
68
|
+
:lt, :leq, :gt, :geq
|
69
|
+
integer_binop(node)
|
70
|
+
when :uminus
|
71
|
+
integer_unop(node)
|
72
|
+
when :eq, :neq
|
73
|
+
equality_binop(node)
|
74
|
+
when :and, :or
|
75
|
+
boolean_binop(node)
|
76
|
+
when :not
|
77
|
+
boolean_unop(node)
|
78
|
+
when :index
|
79
|
+
index(node)
|
80
|
+
when :external
|
81
|
+
external(node)
|
82
|
+
when :call
|
83
|
+
call(node)
|
84
|
+
else
|
85
|
+
raise "unknown node #{ntype node}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def external(node)
|
90
|
+
target, method, args = nvalue(node)
|
91
|
+
name, = nvalue(method)
|
92
|
+
|
93
|
+
if args
|
94
|
+
arg, kw = nvalue(args)
|
95
|
+
|
96
|
+
if arg.nil?
|
97
|
+
out_args = [ "nil" ]
|
98
|
+
else
|
99
|
+
out_args = [ expr(arg) ]
|
100
|
+
end
|
101
|
+
|
102
|
+
kw.each do |kwarg, kwval|
|
103
|
+
out_args << "#{kwarg.inspect} => #{expr(kwval)}"
|
104
|
+
end
|
105
|
+
else
|
106
|
+
out_args = []
|
107
|
+
end
|
108
|
+
|
109
|
+
"#{check_external(target)}.liquor_send(#{name.inspect}, [ #{out_args.join(", ")} ], #{nloc(method).inspect})"
|
110
|
+
end
|
111
|
+
|
112
|
+
def call(node)
|
113
|
+
lhs, rhs = nvalue(node)
|
114
|
+
name, = nvalue(lhs)
|
115
|
+
arg, kw = nvalue(rhs)
|
116
|
+
|
117
|
+
if !@context.function? name
|
118
|
+
raise NameError.new("undefined function `#{name}'", nloc(lhs))
|
119
|
+
end
|
120
|
+
|
121
|
+
function = @context.compiler.function(name)
|
122
|
+
if function.unnamed_arg && arg.nil?
|
123
|
+
raise ArgumentError.new("unnamed argument is required, but none provided", nloc(rhs))
|
124
|
+
elsif !function.unnamed_arg && !arg.nil?
|
125
|
+
raise ArgumentError.new("unnamed argument is not accepted, but is provided", nloc(arg))
|
126
|
+
else
|
127
|
+
function.mandatory_named_args.each do |kwarg,|
|
128
|
+
unless kw.include? kwarg
|
129
|
+
raise ArgumentError.new("named argument `#{kwarg}' is required, but none provided", nloc(rhs))
|
130
|
+
end
|
131
|
+
end
|
132
|
+
kw.each do |kwarg, kwval|
|
133
|
+
if !function.mandatory_named_args.include?(kwarg) &&
|
134
|
+
!function.optional_named_args.include?(kwarg)
|
135
|
+
raise ArgumentError.new("named argument `#{kwarg}' is not accepted, but is provided", nloc(kwval))
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
if arg.nil?
|
141
|
+
args = [ "nil" ]
|
142
|
+
else
|
143
|
+
args = [ expr(arg) ]
|
144
|
+
end
|
145
|
+
|
146
|
+
kw = kw.map do |kwarg, kwval|
|
147
|
+
"#{kwarg.inspect} => #{expr(kwval)}"
|
148
|
+
end
|
149
|
+
args << "{ #{kw.join(", ")} }"
|
150
|
+
|
151
|
+
args << nloc(node).inspect
|
152
|
+
|
153
|
+
"@functions[#{name.inspect}].call(#{args.join(', ')})"
|
154
|
+
end
|
155
|
+
|
156
|
+
def index(node)
|
157
|
+
lhs, rhs = nvalue(node)
|
158
|
+
"#{check_tuple(lhs)}[#{check_integer(rhs)}]"
|
159
|
+
end
|
160
|
+
|
161
|
+
def plus(node)
|
162
|
+
lhs, rhs = nvalue(node)
|
163
|
+
"Runtime.add!(#{expr(lhs)}, #{nloc(lhs).inspect}," +
|
164
|
+
" #{expr(rhs)}, #{nloc(rhs).inspect})"
|
165
|
+
end
|
166
|
+
|
167
|
+
def integer_binop(node)
|
168
|
+
lhs, rhs = nvalue(node)
|
169
|
+
"(#{check_integer(lhs)} #{OPERATORS[ntype(node)]} #{check_integer(rhs)})"
|
170
|
+
end
|
171
|
+
|
172
|
+
def integer_unop(node)
|
173
|
+
expr, = nvalue(node)
|
174
|
+
"#{OPERATORS[ntype(node)]}(#{check_integer(expr)})"
|
175
|
+
end
|
176
|
+
|
177
|
+
def equality_binop(node)
|
178
|
+
lhs, rhs = nvalue(node)
|
179
|
+
"(#{expr(lhs)} #{OPERATORS[ntype(node)]} #{expr(rhs)})"
|
180
|
+
end
|
181
|
+
|
182
|
+
def boolean_binop(node)
|
183
|
+
lhs, rhs = nvalue(node)
|
184
|
+
"(#{convert_boolean(lhs)} #{OPERATORS[ntype(node)]} #{convert_boolean(rhs)})"
|
185
|
+
end
|
186
|
+
|
187
|
+
def boolean_unop(node)
|
188
|
+
expr, = nvalue(node)
|
189
|
+
# converts by itself
|
190
|
+
"#{OPERATORS[ntype(node)]}(#{expr(expr)})"
|
191
|
+
end
|
192
|
+
|
193
|
+
def convert_boolean(node)
|
194
|
+
"!!(#{expr(node)})"
|
195
|
+
end
|
196
|
+
|
197
|
+
def check_integer(node)
|
198
|
+
"Runtime.integer!(#{expr(node)}, #{nloc(node).inspect})"
|
199
|
+
end
|
200
|
+
|
201
|
+
def integer(node)
|
202
|
+
value, = nvalue(node)
|
203
|
+
value
|
204
|
+
end
|
205
|
+
|
206
|
+
def check_string(node)
|
207
|
+
"Runtime.string!(#{expr(node)}, #{nloc(node).inspect})"
|
208
|
+
end
|
209
|
+
|
210
|
+
def string(node)
|
211
|
+
value, = nvalue(node)
|
212
|
+
value.inspect
|
213
|
+
end
|
214
|
+
|
215
|
+
def check_tuple(node)
|
216
|
+
"Runtime.tuple!(#{expr(node)}, #{nloc(node).inspect})"
|
217
|
+
end
|
218
|
+
|
219
|
+
def tuple(node)
|
220
|
+
value, = nvalue(node)
|
221
|
+
code = value.map do |elem|
|
222
|
+
expr elem
|
223
|
+
end.join(", ")
|
224
|
+
"[ #{code} ]"
|
225
|
+
end
|
226
|
+
|
227
|
+
def check_external(node)
|
228
|
+
"Runtime.external!(#{expr(node)}, #{nloc(node).inspect})"
|
229
|
+
end
|
230
|
+
|
231
|
+
def check_interp(node)
|
232
|
+
"Runtime.interp!(#{expr(node)}, #{nloc(node).inspect})"
|
233
|
+
end
|
234
|
+
|
235
|
+
def compile_toplevel(block)
|
236
|
+
compile_block(block)
|
237
|
+
|
238
|
+
[
|
239
|
+
%!lambda { |_env={}, _storage={}|\n!,
|
240
|
+
%| _buf = ""\n|,
|
241
|
+
([
|
242
|
+
%| |,
|
243
|
+
@context.externals.map do |extern|
|
244
|
+
@context.access(extern)
|
245
|
+
end.join(", "),
|
246
|
+
%| = |,
|
247
|
+
@context.externals.map do |extern|
|
248
|
+
%Q|_env[#{extern.inspect}]|
|
249
|
+
end.join(", "),
|
250
|
+
] if @context.externals.any?),
|
251
|
+
%|\n|,
|
252
|
+
flush!,
|
253
|
+
%| _buf\n|,
|
254
|
+
%|}\n|
|
255
|
+
].join
|
256
|
+
end
|
257
|
+
|
258
|
+
def compile_block(block)
|
259
|
+
block.each do |node|
|
260
|
+
case ntype(node)
|
261
|
+
when :plaintext
|
262
|
+
cat! string(node)
|
263
|
+
|
264
|
+
when :interp
|
265
|
+
expr, = nvalue(node)
|
266
|
+
cat! check_interp(expr)
|
267
|
+
|
268
|
+
when :tag
|
269
|
+
ident, args = nvalue(node)
|
270
|
+
name, = nvalue(ident)
|
271
|
+
|
272
|
+
unless @compiler.has_tag? name
|
273
|
+
raise NameError.new("undefined tag `#{name}'", nloc(ident))
|
274
|
+
end
|
275
|
+
|
276
|
+
@compiler.tag(name).compile(self, node)
|
277
|
+
|
278
|
+
else
|
279
|
+
raise "unknown block-level node #{ntype(node)}"
|
280
|
+
end
|
281
|
+
end
|
282
|
+
rescue Liquor::Diagnostic => e
|
283
|
+
@compiler.add_diagnostic e
|
284
|
+
end
|
285
|
+
|
286
|
+
def capture
|
287
|
+
previous_capture_var = @current_capture_var
|
288
|
+
@current_capture_var = @buffer.lines.count.to_s # unique id
|
289
|
+
out! %Q{#{buf} = ""\n}
|
290
|
+
|
291
|
+
yield
|
292
|
+
|
293
|
+
buf
|
294
|
+
ensure
|
295
|
+
@current_capture_var = previous_capture_var
|
296
|
+
end
|
297
|
+
|
298
|
+
def cat!(string)
|
299
|
+
out! "#{buf} << (#{string})\n"
|
300
|
+
end
|
301
|
+
|
302
|
+
def out!(string)
|
303
|
+
string = string.gsub /(^|\n)/m, '\1' + (" " * @context.nesting)
|
304
|
+
@buffer.concat string
|
305
|
+
end
|
306
|
+
|
307
|
+
def flush!
|
308
|
+
@buffer
|
309
|
+
ensure
|
310
|
+
@buffer = ""
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'kaminari/models/array_extension'
|
2
|
+
require 'liquor/extensions/pagination'
|
3
|
+
|
4
|
+
module Kaminari
|
5
|
+
class PaginatableArray
|
6
|
+
def to_drop
|
7
|
+
Liquor::Pagination::Scope.new(self)
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_page_path(url_generator, page)
|
11
|
+
raise NotImplementedError, "Liquor: Kaminari::PaginatableArray cannot generate page paths"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,235 @@
|
|
1
|
+
require 'rails'
|
2
|
+
require 'liquor/drop/drop'
|
3
|
+
|
4
|
+
module Liquor
|
5
|
+
module Pagination
|
6
|
+
include Library
|
7
|
+
|
8
|
+
# Usage:
|
9
|
+
|
10
|
+
=begin
|
11
|
+
{% pagination start: %}
|
12
|
+
<div class='digg_pagination'>
|
13
|
+
{% if page.is_current then: %}
|
14
|
+
<span class="disabled previous_page">← Previous</span>
|
15
|
+
{% else %}
|
16
|
+
<a href="{{ previous_page_url }}" rel="previous" class="previous_page_page">← Previous</a>
|
17
|
+
{% end if %}
|
18
|
+
{% page: %}
|
19
|
+
{% if page.is_current then: %}
|
20
|
+
<em class="current">{{ page.number }}</em>
|
21
|
+
{% elsif page.is_next then: %}
|
22
|
+
<a href="{{ page.url }}" rel="next">{{ page.number }}</a>
|
23
|
+
{% elsif page.is_prev then: %}
|
24
|
+
<a href="{{ page.url }}" rel="previous">{{ page.number }}</a>
|
25
|
+
{% else %}
|
26
|
+
<a href="{{ page.url }}">3</a>
|
27
|
+
{% end if %}
|
28
|
+
{% gap: %}
|
29
|
+
<span class="gap">…</span>
|
30
|
+
{% end: %}
|
31
|
+
{% if page.is_current then: %}
|
32
|
+
<span class="disabled next_page">Next →</span>
|
33
|
+
{% else %}
|
34
|
+
<a href="{{ next_page_url }}" rel="next" class="next_page">Next →</a></div>
|
35
|
+
{% end if %}
|
36
|
+
{% end pagination %}
|
37
|
+
=end
|
38
|
+
|
39
|
+
class UrlGenerator
|
40
|
+
def self.bootstrap
|
41
|
+
unless @bootstrapped
|
42
|
+
include ::Rails.application.routes.url_helpers
|
43
|
+
@bootstrapped = true
|
44
|
+
end
|
45
|
+
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
cattr_accessor :default_url_options
|
50
|
+
self.default_url_options = {}
|
51
|
+
end
|
52
|
+
|
53
|
+
class PageExternal
|
54
|
+
include Liquor::External
|
55
|
+
|
56
|
+
def initialize(collection, options={})
|
57
|
+
@collection = collection
|
58
|
+
@options = options
|
59
|
+
@index = @collection.current_page
|
60
|
+
end
|
61
|
+
|
62
|
+
def is_current
|
63
|
+
@index == @collection.current_page
|
64
|
+
end
|
65
|
+
|
66
|
+
def is_first
|
67
|
+
@index == 1
|
68
|
+
end
|
69
|
+
|
70
|
+
def is_last
|
71
|
+
@index == @collection.total_pages - 1
|
72
|
+
end
|
73
|
+
|
74
|
+
def is_prev
|
75
|
+
@index == @collection.current_page - 1
|
76
|
+
end
|
77
|
+
|
78
|
+
def is_next
|
79
|
+
@index == @collection.current_page + 1
|
80
|
+
end
|
81
|
+
|
82
|
+
export :is_current, :is_first, :is_last, :is_prev, :is_next
|
83
|
+
|
84
|
+
def in_inner_window
|
85
|
+
(@collection.current_page - @index).abs <= @options[:inner_window]
|
86
|
+
end
|
87
|
+
|
88
|
+
def in_outer_window
|
89
|
+
@index <= @options[:outer_window] ||
|
90
|
+
@collection.total_pages - @index < @options[:outer_window]
|
91
|
+
end
|
92
|
+
|
93
|
+
def is_gap
|
94
|
+
!in_outer_window && !in_inner_window
|
95
|
+
end
|
96
|
+
|
97
|
+
export :in_inner_window, :in_outer_window, :is_gap
|
98
|
+
|
99
|
+
def with(index)
|
100
|
+
@index = index
|
101
|
+
self
|
102
|
+
end
|
103
|
+
|
104
|
+
def each_relevant_page
|
105
|
+
relevant_pages.each do |page|
|
106
|
+
with(page); yield
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def relevant_pages
|
111
|
+
left_window_plus_one = 1.upto(@options[:outer_window] + 1).to_a
|
112
|
+
right_window_plus_one = (@collection.total_pages - @options[:outer_window]).upto(@collection.total_pages).to_a
|
113
|
+
inside_window_plus_each_sides = (@collection.current_page - @options[:inner_window] - 1).upto(@collection.current_page + @options[:inner_window] + 1).to_a
|
114
|
+
|
115
|
+
(left_window_plus_one + inside_window_plus_each_sides + right_window_plus_one).uniq.sort.reject {|x| (x < 1) || (x > @collection.total_pages)}
|
116
|
+
end
|
117
|
+
|
118
|
+
def path
|
119
|
+
@url_generator ||= UrlGenerator.bootstrap.new
|
120
|
+
|
121
|
+
if @collection.respond_to? :to_page_path
|
122
|
+
@collection.to_page_path(@url_generator, @index)
|
123
|
+
elsif @collection.is_a? ActiveRecord::Relation
|
124
|
+
@url_generator.polymorphic_path(@collection.klass, page: @index)
|
125
|
+
else
|
126
|
+
raise "Don't know how to generate page path for #{@collection.class}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
export :path
|
130
|
+
end
|
131
|
+
|
132
|
+
class Scope < Liquor::Drop::Scope
|
133
|
+
def total_entries
|
134
|
+
@source.total_count
|
135
|
+
end
|
136
|
+
|
137
|
+
def per_page(size)
|
138
|
+
@source.per(size).to_drop
|
139
|
+
end
|
140
|
+
|
141
|
+
export :total_entries, :per_page
|
142
|
+
end
|
143
|
+
|
144
|
+
tag "pagination" do |emit, context, node|
|
145
|
+
arg, kw = check_args node,
|
146
|
+
nil,
|
147
|
+
:"start" => :block,
|
148
|
+
:"page" => :block,
|
149
|
+
:"gap" => :block,
|
150
|
+
:"end" => :block
|
151
|
+
|
152
|
+
context.nest do
|
153
|
+
context.declare :page
|
154
|
+
|
155
|
+
options = context.access(:options)
|
156
|
+
collection = context.access(:collection)
|
157
|
+
page = context.access(:page)
|
158
|
+
|
159
|
+
emit.out! %Q|#{page} = Liquor::Pagination::PageExternal.new(#{collection}, #{options})\n|
|
160
|
+
|
161
|
+
{ :first_page_path => '1',
|
162
|
+
:last_page_path => "#{collection}.total_pages - 1",
|
163
|
+
:next_page_path => "#{collection}.current_page + 1",
|
164
|
+
:prev_page_path => "#{collection}.current_page - 1",
|
165
|
+
}.each do |var, value|
|
166
|
+
context.declare var
|
167
|
+
emit.out! %Q|#{context.access(var)} = #{page}.with(#{value}).path\n|
|
168
|
+
end
|
169
|
+
|
170
|
+
[ :current_page, :total_pages ].each do |var|
|
171
|
+
context.declare var
|
172
|
+
emit.out! %Q|#{context.access(var)} = #{collection}.#{var}\n|
|
173
|
+
end
|
174
|
+
|
175
|
+
emit.out! %Q|#{page}.with(1)\n|
|
176
|
+
emit.compile_block kw[:start]
|
177
|
+
|
178
|
+
emit.out! %Q|#{page}.each_relevant_page do\n|
|
179
|
+
emit.out! %Q| if #{page}.is_gap\n|
|
180
|
+
emit.compile_block kw[:gap]
|
181
|
+
emit.out! %Q| else\n|
|
182
|
+
emit.compile_block kw[:page]
|
183
|
+
emit.out! %Q| end\n|
|
184
|
+
emit.out! %Q|end\n|
|
185
|
+
|
186
|
+
emit.out! %Q|#{page}.with(#{collection}.total_pages - 1)\n|
|
187
|
+
emit.compile_block kw[:end]
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# {% paginate posts %}
|
192
|
+
|
193
|
+
tag "paginate" do |emit, context, node|
|
194
|
+
name, collection, *kwargs = nvalue(node)
|
195
|
+
check_arg_type(collection, :expr)
|
196
|
+
|
197
|
+
valid_kws = %w[inner_window outer_window]
|
198
|
+
|
199
|
+
kw = Hash[kwargs.map do |kwarg|
|
200
|
+
check_arg_type(kwarg, :expr)
|
201
|
+
|
202
|
+
name, value = kwname(kwarg), kwvalue(kwarg)
|
203
|
+
if !valid_kws.include?(name)
|
204
|
+
raise SyntaxError.new("unexpected `#{name}', expecting one of #{valid_kws.
|
205
|
+
map { |kw| "`#{kw}'" }.join(", ")}", nloc(kwarg))
|
206
|
+
end
|
207
|
+
|
208
|
+
[ name, value ]
|
209
|
+
end]
|
210
|
+
|
211
|
+
context.nest do
|
212
|
+
context.declare :collection
|
213
|
+
emit.out! %Q|#{context.access(:collection)} = #{emit.check_external(collection)}.source\n|
|
214
|
+
|
215
|
+
inner_window = kw[:inner_window] ? emit.check_integer(kw[:inner_window]) : '2'
|
216
|
+
outer_window = kw[:outer_window] ? emit.check_integer(kw[:outer_window]) : '0'
|
217
|
+
|
218
|
+
context.declare :options
|
219
|
+
emit.out! %Q|#{context.access(:options)} = {
|
220
|
+
inner_window: #{inner_window},
|
221
|
+
outer_window: #{outer_window}
|
222
|
+
}\n|
|
223
|
+
|
224
|
+
manager = context.compiler.manager
|
225
|
+
|
226
|
+
source = manager.fetch_partial "_pagination"
|
227
|
+
if source.nil?
|
228
|
+
raise ArgumentError.new("partial `pagination' does not exist", nloc(arg))
|
229
|
+
end
|
230
|
+
|
231
|
+
emit.compile_block source
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|