on master help sign in
Remove @each pagination
7176267iko
DESIGN.md+1
@@ -488,6 +488,7 @@ removed form with its replacement.
488| `@let x $a + 1` (computed without `=`) | `@let x = $a + 1` |
488| `@let x $a + 1` (computed without `=`) | `@let x = $a + 1` |
489| `COND ? A : B` | `if(COND, A, B)` |
489| `COND ? A : B` | `if(COND, A, B)` |
490| `$a ~ " " ~ $b` | `"$a $b"` |
490| `$a ~ " " ~ $b` | `"$a $b"` |
491+| `@each $x in LIST [page N]` | Split the list, or filter it with `@if` |
491| `...$bundle` | `$bundle` |
492| `...$bundle` | `$bundle` |
492| `animate`, `inset-area` | `animation`, `position-area` |
493| `animate`, `inset-area` | `animation`, `position-area` |
493| `$x\|uppercase`, `$c\|darken:10` (filters) | `${uppercase($x)}`, `${darken($c, 10)}` |
494| `$x\|uppercase`, `$c\|darken:10` (filters) | `${uppercase($x)}`, `${darken($c, 10)}` |
crates/htmlang-core/src/parser.rs+8−63
@@ -1325,14 +1325,14 @@ impl Parser {
1325 let var_name = var_names[0].clone();
1325 let var_name = var_names[0].clone();
1326 let index_var = var_names.get(1).cloned();
1326 let index_var = var_names.get(1).cloned();
1327
1327
1328− // Strip a trailing `[page N]` pagination suffix so it doesn't end
1328+ if list_str.trim_end().ends_with(']') && list_str.contains("[page ") {
1329− // up in the last item; it's parsed from the raw line below.
1329+ return Err(ParseError {
1330− let list_str = match list_str.rfind("[page ") {
1330+ line: line_num,
1331− Some(pos) if list_str.trim_end().ends_with(']') => {
1331+ message: "@each pagination (`[page N]`) was removed: split the list or \
1332+ filter it with @if"
1332− list_str[..pos].trim_end().to_string()
1333+ .to_string(),
1333− }
1334+ });
1334− _ => list_str,
1335− };
1335+ }
1336 track_var_refs(&list_str, &mut ctx.used_variables);
1336 track_var_refs(&list_str, &mut ctx.used_variables);
1337 let list_str = substitute_vars(&list_str, &ctx.variables);
1337 let list_str = substitute_vars(&list_str, &ctx.variables);
1338 // Support range syntax: @each $i in 1..5 or @each $i in 0..100 step 10
1338 // Support range syntax: @each $i in 1..5 or @each $i in 0..100 step 10
@@ -1360,50 +1360,6 @@ impl Parser {
1360 .collect()
1360 .collect()
1361 };
1361 };
1362
1362
1363− // Pagination: check for [page N] or [page N per P] suffix
1364− // Parse from the original rest before substitution
1365− let (items, pagination_info) = {
1366− // Check if list_str ended with a page directive (already consumed in items)
1367− // Instead, check the raw rest for [page ...] syntax
1368− let raw_rest = rest;
1369− let page_size = if let Some(page_pos) = raw_rest.find("[page ") {
1370− let after = &raw_rest[page_pos + 6..];
1371− if let Some(close) = after.find(']') {
1372− let page_spec = after[..close].trim();
1373− page_spec.parse::<usize>().ok()
1374− } else {
1375− None
1376− }
1377− } else {
1378− None
1379− };
1380−
1381− if let Some(size) = page_size {
1382− let current_page: usize = ctx
1383− .variables
1384− .get("_page")
1385− .and_then(|v| v.parse().ok())
1386− .unwrap_or(1)
1387− .max(1);
1388− let size = size.max(1);
1389− let total_items = items.len();
1390− let total_pages = total_items.div_ceil(size);
1391− let start = (current_page - 1).saturating_mul(size);
1392− let end = start.saturating_add(size).min(total_items);
1393− let page_items: Vec<String> = if start < total_items {
1394− items[start..end].to_vec()
1395− } else {
1396− Vec::new()
1397− };
1398− (
1399− page_items,
1400− Some((current_page, total_pages, size, total_items)),
1401− )
1402− } else {
1403− (items, None)
1404− }
1405− };
1406−
1407 // Collect body lines
1363 // Collect body lines
1408 let mut body_lines = Vec::new();
1364 let mut body_lines = Vec::new();
1409 while self.pos < self.lines.len() && self.lines[self.pos].indent > current_indent {
1365 while self.pos < self.lines.len() && self.lines[self.pos].indent > current_indent {
@@ -1466,17 +1422,6 @@ impl Parser {
1466 let saved_vars = ctx.variables.clone();
1422 let saved_vars = ctx.variables.clone();
1467 let mut all_nodes = Vec::new();
1423 let mut all_nodes = Vec::new();
1468
1424
1469− // Inject pagination variables if pagination is active
1470− if let Some((page, total_pages, page_size, total_items)) = pagination_info {
1471− ctx.variables.insert("_page".to_string(), page.to_string());
1472− ctx.variables
1473− .insert("_total_pages".to_string(), total_pages.to_string());
1474− ctx.variables
1475− .insert("_page_size".to_string(), page_size.to_string());
1476− ctx.variables
1477− .insert("_total_items".to_string(), total_items.to_string());
1478− }
1479−
1480 let has_extra_vars = var_names.len() > 2
1425 let has_extra_vars = var_names.len() > 2
1481 || (var_names.len() == 2 && items.first().is_some_and(|it| it.contains(' ')));
1426 || (var_names.len() == 2 && items.first().is_some_and(|it| it.contains(' ')));
1482
1427
src/upgrade.rs+8
@@ -653,6 +653,14 @@ fn rewrite_directive_line(
653 return format!("{pad}@data ${} {}", name, pattern);
653 return format!("{pad}@data ${} {}", name, pattern);
654 }
654 }
655 }
655 }
656+ if trimmed.starts_with("@each ") && trimmed.trim_end().ends_with(']') && trimmed.contains("[page ") {
657+ manual.push((
658+ idx + 1,
659+ "@each pagination ([page N]) was removed: split the list or filter it with @if"
660+ .to_string(),
661+ ));
662+ return line.to_string();
663+ }
656 if trimmed.starts_with("@fetch ") {
664 if trimmed.starts_with("@fetch ") {
657 manual.push((
665 manual.push((
658 idx + 1,
666 idx + 1,
tests/regressions.rs−12
@@ -69,12 +69,6 @@ fn json_unicode_escapes_are_decoded() {
69 assert!(out.contains("Café 😀"), "{}", out);
69 assert!(out.contains("Café 😀"), "{}", out);
70}
70}
71
71
72−#[test]
73−fn page_zero_does_not_underflow() {
74− let out = compile("@let _page 0\n@each $x in a, b, c [page 2]\n @text $x");
75− assert!(out.contains(">a<") && out.contains(">b<"), "{}", out);
76−}
77−
78#[test]
72#[test]
79fn range_at_integer_limit_terminates() {
73fn range_at_integer_limit_terminates() {
80 let out = compile("@each $i in 9223372036854775806..9223372036854775807\n @text $i");
74 let out = compile("@each $i in 9223372036854775806..9223372036854775807\n @text $i");
@@ -185,12 +179,6 @@ fn triple_quoted_let_is_a_string_not_a_function() {
185 assert!(!out.contains("$msg"), "{}", out);
179 assert!(!out.contains("$msg"), "{}", out);
186}
180}
187
181
188−#[test]
189−fn pagination_suffix_is_not_an_item() {
190− let out = compile("@each $x in a, b [page 5]\n @text $x");
191− assert!(!out.contains("[page"), "{}", out);
192−}
193−
194#[test]
182#[test]
195fn each_else_does_not_leak_variables() {
183fn each_else_does_not_leak_variables() {
196 let out = compile(
184 let out = compile(
tests/snapshots.rs+3−19
@@ -4458,8 +4458,9 @@ fn snapshot_translations_i18n() {
4458}
4458}
4459
4459
4460#[test]
4460#[test]
4461−fn snapshot_pagination_each() {
4461+fn each_pagination_was_removed() {
4462− snapshot_test("pagination_each");
4462+ let diags = parse_diagnostics("@each $item in a,b,c [page 2]\n @text $item");
4463+ assert!(diags.iter().any(|d| d.message.contains("pagination")), "{:?}", diags);
4463}
4464}
4464
4465
4465#[test]
4466#[test]
@@ -4489,23 +4490,6 @@ fn translations_point_to_data_files() {
4489 );
4490 );
4490}
4491}
4491
4492
4492−#[test]
4493−fn pagination_limits_items() {
4494− let output = compile("@let _page 1\n@each $item in a,b,c,d,e [page 2]\n @text $item");
4495− // Page 1 with page size 2 should show items a,b only
4496− assert!(output.contains(">a<"), "page 1 should contain item a");
4497− assert!(output.contains(">b<"), "page 1 should contain item b");
4498− assert!(!output.contains(">c<"), "page 1 should NOT contain item c");
4499−}
4500−
4501−#[test]
4502−fn pagination_page_2() {
4503− let output = compile("@let _page 2\n@each $item in a,b,c,d,e [page 2]\n @text $item");
4504− assert!(output.contains(">c<"), "page 2 should contain item c");
4505− assert!(output.contains(">d<"), "page 2 should contain item d");
4506− assert!(!output.contains(">a<"), "page 2 should NOT contain item a");
4507−}
4508−
4509#[test]
4493#[test]
4510fn image_auto_preload() {
4494fn image_auto_preload() {
4511 let output = compile("@page Test\n@image logo.png\n@image hero.jpg");
4495 let output = compile("@page Test\n@image logo.png\n@image hero.jpg");
tests/snapshots/pagination_each.hldeleted−3
@@ -1,3 +0,0 @@
1−@let _page 1
2−@each $item in a,b,c,d,e,f [page 2]
3− @text $item
tests/snapshots/pagination_each.htmldeleted−1
@@ -1 +0,0 @@
1−<style>@layer htmlang{}</style><span>a</span><span>b</span>