liquor 0.1.1 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (176) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +3 -9
  5. data/Gemfile +7 -0
  6. data/Guardfile +11 -0
  7. data/MIT-LICENSE +6 -2
  8. data/README.md +4 -122
  9. data/Rakefile +20 -23
  10. data/doc/language-spec.html +768 -0
  11. data/doc/language-spec.md +698 -0
  12. data/lib/liquor.rb +39 -68
  13. data/lib/liquor/ast_tools.rb +28 -0
  14. data/lib/liquor/compiler.rb +110 -0
  15. data/lib/liquor/context.rb +76 -254
  16. data/lib/liquor/diagnostics.rb +151 -0
  17. data/lib/liquor/drop/drop.rb +168 -0
  18. data/lib/liquor/drop/drop_delegation.rb +24 -0
  19. data/lib/liquor/drop/drop_scope.rb +118 -0
  20. data/lib/liquor/drop/dropable.rb +17 -0
  21. data/lib/liquor/emitter.rb +313 -0
  22. data/lib/liquor/extensions/kaminari.rb +14 -0
  23. data/lib/liquor/extensions/pagination.rb +235 -0
  24. data/lib/liquor/extensions/rails.rb +97 -0
  25. data/lib/liquor/extensions/thinking_sphinx.rb +14 -0
  26. data/lib/liquor/extensions/tire.rb +30 -0
  27. data/lib/liquor/external.rb +79 -0
  28. data/lib/liquor/function.rb +94 -0
  29. data/lib/liquor/grammar/lexer.rb +1223 -0
  30. data/lib/liquor/grammar/lexer.rl +297 -0
  31. data/lib/liquor/grammar/parser.racc +288 -0
  32. data/lib/liquor/grammar/parser.rb +885 -0
  33. data/lib/liquor/library.rb +41 -0
  34. data/lib/liquor/manager.rb +146 -0
  35. data/lib/liquor/runtime.rb +167 -0
  36. data/lib/liquor/stdlib/builtin_functions.rb +315 -0
  37. data/lib/liquor/stdlib/builtin_tags.rb +228 -0
  38. data/lib/liquor/stdlib/html_truncater.rb +162 -0
  39. data/lib/liquor/stdlib/partial_tags.rb +76 -0
  40. data/lib/liquor/tag.rb +83 -14
  41. data/lib/liquor/version.rb +1 -1
  42. data/liquor.gemspec +29 -6
  43. data/spec/builtins_spec.rb +264 -0
  44. data/spec/compiler_spec.rb +136 -0
  45. data/spec/context_spec.rb +49 -0
  46. data/spec/drop_delegation_spec.rb +21 -0
  47. data/spec/drop_spec.rb +222 -0
  48. data/spec/errors_spec.rb +40 -0
  49. data/spec/external_spec.rb +207 -0
  50. data/spec/function_spec.rb +80 -0
  51. data/spec/lexer_spec.rb +173 -0
  52. data/spec/library_spec.rb +18 -0
  53. data/spec/manager_spec.rb +84 -0
  54. data/spec/parser_spec.rb +381 -0
  55. data/spec/partials_spec.rb +74 -0
  56. data/spec/runtime_spec.rb +97 -0
  57. data/spec/spec_helper.rb +94 -0
  58. data/spec/tag_spec.rb +7 -0
  59. metadata +216 -173
  60. data/AUTHORS +0 -2
  61. data/CHANGELOG +0 -48
  62. data/Gemfile.lock +0 -91
  63. data/History.txt +0 -44
  64. data/LICENSE +0 -23
  65. data/example/server/example_servlet.rb +0 -37
  66. data/example/server/liquid_servlet.rb +0 -28
  67. data/example/server/liquor_servlet.rb +0 -28
  68. data/example/server/server.rb +0 -12
  69. data/example/server/templates/index.liquid +0 -6
  70. data/example/server/templates/index.liquor +0 -6
  71. data/example/server/templates/products.liquid +0 -45
  72. data/example/server/templates/products.liquor +0 -45
  73. data/init.rb +0 -8
  74. data/lib/extras/liquid_view.rb +0 -51
  75. data/lib/extras/liquor_view.rb +0 -51
  76. data/lib/liquor/block.rb +0 -101
  77. data/lib/liquor/condition.rb +0 -120
  78. data/lib/liquor/document.rb +0 -17
  79. data/lib/liquor/drop.rb +0 -256
  80. data/lib/liquor/errors.rb +0 -11
  81. data/lib/liquor/extensions.rb +0 -72
  82. data/lib/liquor/file_system.rb +0 -62
  83. data/lib/liquor/htmltags.rb +0 -74
  84. data/lib/liquor/module_ex.rb +0 -60
  85. data/lib/liquor/standardfilters.rb +0 -315
  86. data/lib/liquor/strainer.rb +0 -58
  87. data/lib/liquor/tags/assign.rb +0 -33
  88. data/lib/liquor/tags/capture.rb +0 -35
  89. data/lib/liquor/tags/case.rb +0 -83
  90. data/lib/liquor/tags/comment.rb +0 -9
  91. data/lib/liquor/tags/content_for.rb +0 -54
  92. data/lib/liquor/tags/cycle.rb +0 -59
  93. data/lib/liquor/tags/for.rb +0 -136
  94. data/lib/liquor/tags/if.rb +0 -80
  95. data/lib/liquor/tags/ifchanged.rb +0 -20
  96. data/lib/liquor/tags/include.rb +0 -56
  97. data/lib/liquor/tags/unless.rb +0 -33
  98. data/lib/liquor/tags/yield.rb +0 -49
  99. data/lib/liquor/template.rb +0 -181
  100. data/lib/liquor/variable.rb +0 -52
  101. data/performance/shopify.rb +0 -92
  102. data/performance/shopify/comment_form.rb +0 -33
  103. data/performance/shopify/database.rb +0 -45
  104. data/performance/shopify/json_filter.rb +0 -7
  105. data/performance/shopify/liquid.rb +0 -18
  106. data/performance/shopify/liquor.rb +0 -18
  107. data/performance/shopify/money_filter.rb +0 -18
  108. data/performance/shopify/paginate.rb +0 -93
  109. data/performance/shopify/shop_filter.rb +0 -98
  110. data/performance/shopify/tag_filter.rb +0 -25
  111. data/performance/shopify/vision.database.yml +0 -945
  112. data/performance/shopify/weight_filter.rb +0 -11
  113. data/performance/tests/dropify/article.liquid +0 -74
  114. data/performance/tests/dropify/blog.liquid +0 -33
  115. data/performance/tests/dropify/cart.liquid +0 -66
  116. data/performance/tests/dropify/collection.liquid +0 -22
  117. data/performance/tests/dropify/index.liquid +0 -47
  118. data/performance/tests/dropify/page.liquid +0 -8
  119. data/performance/tests/dropify/product.liquid +0 -68
  120. data/performance/tests/dropify/theme.liquid +0 -105
  121. data/performance/tests/ripen/article.liquid +0 -74
  122. data/performance/tests/ripen/blog.liquid +0 -13
  123. data/performance/tests/ripen/cart.liquid +0 -54
  124. data/performance/tests/ripen/collection.liquid +0 -29
  125. data/performance/tests/ripen/index.liquid +0 -32
  126. data/performance/tests/ripen/page.liquid +0 -4
  127. data/performance/tests/ripen/product.liquid +0 -75
  128. data/performance/tests/ripen/theme.liquid +0 -85
  129. data/performance/tests/tribble/404.liquid +0 -56
  130. data/performance/tests/tribble/article.liquid +0 -98
  131. data/performance/tests/tribble/blog.liquid +0 -41
  132. data/performance/tests/tribble/cart.liquid +0 -134
  133. data/performance/tests/tribble/collection.liquid +0 -70
  134. data/performance/tests/tribble/index.liquid +0 -94
  135. data/performance/tests/tribble/page.liquid +0 -56
  136. data/performance/tests/tribble/product.liquid +0 -116
  137. data/performance/tests/tribble/search.liquid +0 -51
  138. data/performance/tests/tribble/theme.liquid +0 -90
  139. data/performance/tests/vogue/article.liquid +0 -66
  140. data/performance/tests/vogue/blog.liquid +0 -32
  141. data/performance/tests/vogue/cart.liquid +0 -58
  142. data/performance/tests/vogue/collection.liquid +0 -19
  143. data/performance/tests/vogue/index.liquid +0 -22
  144. data/performance/tests/vogue/page.liquid +0 -3
  145. data/performance/tests/vogue/product.liquid +0 -62
  146. data/performance/tests/vogue/theme.liquid +0 -122
  147. data/test/assign_test.rb +0 -11
  148. data/test/block_test.rb +0 -58
  149. data/test/capture_test.rb +0 -41
  150. data/test/condition_test.rb +0 -115
  151. data/test/content_for_test.rb +0 -15
  152. data/test/context_test.rb +0 -479
  153. data/test/drop_test.rb +0 -162
  154. data/test/error_handling_test.rb +0 -89
  155. data/test/extra/breakpoint.rb +0 -547
  156. data/test/extra/caller.rb +0 -80
  157. data/test/file_system_test.rb +0 -30
  158. data/test/filter_test.rb +0 -147
  159. data/test/helper.rb +0 -24
  160. data/test/html_tag_test.rb +0 -31
  161. data/test/if_else_test.rb +0 -139
  162. data/test/include_tag_test.rb +0 -129
  163. data/test/module_ex_test.rb +0 -89
  164. data/test/output_test.rb +0 -121
  165. data/test/parsing_quirks_test.rb +0 -54
  166. data/test/regexp_test.rb +0 -45
  167. data/test/security_test.rb +0 -41
  168. data/test/standard_filter_test.rb +0 -170
  169. data/test/standard_tag_test.rb +0 -405
  170. data/test/statements_test.rb +0 -137
  171. data/test/strainer_test.rb +0 -27
  172. data/test/template_test.rb +0 -82
  173. data/test/test_helper.rb +0 -28
  174. data/test/unless_else_test.rb +0 -27
  175. data/test/variable_test.rb +0 -173
  176. 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">&#8592; Previous</span>
15
+ {% else %}
16
+ <a href="{{ previous_page_url }}" rel="previous" class="previous_page_page">&#8592; 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">&hellip;</span>
30
+ {% end: %}
31
+ {% if page.is_current then: %}
32
+ <span class="disabled next_page">Next &#8594;</span>
33
+ {% else %}
34
+ <a href="{{ next_page_url }}" rel="next" class="next_page">Next &#8594;</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