Repository: cobalt-org/cobalt.rs (rust)

Issue:
cobalt-bin Crash Report - Pagination and paginator
Hi, 

I am using pagination as described here: https://cobalt-org.github.io/docs/posts/
It worked about 3 months ago. However with 0.19.8 I get a crash when calling `cobalt build`.


This is the crash report:

cat /var/folders/yg/n6fwj58d7b31v4kjjsmw67sc0000gn/T/report-9613a5e2-de32-417e-9fd4-0a82c8ed3365.toml                                                                             
name = "cobalt-bin"
operating_system = "Mac OS 13.7.4 [64-bit]"
crate_version = "0.19.8"
explanation = """
Panic occurred in file 'src/cobalt.rs' at line 251
"""
cause = "We detected pagination enabled but we have no paginator"
method = "Panic"
backtrace = """
   0:        0x104872358 - backtrace::capture::Backtrace::create::hbc3d8f243c9e52de
   1:        0x1048722ac - backtrace::capture::Backtrace::new::h26e27755df3e353c
   2:        0x10473a0ac - human_panic::report::Report::new::h0e93a673cf947902
   3:        0x10473b29c - human_panic::handle_dump::h14c3d8597c847c8b
   4:        0x1046eccdc - human_panic::setup_panic::{{closure}}::h196f8410348e90d9
   5:        0x104b8beb8 - std::panicking::rust_panic_with_hook::h00b2262cd25a9e0f
   6:        0x104b6f53c - std::panicking::begin_panic_handler::{{closure}}::h466628b32158069b
   7:        0x104b6f238 - std::sys::backtrace::__rust_end_short_backtrace::h328e872885259eca
   8:        0x104b8b944 - _rust_begin_unwind
   9:        0x104bce644 - core::panicking::panic_fmt::h53349c61e2cc42f9
  10:        0x104bceb28 - core::option::expect_failed::h3989caede9c5b9e0
  11:        0x1047c9320 - cobalt::cobalt::generate_pages::hdc727cdd331ec16b
  12:        0x1047c3a48 - cobalt::cobalt::build::h47a3317530badc28
  13:        0x1046fd830 - cobalt::Cli::run::hbc6cbd367b867d20
  14:        0x104704f9c - cobalt::main::h30454941ca63f5b2
"""

Pull request:
fix(page): Don't panic on empty all 
Fixes #1239

Patch:
diff --git a/src/pagination/all.rs b/src/pagination/all.rs
index a311cb3..ee487aa 100644
--- a/src/pagination/all.rs
+++ b/src/pagination/all.rs
@@ -14,6 +14,19 @@ pub(crate) fn create_all_paginators(
     // f32 used here in order to not lose information to ceil the result,
     // otherwise we can lose an index
     let total_indexes = (total_pages as f32 / config.per_page as f32).ceil() as usize;
+
+    if total_pages == 0 {
+        return Ok(vec![paginator::create_paginator(
+            0,
+            total_indexes,
+            total_pages,
+            config,
+            doc,
+            &[],
+            index_title,
+        )?]);
+    }
+
     let paginators: Result<Vec<_>> = all_posts
         .chunks(config.per_page as usize)
         .enumerate()
diff --git a/src/pagination/tags.rs b/src/pagination/tags.rs
index daa362d..c8edc98 100644
--- a/src/pagination/tags.rs
+++ b/src/pagination/tags.rs
@@ -50,21 +50,22 @@ fn walk_tags(
     doc: &Document,
 ) -> Result<Vec<Paginator>> {
     // create all other paginators
-    let mut tag_paginators: TagPaginators = per_tags
-        .iter_mut()
-        .try_fold(TagPaginators::default(), |mut acc, (tag, posts)| {
-            sort_posts(posts, config);
-            let cur_tag_paginators = all::create_all_paginators(
-                posts,
-                doc,
-                config,
-                Some(&liquid::model::Value::scalar(tag.to_owned())),
-            )?;
-            acc.firsts_of_tags.push(cur_tag_paginators[0].clone());
-            acc.paginators.extend(cur_tag_paginators.into_iter());
-            Ok(acc)
-        })
-        .or_else(std::result::Result::<_, anyhow::Error>::Err)?;
+    let mut tag_paginators = TagPaginators::default();
+    for (tag, posts) in per_tags.iter_mut() {
+        sort_posts(posts, config);
+        let cur_tag_paginators = all::create_all_paginators(
+            posts,
+            doc,
+            config,
+            Some(&liquid::model::Value::scalar(tag.to_owned())),
+        )?;
+        tag_paginators
+            .firsts_of_tags
+            .push(cur_tag_paginators[0].clone());
+        tag_paginators
+            .paginators
+            .extend(cur_tag_paginators.into_iter());
+    }
 
     tag_paginators.firsts_of_tags.sort_unstable_by_key(|p| {
         p.index_title


Test patch:
diff --git a/tests/cmd/pagination_all_empty.in/_cobalt.yml b/tests/cmd/pagination_all_empty.in/_cobalt.yml
new file mode 100644
index 0000000..6c2e919
--- /dev/null
+++ b/tests/cmd/pagination_all_empty.in/_cobalt.yml
@@ -0,0 +1,2 @@
+syntax_highlight:
+  enabled: false
diff --git a/tests/cmd/pagination_all_empty.in/_dest/index.html b/tests/cmd/pagination_all_empty.in/_dest/index.html
new file mode 100644
index 0000000..ae679a8
--- /dev/null
+++ b/tests/cmd/pagination_all_empty.in/_dest/index.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>test</title>
+    </head>
+    <body>
+        <h1></h1>
+
+        This is my Index page!
+
+
+
+
+<div>
+  
+  
+
+  <span>1 / 0</span>
+</div>
+
+    </body>
+</html>
+
diff --git a/tests/cmd/pagination_all_empty.in/_layouts/default.liquid b/tests/cmd/pagination_all_empty.in/_layouts/default.liquid
new file mode 100644
index 0000000..426ae69
--- /dev/null
+++ b/tests/cmd/pagination_all_empty.in/_layouts/default.liquid
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>test</title>
+    </head>
+    <body>
+        <h1>{{ page.permalink }}</h1>
+
+        {{ page.content }}
+    </body>
+</html>
+
diff --git a/tests/cmd/pagination_all_empty.in/_layouts/posts.liquid b/tests/cmd/pagination_all_empty.in/_layouts/posts.liquid
new file mode 100644
index 0000000..6a21c64
--- /dev/null
+++ b/tests/cmd/pagination_all_empty.in/_layouts/posts.liquid
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>My blog - {{ page.title }}</title>
+    </head>
+    <body>
+        {{ page.content }}
+    </body>
+</html>
+
diff --git a/tests/cmd/pagination_all_empty.in/index.liquid b/tests/cmd/pagination_all_empty.in/index.liquid
new file mode 100644
index 0000000..2a30752
--- /dev/null
+++ b/tests/cmd/pagination_all_empty.in/index.liquid
@@ -0,0 +1,25 @@
+---
+permalink: /
+layout: default.liquid
+pagination:
+  include: All
+---
+This is my Index page!
+
+{% for page in paginator.pages %}
+ <a href="{{page.permalink}}">{{ page.title }}</a>
+{% endfor %}
+
+
+<div>
+  {% if paginator.previous_index %}
+    <a href="/{{ paginator.previous_index_permalink }}"
+    class="left arrow">&#8592;</a>
+  {% endif %}
+  {% if paginator.next_index %}
+    <a href="/{{ paginator.next_index_permalink }}"
+    class="right arrow">&#8594;</a>
+  {% endif %}
+
+  <span>{{ paginator.index }} / {{ paginator.total_indexes }}</span>
+</div>
diff --git a/tests/cmd/pagination_all_empty.md b/tests/cmd/pagination_all_empty.md
new file mode 100644
index 0000000..eedf2e0
--- /dev/null
+++ b/tests/cmd/pagination_all_empty.md
@@ -0,0 +1,15 @@
+```console
+$ cobalt -v build --destination _dest
+DEBUG: Using config file `./_cobalt.yml`
+Building from `.` into `[CWD]/_dest`
+DEBUG: glob converted to regex: Glob { glob: "**/.*", re: "(?-u)^(?:/?|.*/)//.[^/]*$", opts: GlobOptions { case_insensitive: false, literal_separator: true, backslash_escape: true, empty_alternates: false }, tokens: Tokens([RecursivePrefix, Literal('.'), ZeroOrMore]) }
+DEBUG: glob converted to regex: Glob { glob: "**/_*", re: "(?-u)^(?:/?|.*/)_[^/]*$", opts: GlobOptions { case_insensitive: false, literal_separator: true, backslash_escape: true, empty_alternates: false }, tokens: Tokens([RecursivePrefix, Literal('_'), ZeroOrMore]) }
+DEBUG: built glob set; 5 literals, 0 basenames, 0 extensions, 0 prefixes, 0 suffixes, 0 required extensions, 2 regexes
+DEBUG: Loading data from `./_data`
+DEBUG: glob converted to regex: Glob { glob: "**/.*", re: "(?-u)^(?:/?|.*/)//.[^/]*$", opts: GlobOptions { case_insensitive: false, literal_separator: true, backslash_escape: true, empty_alternates: false }, tokens: Tokens([RecursivePrefix, Literal('.'), ZeroOrMore]) }
+DEBUG: glob converted to regex: Glob { glob: "**/_*", re: "(?-u)^(?:/?|.*/)_[^/]*$", opts: GlobOptions { case_insensitive: false, literal_separator: true, backslash_escape: true, empty_alternates: false }, tokens: Tokens([RecursivePrefix, Literal('_'), ZeroOrMore]) }
+DEBUG: built glob set; 0 literals, 0 basenames, 0 extensions, 0 prefixes, 0 suffixes, 0 required extensions, 2 regexes
+DEBUG: Loading snippets from `./_includes`
+Build successful
+
+```
diff --git a/tests/cmd/pagination_categories_empty.in/_cobalt.yml b/tests/cmd/pagination_categories_empty.in/_cobalt.yml
new file mode 100644
index 0000000..6c2e919
--- /dev/null
+++ b/tests/cmd/pagination_categories_empty.in/_cobalt.yml
@@ -0,0 +1,2 @@
+syntax_highlight:
+  enabled: false
diff --git a/tests/cmd/pagination_categories_empty.in/_dest/categories.html b/tests/cmd/pagination_categories_empty.in/_dest/categories.html
new file mode 100644
index 0000000..26e0a91
--- /dev/null
+++ b/tests/cmd/pagination_categories_empty.in/_dest/categories.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>test</title>
+    </head>
+    <body>
+        <h1>categories.html</h1>
+
+        
+
+
+
+
+    </body>
+</html>
+
diff --git a/tests/cmd/pagination_categories_empty.in/_layouts/default.liquid b/tests/cmd/pagination_categories_empty.in/_layouts/default.liquid
new file mode 100644
index 0000000..426ae69
--- /dev/null
+++ b/tests/cmd/pagination_categories_empty.in/_layouts/default.liquid
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>test</title>
+    </head>
+    <body>
+        <h1>{{ page.permalink }}</h1>
+
+        {{ page.content }}
+    </body>
+</html>
+
diff --git a/tests/cmd/pagination_categories_empty.in/_layouts/posts.liquid b/tests/cmd/pagination_categories_empty.in/_layouts/posts.liquid
new file mode 100644
index 0000000..6a21c64
--- /dev/null
+++ b/tests/cmd/pagination_categories_empty.in/_layouts/posts.liquid
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>My blog - {{ page.title }}</title>
+    </head>
+    <body>
+        {{ page.content }}
+    </body>
+</html>
+
diff --git a/tests/cmd/pagination_categories_empty.in/index.liquid b/tests/cmd/pagination_categories_empty.in/index.liquid
new file mode 100644
index 0000000..97449f4
--- /dev/null
+++ b/tests/cmd/pagination_categories_empty.in/index.liquid
@@ -0,0 +1,41 @@
+---
+permalink: /categories.html
+layout: default.liquid
+pagination:
+  include: Categories
+  permalink_suffix: ./_p/{{num}}
+---
+{% if paginator.index_title %}
+  <div>Category: {% for category in paginator.index_title %}{{ category | prepend: "/" }}{% endfor %}</div>
+  {% if paginator.indexes %}
+    <div>Sub-categories:</div>
+  {% endif %}
+{% endif %}
+{% if paginator.indexes %}
+<ul>
+  {% for pcat in paginator.indexes %}
+    <li><a href="/{{ pcat.index_permalink }}/">{{ pcat.index_title | last }} ({{ pcat.total_pages }})</a>
+  {% endfor %}
+</ul>
+{% endif %}
+
+{% if paginator.pages %}
+<div>Posts in this category:</div>
+<ul>
+  {% for page in paginator.pages %}
+  <li><a href="/{{ page.permalink }}">{{ page.title }}</a>
+    {% endfor %}
+</ul>
+<div>
+  {% if paginator.previous_index %}
+    <a href="{{ paginator.previous_index_permalink }}"
+    class="left arrow">&#8592;</a>
+  {% endif %}
+  {% if paginator.next_index %}
+    <a href="{{ paginator.next_index_permalink }}"
+    class="right arrow">&#8594;</a>
+  {% endif %}
+
+  <span>{{ paginator.index }} / {{ paginator.total_indexes }}</span>
+</div>
+{% endif %}
diff --git a/tests/cmd/pagination_categories_empty.md b/tests/cmd/pagination_categories_empty.md
new file mode 100644
index 0000000..eedf2e0
--- /dev/null
+++ b/tests/cmd/pagination_categories_empty.md
@@ -0,0 +1,15 @@
+```console
+$ cobalt -v build --destination _dest
+DEBUG: Using config file `./_cobalt.yml`
+Building from `.` into `[CWD]/_dest`
+DEBUG: glob converted to regex: Glob { glob: "**/.*", re: "(?-u)^(?:/?|.*/)//.[^/]*$", opts: GlobOptions { case_insensitive: false, literal_separator: true, backslash_escape: true, empty_alternates: false }, tokens: Tokens([RecursivePrefix, Literal('.'), ZeroOrMore]) }
+DEBUG: glob converted to regex: Glob { glob: "**/_*", re: "(?-u)^(?:/?|.*/)_[^/]*$", opts: GlobOptions { case_insensitive: false, literal_separator: true, backslash_escape: true, empty_alternates: false }, tokens: Tokens([RecursivePrefix, Literal('_'), ZeroOrMore]) }
+DEBUG: built glob set; 5 literals, 0 basenames, 0 extensions, 0 prefixes, 0 suffixes, 0 required extensions, 2 regexes
+DEBUG: Loading data from `./_data`
+DEBUG: glob converted to regex: Glob { glob: "**/.*", re: "(?-u)^(?:/?|.*/)//.[^/]*$", opts: GlobOptions { case_insensitive: false, literal_separator: true, backslash_escape: true, empty_alternates: false }, tokens: Tokens([RecursivePrefix, Literal('.'), ZeroOrMore]) }
+DEBUG: glob converted to regex: Glob { glob: "**/_*", re: "(?-u)^(?:/?|.*/)_[^/]*$", opts: GlobOptions { case_insensitive: false, literal_separator: true, backslash_escape: true, empty_alternates: false }, tokens: Tokens([RecursivePrefix, Literal('_'), ZeroOrMore]) }
+DEBUG: built glob set; 0 literals, 0 basenames, 0 extensions, 0 prefixes, 0 suffixes, 0 required extensions, 2 regexes
+DEBUG: Loading snippets from `./_includes`
+Build successful
+
+```
diff --git a/tests/cmd/pagination_dates_empty.in/_cobalt.yml b/tests/cmd/pagination_dates_empty.in/_cobalt.yml
new file mode 100644
index 0000000..6c2e919
--- /dev/null
+++ b/tests/cmd/pagination_dates_empty.in/_cobalt.yml
@@ -0,0 +1,2 @@
+syntax_highlight:
+  enabled: false
diff --git a/tests/cmd/pagination_dates_empty.in/_dest/dates.html b/tests/cmd/pagination_dates_empty.in/_dest/dates.html
new file mode 100644
index 0000000..acc27da
--- /dev/null
+++ b/tests/cmd/pagination_dates_empty.in/_dest/dates.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>test</title>
+    </head>
+    <body>
+        <h1>dates.html</h1>
+
+        
+
+
+
+
+    </body>
+</html>
+
diff --git a/tests/cmd/pagination_dates_empty.in/_layouts/default.liquid b/tests/cmd/pagination_dates_empty.in/_layouts/default.liquid
new file mode 100644
index 0000000..426ae69
--- /dev/null
+++ b/tests/cmd/pagination_dates_empty.in/_layouts/default.liquid
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>test</title>
+    </head>
+    <body>
+        <h1>{{ page.permalink }}</h1>
+
+        {{ page.content }}
+    </body>
+</html>
+
diff --git a/tests/cmd/pagination_dates_empty.in/_layouts/posts.liquid b/tests/cmd/pagination_dates_empty.in/_layouts/posts.liquid
new file mode 100644
index 0000000..6a21c64
--- /dev/null
+++ b/tests/cmd/pagination_dates_empty.in/_layouts/posts.liquid
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>My blog - {{ page.title }}</title>
+    </head>
+    <body>
+        {{ page.content }}
+    </body>
+</html>
+
diff --git a/tests/cmd/pagination_dates_empty.in/index.liquid b/tests/cmd/pagination_dates_empty.in/index.liquid
new file mode 100644
index 0000000..3555b23
--- /dev/null
+++ b/tests/cmd/pagination_dates_empty.in/index.liquid
@@ -0,0 +1,44 @@
+---
+permalink: /dates.html
+layout: default.liquid
+pagination:
+  include: Dates
+  permalink_suffix: ./_p/{{num}}
+---
+{% if paginator.index_title %}
+  {% capture date_title %}{% for field in paginator.index_title %}{{ field | prepend: "-" }}{% endfor %}{% endcapture %}
+  {% capture date_clean %}{{ date_title | remove_first: "-" }}{% endcapture %}
+  <div>Date: {{ date_clean | date: "%Y/%B" }}</div>
+  {% if paginator.indexes %}
+    <div>Sub-dates:</div>
+  {% endif %}
+{% endif %}
+{% if paginator.indexes %}
+All years:
+<ul>
+  {% for pdate in paginator.indexes %}
+    <li><a href="/{{ pdate.index_permalink }}/">{{ pdate.index_title | last }} ({{ pdate.total_pages }})</a>
+  {% endfor %}
+</ul>
+{% endif %}
+
+{% if paginator.pages %}
+<div>Posts in this date:</div>
+<ul>
+  {% for page in paginator.pages %}
+  <li><a href="/{{ page.permalink }}">{{ page.title }}</a>
+    {% endfor %}
+</ul>
+<div>
+  {% if paginator.previous_index %}
+    <a href="{{ paginator.previous_index_permalink }}"
+    class="left arrow">&#8592;</a>
+  {% endif %}
+  {% if paginator.next_index %}
+    <a href="{{ paginator.next_index_permalink }}"
+    class="right arrow">&#8594;</a>
+  {% endif %}
+
+  <span>{{ paginator.index }} / {{ paginator.total_indexes }}</span>
+</div>
+{% endif %}
diff --git a/tests/cmd/pagination_dates_empty.md b/tests/cmd/pagination_dates_empty.md
new file mode 100644
index 0000000..eedf2e0
--- /dev/null
+++ b/tests/cmd/pagination_dates_empty.md
@@ -0,0 +1,15 @@
+```console
+$ cobalt -v build --destination _dest
+DEBUG: Using config file `./_cobalt.yml`
+Building from `.` into `[CWD]/_dest`
+DEBUG: glob converted to regex: Glob { glob: "**/.*", re: "(?-u)^(?:/?|.*/)//.[^/]*$", opts: GlobOptions { case_insensitive: false, literal_separator: true, backslash_escape: true, empty_alternates: false }, tokens: Tokens([RecursivePrefix, Literal('.'), ZeroOrMore]) }
+DEBUG: glob converted to regex: Glob { glob: "**/_*", re: "(?-u)^(?:/?|.*/)_[^/]*$", opts: GlobOptions { case_insensitive: false, literal_separator: true, backslash_escape: true, empty_alternates: false }, tokens: Tokens([RecursivePrefix, Literal('_'), ZeroOrMore]) }
+DEBUG: built glob set; 5 literals, 0 basenames, 0 extensions, 0 prefixes, 0 suffixes, 0 required extensions, 2 regexes
+DEBUG: Loading data from `./_data`
+DEBUG: glob converted to regex: Glob { glob: "**/.*", re: "(?-u)^(?:/?|.*/)//.[^/]*$", opts: GlobOptions { case_insensitive: false, literal_separator: true, backslash_escape: true, empty_alternates: false }, tokens: Tokens([RecursivePrefix, Literal('.'), ZeroOrMore]) }
+DEBUG: glob converted to regex: Glob { glob: "**/_*", re: "(?-u)^(?:/?|.*/)_[^/]*$", opts: GlobOptions { case_insensitive: false, literal_separator: true, backslash_escape: true, empty_alternates: false }, tokens: Tokens([RecursivePrefix, Literal('_'), ZeroOrMore]) }
+DEBUG: built glob set; 0 literals, 0 basenames, 0 extensions, 0 prefixes, 0 suffixes, 0 required extensions, 2 regexes
+DEBUG: Loading snippets from `./_includes`
+Build successful
+
+```
diff --git a/tests/cmd/pagination_tags_empty.in/_cobalt.yml b/tests/cmd/pagination_tags_empty.in/_cobalt.yml
new file mode 100644
index 0000000..6c2e919
--- /dev/null
+++ b/tests/cmd/pagination_tags_empty.in/_cobalt.yml
@@ -0,0 +1,2 @@
+syntax_highlight:
+  enabled: false
diff --git a/tests/cmd/pagination_tags_empty.in/_dest/tags/index.html b/tests/cmd/pagination_tags_empty.in/_dest/tags/index.html
new file mode 100644
index 0000000..ab47a0e
--- /dev/null
+++ b/tests/cmd/pagination_tags_empty.in/_dest/tags/index.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>test</title>
+    </head>
+    <body>
+        <h1>tags</h1>
+
+        
+<ul>
+  
+</ul>
+
+
+    </body>
+</html>
+
diff --git a/tests/cmd/pagination_tags_empty.in/_layouts/default.liquid b/tests/cmd/pagination_tags_empty.in/_layouts/default.liquid
new file mode 100644
index 0000000..426ae69
--- /dev/null
+++ b/tests/cmd/pagination_tags_empty.in/_layouts/default.liquid
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>test</title>
+    </head>
+    <body>
+        <h1>{{ page.permalink }}</h1>
+
+        {{ page.content }}
+    </body>
+</html>
+
diff --git a/tests/cmd/pagination_tags_empty.in/_layouts/posts.liquid b/tests/cmd/pagination_tags_empty.in/_layouts/posts.liquid
new file mode 100644
index 0000000..6a21c64
--- /dev/null
+++ b/tests/cmd/pagination_tags_empty.in/_layouts/posts.liquid
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>My blog - {{ page.title }}</title>
+    </head>
+    <body>
+        {{ page.content }}
+    </body>
+</html>
+
diff --git a/tests/cmd/pagination_tags_empty.in/index.liquid b/tests/cmd/pagination_tags_empty.in/index.liquid
new file mode 100644
index 0000000..9c5db52
--- /dev/null
+++ b/tests/cmd/pagination_tags_empty.in/index.liquid
@@ -0,0 +1,33 @@
+---
+permalink: /tags
+layout: default.liquid
+pagination:
+  include: Tags
+  permalink_suffix: _p/{{num}}
+  per_page: 2
+---
+{% if paginator.indexes %}
+<ul>
+  {% for ptag in paginator.indexes %}
+  <li><a href="/{{ ptag.index_permalink }}/">{{ ptag.index_title }} ({{ ptag.total_pages }})</a>
+    {% endfor %}
+</ul>
+{% else %}
+<ul>
+  {% for page in paginator.pages %}
+  <li><a href="/{{ page.permalink }}">{{ page.title }}</a>
+    {% endfor %}
+</ul>
+<div>
+  {% if paginator.previous_index %}
+    <a href="/{{ paginator.previous_index_permalink }}"
+    class="left arrow">&#8592;</a>
+  {% endif %}
+  {% if paginator.next_index %}
+    <a href="/{{ paginator.next_index_permalink }}"
+    class="right arrow">&#8594;</a>
+  {% endif %}
+
+  <span>{{ paginator.index }} / {{ paginator.total_indexes }}</span>
+</div>
+{% endif %}
diff --git a/tests/cmd/pagination_tags_empty.md b/tests/cmd/pagination_tags_empty.md
new file mode 100644
index 0000000..eedf2e0
--- /dev/null
+++ b/tests/cmd/pagination_tags_empty.md
@@ -0,0 +1,15 @@
+```console
+$ cobalt -v build --destination _dest
+DEBUG: Using config file `./_cobalt.yml`
+Building from `.` into `[CWD]/_dest`
+DEBUG: glob converted to regex: Glob { glob: "**/.*", re: "(?-u)^(?:/?|.*/)//.[^/]*$", opts: GlobOptions { case_insensitive: false, literal_separator: true, backslash_escape: true, empty_alternates: false }, tokens: Tokens([RecursivePrefix, Literal('.'), ZeroOrMore]) }
+DEBUG: glob converted to regex: Glob { glob: "**/_*", re: "(?-u)^(?:/?|.*/)_[^/]*$", opts: GlobOptions { case_insensitive: false, literal_separator: true, backslash_escape: true, empty_alternates: false }, tokens: Tokens([RecursivePrefix, Literal('_'), ZeroOrMore]) }
+DEBUG: built glob set; 5 literals, 0 basenames, 0 extensions, 0 prefixes, 0 suffixes, 0 required extensions, 2 regexes
+DEBUG: Loading data from `./_data`
+DEBUG: glob converted to regex: Glob { glob: "**/.*", re: "(?-u)^(?:/?|.*/)//.[^/]*$", opts: GlobOptions { case_insensitive: false, literal_separator: true, backslash_escape: true, empty_alternates: false }, tokens: Tokens([RecursivePrefix, Literal('.'), ZeroOrMore]) }
+DEBUG: glob converted to regex: Glob { glob: "**/_*", re: "(?-u)^(?:/?|.*/)_[^/]*$", opts: GlobOptions { case_insensitive: false, literal_separator: true, backslash_escape: true, empty_alternates: false }, tokens: Tokens([RecursivePrefix, Literal('_'), ZeroOrMore]) }
+DEBUG: built glob set; 0 literals, 0 basenames, 0 extensions, 0 prefixes, 0 suffixes, 0 required extensions, 2 regexes
+DEBUG: Loading snippets from `./_includes`
+Build successful
+
+```