python-liquid 2.3.2__py3-none-any.whl → 2.3.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
liquid/__init__.py CHANGED
@@ -56,7 +56,7 @@ from .tag import Tag
56
56
 
57
57
  from . import future
58
58
 
59
- __version__ = "2.3.2"
59
+ __version__ = "2.3.3"
60
60
 
61
61
  __all__ = (
62
62
  "AwareBoundTemplate",
@@ -310,12 +310,12 @@ class LeExpression(Expression):
310
310
  def evaluate(self, context: RenderContext) -> object:
311
311
  left = self.left.evaluate(context)
312
312
  right = self.right.evaluate(context)
313
- return _eq(left, right) or _lt(self.token, left, right)
313
+ return _le(left, right)
314
314
 
315
315
  async def evaluate_async(self, context: RenderContext) -> object:
316
316
  left = await self.left.evaluate_async(context)
317
317
  right = await self.right.evaluate_async(context)
318
- return _eq(left, right) or _lt(self.token, left, right)
318
+ return _le(left, right)
319
319
 
320
320
  def children(self) -> list[Expression]:
321
321
  return [self.left, self.right]
@@ -335,12 +335,12 @@ class GeExpression(Expression):
335
335
  def evaluate(self, context: RenderContext) -> object:
336
336
  left = self.left.evaluate(context)
337
337
  right = self.right.evaluate(context)
338
- return _eq(left, right) or _lt(self.token, right, left)
338
+ return _le(right, left)
339
339
 
340
340
  async def evaluate_async(self, context: RenderContext) -> object:
341
341
  left = await self.left.evaluate_async(context)
342
342
  right = await self.right.evaluate_async(context)
343
- return _eq(left, right) or _lt(self.token, right, left)
343
+ return _le(right, left)
344
344
 
345
345
  def children(self) -> list[Expression]:
346
346
  return [self.left, self.right]
@@ -583,6 +583,12 @@ def is_truthy(obj: object) -> bool:
583
583
 
584
584
 
585
585
  def _eq(left: object, right: object) -> bool:
586
+ if is_undefined(left):
587
+ return right is None or is_undefined(right)
588
+
589
+ if is_undefined(right):
590
+ return left is None or is_undefined(left)
591
+
586
592
  if hasattr(left, "__liquid__"):
587
593
  left = left.__liquid__()
588
594
 
@@ -612,14 +618,14 @@ def _lt(token: Token, left: object, right: object) -> bool:
612
618
  if isinstance(left, str) and isinstance(right, str):
613
619
  return left < right
614
620
 
615
- if isinstance(left, bool) or isinstance(right, bool):
616
- return False
617
-
618
621
  if isinstance(left, (int, float, Decimal)) and isinstance(
619
622
  right, (int, float, Decimal)
620
623
  ):
621
624
  return left < right
622
625
 
626
+ if left is None or right is None:
627
+ return False
628
+
623
629
  raise LiquidTypeError(
624
630
  f"'<' and '>' are not supported between '{left.__class__.__name__}' "
625
631
  f"and '{right.__class__.__name__}'",
@@ -627,6 +633,30 @@ def _lt(token: Token, left: object, right: object) -> bool:
627
633
  )
628
634
 
629
635
 
636
+ def _le(left: object, right: object) -> bool:
637
+ if hasattr(left, "__liquid__"):
638
+ left = left.__liquid__()
639
+
640
+ if hasattr(right, "__liquid__"):
641
+ right = right.__liquid__()
642
+
643
+ if isinstance(left, str) and isinstance(right, str):
644
+ return left < right
645
+
646
+ if isinstance(left, bool) or isinstance(right, bool):
647
+ return False
648
+
649
+ if isinstance(left, (int, float, Decimal)) and isinstance(
650
+ right, (int, float, Decimal)
651
+ ):
652
+ return left < right
653
+
654
+ if left is None or right is None:
655
+ return False
656
+
657
+ return _eq(left, right)
658
+
659
+
630
660
  def _contains(token: Token, left: object, right: object) -> bool:
631
661
  if not is_truthy(left) or not is_truthy(right):
632
662
  return False
@@ -237,12 +237,12 @@ class RangeLiteral(Expression):
237
237
  def _make_range(self, start: Any, stop: Any) -> range:
238
238
  try:
239
239
  start = to_int(start)
240
- except ValueError:
240
+ except (ValueError, TypeError):
241
241
  start = 0
242
242
 
243
243
  try:
244
244
  stop = to_int(stop)
245
- except ValueError:
245
+ except (ValueError, TypeError):
246
246
  stop = 0
247
247
 
248
248
  # Descending ranges don't work
@@ -189,6 +189,10 @@ class TablerowNode(Node):
189
189
  name = self.expression.identifier
190
190
  loop_iter, length = self.expression.evaluate(context)
191
191
 
192
+ if not length:
193
+ # An empty iterable renders no rows.
194
+ return True
195
+
192
196
  if self.expression.cols:
193
197
  cols = self._int_or_zero(self.expression.cols.evaluate(context))
194
198
  else:
@@ -204,7 +208,7 @@ class TablerowNode(Node):
204
208
  buffer.write('<tr class="row1">\n')
205
209
  _break = False
206
210
 
207
- with context.extend(namespace):
211
+ with context.loop(namespace, tablerow):
208
212
  for item in tablerow:
209
213
  namespace[name] = item
210
214
  buffer.write(f'<td class="col{tablerow.col}">')
liquid/context.py CHANGED
@@ -39,6 +39,7 @@ from .utils import ReadOnlyChainMap
39
39
 
40
40
  if TYPE_CHECKING:
41
41
  from .builtin.tags.for_tag import ForLoop
42
+ from .builtin.tags.tablerow_tag import TableRow
42
43
  from .environment import Environment
43
44
  from .template import BoundTemplate
44
45
  from .token import Token
@@ -108,8 +109,10 @@ class RenderContext:
108
109
  "macros": {},
109
110
  }
110
111
 
111
- # As stack of forloop objects. Used for populating forloop.parentloop.
112
- self.loops: list[ForLoop] = []
112
+ # As stack of loop objects. Used for populating forloop.parentloop and
113
+ # calculating the effective loop count for the loop iteration resource
114
+ # limit.
115
+ self.loops: list[Union[ForLoop, TableRow]] = []
113
116
 
114
117
  # A list of tags names that are disallowed in this context. For example,
115
118
  # partial templates rendered using the "render" tag are not allowed to
@@ -368,7 +371,7 @@ class RenderContext:
368
371
 
369
372
  @contextmanager
370
373
  def loop(
371
- self, namespace: Mapping[str, object], forloop: ForLoop
374
+ self, namespace: Mapping[str, object], forloop: ForLoop | TableRow
372
375
  ) -> Iterator[RenderContext]:
373
376
  """Just like `Context.extend`, but keeps track of ForLoop objects too."""
374
377
  self.raise_for_loop_limit(forloop.length)
@@ -381,10 +384,11 @@ class RenderContext:
381
384
 
382
385
  def parentloop(self) -> Union[Undefined, object]:
383
386
  """Return the last ForLoop object from the loop stack."""
384
- try:
385
- return self.loops[-1]
386
- except IndexError:
387
- return self.env.undefined("parentloop", token=None)
387
+ for loop in reversed(self.loops):
388
+ if hasattr(loop, "parentloop"):
389
+ return loop
390
+
391
+ return self.env.undefined("parentloop", token=None)
388
392
 
389
393
  def raise_for_loop_limit(self, length: int = 1) -> None:
390
394
  """Raise a `LoopIterationLimitError` if loop stack is bigger than the limit."""
liquid/lex.py CHANGED
@@ -54,14 +54,21 @@ def compile_liquid_rules(
54
54
  rf"{tag_s}-?\s*enddoc\s*(?P<rsd>-?){tag_e}"
55
55
  )
56
56
 
57
- output_pattern = rf"{stmt_s}-?\s*(?P<stmt>.*?)\s*(?P<rss>-?){stmt_e}"
57
+ output_pattern = (
58
+ rf"{stmt_s}-?\s*"
59
+ rf"(?P<stmt>(?:(?!-?{stmt_e})[\s\S])*(?<!\s))\s*"
60
+ rf"(?P<rss>-?){stmt_e}"
61
+ )
58
62
 
59
63
  # The "name" group is zero or more characters so that a malformed tag (one
60
64
  # with no name) does not get treated as a literal.
61
65
  #
62
66
  # The `#` in the `name` group is specifically for the inline comment tag.
63
67
  tag_pattern = (
64
- rf"{tag_s}-?(?P<pre>\s*(?P<name>#|\w*)\s*)(?P<expr>.*?)\s*(?P<rst>-?){tag_e}"
68
+ rf"{tag_s}-?\s*"
69
+ rf"(?P<name>#|\w+)\s*"
70
+ rf"(?P<expr>(?:(?!-?{tag_e})[\s\S])*(?<!\s))\s*"
71
+ rf"(?P<rst>-?){tag_e}"
65
72
  )
66
73
 
67
74
  if not comment_start_string:
@@ -157,6 +164,7 @@ def _tokenize_template(source: str, rules: Pattern[str]) -> Iterator[Token]: #
157
164
 
158
165
  lstrip = bool(match.group("rss"))
159
166
  continue
167
+
160
168
  elif kind == "TAG":
161
169
  name = match.group("name")
162
170
  yield Token(
@@ -219,7 +227,7 @@ def _tokenize_template(source: str, rules: Pattern[str]) -> Iterator[Token]: #
219
227
  )
220
228
  if value.startswith(r"{%"):
221
229
  raise LiquidSyntaxError(
222
- "expected '%}', found end of file",
230
+ "missing tag name or closing delimiter",
223
231
  token=Token(
224
232
  TOKEN_EOF,
225
233
  value=match.group(),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: python-liquid
3
- Version: 2.3.2
3
+ Version: 2.3.3
4
4
  Summary: A Python engine for the Liquid template language.
5
5
  Project-URL: Change Log, https://github.com/jg-rp/liquid/blob/main/CHANGES.md
6
6
  Project-URL: Documentation, https://jg-rp.github.io/liquid/
@@ -1,12 +1,12 @@
1
- liquid/__init__.py,sha256=qJCQnUp4SFeIjlrsTo6I07bqqVUFptfOj9xbwHXb3nQ,7696
1
+ liquid/__init__.py,sha256=1vgDH9TV0Poss1BQ7fe7GKVSEmrjInlgjMEsMOX0P98,7696
2
2
  liquid/analyze_tags.py,sha256=Uc1nueKLRiIejs2JyQIC7u2pxp9l-5HJAMWlg-Qj1m8,7615
3
3
  liquid/ast.py,sha256=4ZqSbuW4mv9IvoIMPk3OICUaOI1vPh8P8uFCrXI_3XA,8140
4
- liquid/context.py,sha256=fEuMsj9MVguX3Kzy9_k99TWekOwAToL0RtbGm9yZ71o,21718
4
+ liquid/context.py,sha256=GYMG5-u3JI3w1jN_osEtyNsjziWgjBBMFZTilJEMap0,21931
5
5
  liquid/environment.py,sha256=7lq5Y6jAaN6dzWMbqXYnUcSBuOke768xoPidP-WK2Y4,24157
6
6
  liquid/exceptions.py,sha256=NNEWqVdY2QCjCvTtfqnV0_b1C0jL96IHOn6NlnabBXY,8451
7
7
  liquid/expression.py,sha256=Ozgajah2R5eg1yWA4ITFGaMFoOLt8GIjkE1NKqbBEzk,1143
8
8
  liquid/filter.py,sha256=3NtqGk6nHNwe6ux3X6ZIzzcEMJNA_SaMrBmMQA8hf-I,6735
9
- liquid/lex.py,sha256=PrnnUWhXZO6novkO0KE-p4virh5cNJsg7m85sg2IUiw,8004
9
+ liquid/lex.py,sha256=vM8mU7OnqH_nLlvnT0S3GJT2VxdnXjRXr_TRkp2y2ZE,8139
10
10
  liquid/limits.py,sha256=BxqiC4Ax5iadmh6jDRcUNjFfOvbMkvZ1a046R1Cb8UA,1700
11
11
  liquid/loader.py,sha256=R_RniSRszCXmRVZgXlu9WjV130cWvspVcC3vIVpf-qg,4381
12
12
  liquid/messages.py,sha256=r6OcJG4-dLEULc9JtQ-mh3Jf6nNZ9Aa2reun7zYiUyc,11861
@@ -32,10 +32,10 @@ liquid/builtin/expressions/__init__.py,sha256=5scdgzOrMK9SxKpab3ebJnfO56Dl4bYLXO
32
32
  liquid/builtin/expressions/_tokenize.py,sha256=p7RbeOg8iwXDnW0fIL4_7FSrBulkPYrCm3ialpEIsDE,5737
33
33
  liquid/builtin/expressions/arguments.py,sha256=hlZh7LLFT5UismTo7mTlVjM87_ihUJo0BYuCxyTwKMI,6448
34
34
  liquid/builtin/expressions/filtered.py,sha256=FqPNZLjwDdlCfivtBa7e0wuQYwcJdKUl3_UY9AS3zG4,13073
35
- liquid/builtin/expressions/logical.py,sha256=S8T9IgsuWgv0s7GYcGvIJFOqYMiMFRpat7zF3nCZHR0,20388
35
+ liquid/builtin/expressions/logical.py,sha256=sVjmg6IZAEzlj8j_UzyDetPRlrxqOeF_HhlKDSsAzZc,20980
36
36
  liquid/builtin/expressions/loop.py,sha256=F2mBD9_bXtarzmEKstak_u50cXF9ijlKs9Ip2Q_BbsQ,8839
37
37
  liquid/builtin/expressions/path.py,sha256=m1J33uIuEFlUaRXR_-G6qcsClOm0B5jbTZ0Pdt1P7Ak,5703
38
- liquid/builtin/expressions/primitive.py,sha256=Gzaeb-dF84zsMn5ZYhe-FOosnSbkkM2C6JLqjLvK5ns,11352
38
+ liquid/builtin/expressions/primitive.py,sha256=MTREfbutdN-N0gDYrWrtP8X2cEUFUW3rs6nJmqv37rU,11378
39
39
  liquid/builtin/filters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
40
  liquid/builtin/filters/array.py,sha256=jy35hoouAWI1yG2JX5OMG39Az2dmP--4IcuwqT59JMY,10281
41
41
  liquid/builtin/filters/extra.py,sha256=m13_1nbZyRBl3CZV9WWGLlUZueRPt8cKPEHRAioqM_o,2710
@@ -66,7 +66,7 @@ liquid/builtin/tags/increment_tag.py,sha256=SV2nrbE90l4iz36WCROqGE0RkkzztWUpU1_P
66
66
  liquid/builtin/tags/inline_comment_tag.py,sha256=hLLtvaI-VpJxzFeoLTwDTj46g13A1ke6blqVo2MP0Cs,1357
67
67
  liquid/builtin/tags/liquid_tag.py,sha256=o3ybbBmjIY94y_PpOUbgSwktENrGJ-jhZ124-rTlM4Q,5524
68
68
  liquid/builtin/tags/render_tag.py,sha256=iYWjKYQBmGHQVQ9OZgPZvVhraygFdmPzITin7cKJI5E,14309
69
- liquid/builtin/tags/tablerow_tag.py,sha256=qE5hIrlJXdKiLJ0u1YLgjnqt6GedWujfcfIO0b9x0d8,8947
69
+ liquid/builtin/tags/tablerow_tag.py,sha256=IFG3G8e2zn94H-qFtlCaqYK46msNybp3LqbSlNIsI84,9052
70
70
  liquid/builtin/tags/unless_tag.py,sha256=udlHvVxILJsYfDaWRkj6Ynmnz2-2FF5RfkP0eizXt7k,6932
71
71
  liquid/extra/__init__.py,sha256=KrE9DAkwCAOLIaBHt73pnL6sb1tN5-UEKoQsX1b43Gg,2886
72
72
  liquid/extra/filters/__init__.py,sha256=tsEfSmaJvd6xN8DfKISPOm_KcHOmBVc4RSQqViD8NvY,733
@@ -90,7 +90,7 @@ liquid/utils/chain_map.py,sha256=nxkw3wwF6ddlGarIuL7Ii2elm4dU80LySgdQx1oift0,151
90
90
  liquid/utils/html.py,sha256=TmqOOpRMsy7fqZLj7X5ybd_XQnWW2YDAVDwTP1Gwf40,1706
91
91
  liquid/utils/lru_cache.py,sha256=p7bXOGaUwJpLsREI2lGSzK6lLna5W_k_zNXKWnPJdos,4022
92
92
  liquid/utils/text.py,sha256=1SwDECNMaqnnZ05je_AZZgxqzZd6U-mvq5jNU3W1-Qk,841
93
- python_liquid-2.3.2.dist-info/METADATA,sha256=FpoUkgvAUbCL5Xg2He4xOmqVvCU1E_xvZpPn8ci9FYw,6786
94
- python_liquid-2.3.2.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
95
- python_liquid-2.3.2.dist-info/licenses/LICENSE,sha256=yAFURzud5ERNHt1rZIPnTLJ92ep7q8y5yG9g5DUMR_E,1075
96
- python_liquid-2.3.2.dist-info/RECORD,,
93
+ python_liquid-2.3.3.dist-info/METADATA,sha256=a-KRGeZCSl--UvxyKik-9b25wCnmrH2NmHt41buB618,6786
94
+ python_liquid-2.3.3.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
95
+ python_liquid-2.3.3.dist-info/licenses/LICENSE,sha256=yAFURzud5ERNHt1rZIPnTLJ92ep7q8y5yG9g5DUMR_E,1075
96
+ python_liquid-2.3.3.dist-info/RECORD,,