diff --git a/config.toml b/config.toml index b9035fc..bc59d03 100644 --- a/config.toml +++ b/config.toml @@ -46,6 +46,48 @@ # directory is used. #asset_files = [] +# Whether to split history output into pages +# +# If non-zero and a `history` template is specified, the history +# output is generated several times, each with the `history` variable +# in the template engine containing the next `paginate_history` number +# of entries. +# +# A `page` variable is available in the template, which contains the +# current, next, previous, and total number of pages. +# +# Use the "%PAGE%" variable in the `history` output variable to +# substitute the page number into the produced filename. +paginate_history = 50 + +# Whether to split branches output into pages +# +# If non-zero and a `branches` template is specified, the branches +# output is generated several times, each with the `branches` variable +# in the template engine containing the next `paginate_branches` +# number of entries. +# +# A `page` variable is available in the template, which contains the +# current, next, previous, and total number of pages. +# +# Use the "%PAGE%" variable in the `branches` output variable to +# substitute the page number into the produced filename. +paginate_branches = 50 + +# Whether to split tags output into pages +# +# If non-zero and a `tags` template is specified, the tags output is +# generated several times, each with the `tags` variable in the +# template engine containing the next `paginate_tags` number of +# entries. +# +# A `page` variable is available in the template, which contains the +# current, next, previous, and total number of pages. +# +# Use the "%PAGE%" variable in the `tags` output variable to +# substitute the page number into the produced filename. +paginate_tags = 50 + # Whether to render Markdown files in repos into HTML. # # Rendering Markdown can make site generation take more time. It has potential @@ -215,6 +257,18 @@ repo_list = "repos.html" # This template executes one time per repository. repo_summary = "summary.html" +# Template responsible for displaying the commit history. +# +# This template is evaluated with the same data as `repo_summary`. If +# the `paginate_history` setting is non-zero, this may be called +# several times with the `history` template variable reduced to the +# requested page size, and with a `page` template variable provided to +# identify the current, previous, and next pages. +# +# This template executes at least one time per repository, or several +# times if paginated. +history = "history.html" + # Template responsible for displaying a single commit. # # Called once per parsed commit, with both the whole repository and the current @@ -223,6 +277,18 @@ repo_summary = "summary.html" # This template executes many times. commit = "commit.html" +# Template responsible for displaying the repo branches. +# +# This template is evaluated with the same data as `repo_summary`. If +# the `paginate_branches` setting is non-zero, this may be called +# several times with the `branches` template variable reduced to the +# requested page size, and with a `page` template variable provided to +# identify the current, previous, and next pages. +# +# This template executes at least one time per repository, or several +# times if paginated. +branches = "branches.html" + # Template responsible for displaying a single branch. # # Called once per parsed branch, with both the whole repository and the current @@ -231,6 +297,18 @@ commit = "commit.html" # This template executes many times. branch = "branch.html" +# Template responsible for displaying the repo tags. +# +# This template is evaluated with the same data as `repo_summary`. If +# the `paginate_tags` setting is non-zero, this may be called several +# times with the `tags` template variable reduced to the requested +# page size, and with a `page` template variable provided to identify +# the current, previous, and next pages. +# +# This template executes at least one time per repository, or several +# times if paginated. +tags = "tags.html" + # Template responsible for displaying a single tag. # # Called once per parsed tag, with both the whole repository and the current @@ -279,6 +357,7 @@ error = "404.html" ## ## * "%REPO%" -- replaced with the name of the currently processing repository ## * "%ID%" -- replaced with the ID of the currently processing object +## * "%PAGE%" -- replaced with the current page number if output is paginated ## ## All except `path` are optional. If not specified, sensible defaults will be ## used. @@ -288,8 +367,11 @@ error = "404.html" path = "rendered/" repo_list = "repos.html" repo_summary = "%REPO%/summary.html" +history = "%REPO%/history%PAGE%.html" commit = "%REPO%/commit/%ID%.html" +branches = "%REPO%/branches%PAGE%.html" branch = "%REPO%/branch/%ID%.html" +tags = "%REPO%/tags%PAGE%.html" tag = "%REPO%/tag/%ID%.html" file = "%REPO%/file/%ID%.html" syntax_css = "%REPO%/file/syntax.css" @@ -366,18 +448,21 @@ generated_by = "Itsy-Gitsy" # Per-repository settings, same as the global versions described above: -#render_markdown = false -#syntax_highlighting = false +#render_markdown = false +#syntax_highlighting = false #syntax_highlight_theme = "base16-ocean.dark" -#limit_history = 500 -#limit_commits = 500 -#limit_branches = 500 -#limit_tags = 500 -#limit_tree_depth = 20 -#limit_file_size = 2097152 -#limit_repo_size = 52428800 -#limit_total_size = 524288000 -#asset_files = ["LICENSE"] +#paginate_history = 50 +#paginate_branches = 50 +#paginate_tags = 50 +#limit_history = 500 +#limit_commits = 500 +#limit_branches = 500 +#limit_tags = 500 +#limit_tree_depth = 20 +#limit_file_size = 2097152 +#limit_repo_size = 52428800 +#limit_total_size = 524288000 +#asset_files = ["LICENSE"] # An alternative way to specify the user-defined attributes. # diff --git a/src/generate.rs b/src/generate.rs index 966e3a5..e0c79a4 100644 --- a/src/generate.rs +++ b/src/generate.rs @@ -3,7 +3,7 @@ use crate::{ git::{dir_listing, parse_repo, GitFile, GitRepo, GitsyMetadata}, loud, louder, loudest, normal, normal_noln, settings::{GitsyCli, GitsyRepoDescriptions, GitsySettings, GitsySettingsRepo}, - template::{DirFilter, FileFilter, TsDateFn, TsTimestampFn}, + template::{DirFilter, FileFilter, Pagination, TsDateFn, TsTimestampFn}, util::GitsyError, }; use git2::{Error, Repository}; @@ -316,6 +316,31 @@ impl GitsyGenerator { } } + if let Some(templ_file) = self.settings.templates.branches.as_deref() { + let mut paged_ctx = local_ctx.clone(); + paged_ctx.remove("branches"); + let pages = summary + .branches + .chunks(self.settings.paginate_branches()); + let page_count = pages.len(); + for (idx, page) in pages.enumerate() { + let pagination = + Pagination::new(idx + 1, page_count, &self.settings.outputs.branches(Some(&summary), None)); + paged_ctx.insert("page", &pagination.with_relative_paths()); + paged_ctx.insert("branches", &page); + match tera.render(templ_file, &paged_ctx) { + Ok(rendered) => { + repo_bytes += GitsyGenerator::write_rendered(&pagination.cur_page, &rendered); + } + Err(x) => match x.kind { + _ => error!("ERROR: {:?}", x), + }, + } + paged_ctx.remove("page"); + paged_ctx.remove("branches"); + } + } + for branch in &summary.branches { size_check!(repo_desc, repo_bytes, total_bytes, break); local_ctx.insert("branch", branch); @@ -335,6 +360,31 @@ impl GitsyGenerator { local_ctx.remove("branch"); } + if let Some(templ_file) = self.settings.templates.tags.as_deref() { + let mut paged_ctx = local_ctx.clone(); + paged_ctx.remove("tags"); + let pages = summary + .tags + .chunks(self.settings.paginate_tags()); + let page_count = pages.len(); + for (idx, page) in pages.enumerate() { + let pagination = + Pagination::new(idx + 1, page_count, &self.settings.outputs.tags(Some(&summary), None)); + paged_ctx.insert("page", &pagination.with_relative_paths()); + paged_ctx.insert("tags", &page); + match tera.render(templ_file, &paged_ctx) { + Ok(rendered) => { + repo_bytes += GitsyGenerator::write_rendered(&pagination.cur_page, &rendered); + } + Err(x) => match x.kind { + _ => error!("ERROR: {:?}", x), + }, + } + paged_ctx.remove("page"); + paged_ctx.remove("tags"); + } + } + for tag in &summary.tags { size_check!(repo_desc, repo_bytes, total_bytes, break); local_ctx.insert("tag", tag); @@ -360,6 +410,31 @@ impl GitsyGenerator { local_ctx.remove("commit"); } + if let Some(templ_file) = self.settings.templates.history.as_deref() { + let mut paged_ctx = local_ctx.clone(); + paged_ctx.remove("history"); + let pages = summary + .history + .chunks(self.settings.paginate_history()); + let page_count = pages.len(); + for (idx, page) in pages.enumerate() { + let pagination = + Pagination::new(idx + 1, page_count, &self.settings.outputs.history(Some(&summary), None)); + paged_ctx.insert("page", &pagination.with_relative_paths()); + paged_ctx.insert("history", &page); + match tera.render(templ_file, &paged_ctx) { + Ok(rendered) => { + repo_bytes += GitsyGenerator::write_rendered(&pagination.cur_page, &rendered); + } + Err(x) => match x.kind { + _ => error!("ERROR: {:?}", x), + }, + } + paged_ctx.remove("page"); + paged_ctx.remove("history"); + } + } + for (_id, commit) in &summary.commits { size_check!(repo_desc, repo_bytes, total_bytes, break); local_ctx diff --git a/src/main.rs b/src/main.rs index 4bdac3e..7f2659e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,6 @@ use settings::{GitsyCli, GitsySettings}; // TODO: // -// * pagination // * basic, light, dark, and fancy default themes // * better error propagation // * automated tests diff --git a/src/settings.rs b/src/settings.rs index 09a34c9..c2df4fc 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -70,8 +70,11 @@ pub struct GitsySettingsTemplates { pub path: PathBuf, pub repo_list: Option, pub repo_summary: Option, + pub history: Option, pub commit: Option, + pub branches: Option, pub branch: Option, + pub tags: Option, pub tag: Option, pub file: Option, pub dir: Option, @@ -84,8 +87,11 @@ pub struct GitsySettingsOutputs { pub cloned_repos: Option, pub repo_list: Option, pub repo_summary: Option, + pub history: Option, pub commit: Option, + pub branches: Option, pub branch: Option, + pub tags: Option, pub tag: Option, pub file: Option, pub dir: Option, @@ -140,18 +146,22 @@ macro_rules! output_path_fn { } //step_map_first!(boil_in_wort, Boil, Wort, |b: &Boil| { b.wort_start() }); +#[rustfmt::skip] impl GitsySettingsOutputs { - output_path_fn!(repo_list, GitObject, full_hash, false, "repos.html"); - output_path_fn!(repo_summary, GitObject, full_hash, false, "%REPO%/summary.html"); - output_path_fn!(commit, GitObject, full_hash, false, "%REPO%/commit/%ID%.html"); - output_path_fn!(branch, GitObject, full_hash, false, "%REPO%/branch/%ID%.html"); - output_path_fn!(tag, GitObject, full_hash, false, "%REPO%/tag/%ID%.html"); - output_path_fn!(file, GitFile, id, false, "%REPO%/file/%ID%.html"); - output_path_fn!(syntax_css, GitObject, full_hash, false, "%REPO%/file/syntax.css"); - output_path_fn!(dir, GitFile, id, false, "%REPO%/dir/%ID%.html"); - output_path_fn!(error, GitObject, full_hash, false, "404.html"); - output_path_fn!(global_assets, GitObject, full_hash, true, "assets/"); - output_path_fn!(repo_assets, GitObject, full_hash, true, "%REPO%/assets/"); + output_path_fn!(repo_list, GitObject, full_hash, false, "repos.html"); + output_path_fn!(repo_summary, GitObject, full_hash, false, "%REPO%/summary.html"); + output_path_fn!(history, GitObject, full_hash, false, "%REPO%/history%PAGE%.html"); + output_path_fn!(commit, GitObject, full_hash, false, "%REPO%/commit/%ID%.html"); + output_path_fn!(branches, GitObject, full_hash, false, "%REPO%/branches%PAGE%.html"); + output_path_fn!(branch, GitObject, full_hash, false, "%REPO%/branch/%ID%.html"); + output_path_fn!(tags, GitObject, full_hash, false, "%REPO%/tags%PAGE%.html"); + output_path_fn!(tag, GitObject, full_hash, false, "%REPO%/tag/%ID%.html"); + output_path_fn!(file, GitFile, id, false, "%REPO%/file/%ID%.html"); + output_path_fn!(syntax_css, GitObject, full_hash, false, "%REPO%/file/syntax.css"); + output_path_fn!(dir, GitFile, id, false, "%REPO%/dir/%ID%.html"); + output_path_fn!(error, GitObject, full_hash, false, "404.html"); + output_path_fn!(global_assets, GitObject, full_hash, true, "assets/"); + output_path_fn!(repo_assets, GitObject, full_hash, true, "%REPO%/assets/"); } #[derive(Clone, Deserialize, Default, Debug)] @@ -165,6 +175,9 @@ pub struct GitsySettingsRepo { pub syntax_highlight: Option, pub syntax_highlight_theme: Option, pub attributes: Option>, + pub paginate_history: Option, + pub paginate_branches: Option, + pub paginate_tags: Option, pub limit_history: Option, pub limit_commits: Option, pub limit_branches: Option, @@ -201,6 +214,9 @@ pub struct GitsySettings { pub templates: GitsySettingsTemplates, #[serde(rename(deserialize = "gitsy_outputs"))] pub outputs: GitsySettingsOutputs, + pub paginate_history: Option, + pub paginate_branches: Option, + pub paginate_tags: Option, pub limit_history: Option, pub limit_commits: Option, pub limit_branches: Option, @@ -265,6 +281,9 @@ impl GitsySettings { global_to_repo!(settings, repo, render_markdown); global_to_repo!(settings, repo, syntax_highlight); global_to_repo!(settings, repo, syntax_highlight_theme); + global_to_repo!(settings, repo, paginate_history); + global_to_repo!(settings, repo, paginate_branches); + global_to_repo!(settings, repo, paginate_tags); global_to_repo!(settings, repo, limit_history); global_to_repo!(settings, repo, limit_commits); global_to_repo!(settings, repo, limit_branches); @@ -294,6 +313,9 @@ impl GitsySettings { render_markdown: settings.render_markdown.clone(), syntax_highlight: settings.syntax_highlight.clone(), syntax_highlight_theme: settings.syntax_highlight_theme.clone(), + paginate_history: settings.paginate_history.clone(), + paginate_branches: settings.paginate_branches.clone(), + paginate_tags: settings.paginate_tags.clone(), limit_history: settings.limit_history.clone(), limit_commits: settings.limit_commits.clone(), limit_branches: settings.limit_branches.clone(), @@ -311,4 +333,25 @@ impl GitsySettings { } (settings, repo_descriptions) } + + pub fn paginate_history(&self) -> usize { + match self.paginate_history.unwrap_or(usize::MAX) { + x if x == 0 => usize::MAX, + x => x, + } + } + + pub fn paginate_branches(&self) -> usize { + match self.paginate_branches.unwrap_or(usize::MAX) { + x if x == 0 => usize::MAX, + x => x, + } + } + + pub fn paginate_tags(&self) -> usize { + match self.paginate_tags.unwrap_or(usize::MAX) { + x if x == 0 => usize::MAX, + x => x, + } + } } diff --git a/src/template.rs b/src/template.rs index 48efbb4..64d0232 100644 --- a/src/template.rs +++ b/src/template.rs @@ -1,6 +1,8 @@ use crate::git::GitFile; use chrono::{naive::NaiveDateTime, offset::FixedOffset, DateTime}; +use serde::Serialize; use std::collections::HashMap; +use std::path::PathBuf; use tera::{to_value, try_get_value, Filter, Function, Value}; fn ts_to_date(ts: i64, offset: Option, format: Option) -> String { @@ -105,3 +107,97 @@ impl Function for TsTimestampFn { Ok(to_value(ts_to_git_timestamp(ts, tz)).unwrap()) } } + +#[derive(Serialize)] +pub struct Pagination { + pub pages: usize, + pub page_idx: usize, + pub page_str: String, + pub cur_page: String, + pub next_page: Option, + pub prev_page: Option, +} +impl Pagination { + pub fn new(cur: usize, total: usize, url_template: &str) -> Self { + let digits = total.to_string().len().max(2); + let next = match cur + 1 <= total { + true => Some(cur + 1), + false => None, + }; + let prev = match cur <= 1 { + true => None, + false => Some(cur - 1), + }; + let cur_str = match cur <= 1 { + true => String::new(), + false => format!("{:0w$}", cur, w = digits), + }; + let next_str = match next.unwrap_or(0) <= 1 { + true => String::new(), + false => format!("{:0w$}", next.unwrap_or(0), w = digits), + }; + let prev_str = match prev.unwrap_or(0) <= 1 { + true => String::new(), + false => format!("{:0w$}", prev.unwrap_or(0), w = digits), + }; + let cur_page = url_template.replace("%PAGE%", &cur_str); + let next_page = match next { + Some(_) => Some(url_template.replace("%PAGE%", &next_str)), + _ => None, + }; + let prev_page = match prev { + Some(_) => Some(url_template.replace("%PAGE%", &prev_str)), + _ => None, + }; + Pagination { + pages: total, + page_idx: cur, + page_str: cur_str.clone(), + cur_page, + next_page, + prev_page, + } + } + + pub fn with_relative_paths(&self) -> Self { + let cur_page = { + let path = PathBuf::from(&self.cur_page); + path.file_name() + .expect(&format!("Invalid output filename: {}", self.cur_page)) + .to_string_lossy() + .to_string() + }; + let next_page = match &self.next_page { + Some(p) => { + let path = PathBuf::from(p); + Some( + path.file_name() + .expect(&format!("Invalid output filename: {}", p)) + .to_string_lossy() + .to_string(), + ) + } + _ => None, + }; + let prev_page = match &self.prev_page { + Some(p) => { + let path = PathBuf::from(p); + Some( + path.file_name() + .expect(&format!("Invalid output filename: {}", p)) + .to_string_lossy() + .to_string(), + ) + } + _ => None, + }; + Pagination { + pages: self.pages, + page_idx: self.page_idx, + page_str: self.page_str.clone(), + cur_page, + next_page, + prev_page, + } + } +} diff --git a/templates/branches.html b/templates/branches.html new file mode 100644 index 0000000..db98fea --- /dev/null +++ b/templates/branches.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} + +{% block content %} + + + + + + + + + {% for entry in branches -%} + + + + + + + + {% endfor -%} +
BranchCommit IDMessageAuthorDate
{{entry.ref_name}}{{entry.short_hash}}{{entry.summary}}{{entry.author.name}}{{ts_to_date(ts=entry.ts_utc, tz=entry.ts_offset)}}
+{% if page.prev_page %}PREV{% endif %}       CUR       {%if page.next_page %}NEXT{% endif %}
+{% endblock content %} diff --git a/templates/history.html b/templates/history.html new file mode 100644 index 0000000..5b950cd --- /dev/null +++ b/templates/history.html @@ -0,0 +1,27 @@ +{% extends "base.html" %} + +{% block content %} + + + + + + + + + + {% for entry in history -%} + {% if loop.index0 < 250 -%} + + + + + + + + +{% endif -%} +{% endfor -%} +
Commit IDMessageAuthorDateDiffRefs
{{entry.short_hash}}{{entry.summary}}{{entry.author.name}}{{ts_to_date(ts=entry.ts_utc, tz=entry.ts_offset)}}{{entry.stats.files}} (+{{entry.stats.additions}}/-{{entry.stats.deletions}}){%- for ref in entry.alt_refs -%}{%- if loop.index0 > 0 -%},  {%- endif -%}{{ref}}{%- endfor -%}
+{% if page.prev_page %}PREV{% endif %}       CUR       {%if page.next_page %}NEXT{% endif %}
+{% endblock content %} diff --git a/templates/summary.html b/templates/summary.html index 4d81e08..e016439 100644 --- a/templates/summary.html +++ b/templates/summary.html @@ -1,6 +1,10 @@ {% extends "base.html" %} {% block content %} +Commit history
+All branches
+All tags
+
@@ -11,7 +15,7 @@ {% for entry in history -%} - {% if loop.index0 < 250 -%} + {% if loop.index0 < 10 -%} @@ -33,6 +37,7 @@ {% for entry in branches -%} + {% if loop.index0 < 10 -%} @@ -40,6 +45,7 @@ + {% endif -%} {% endfor -%}
Commit IDRefs
{{entry.short_hash}} {{entry.summary}}Date
{{entry.ref_name}} {{entry.short_hash}}{{entry.author.name}} {{ts_to_date(ts=entry.ts_utc, tz=entry.ts_offset)}}
@@ -52,6 +58,7 @@ Date {% for entry in tags -%} + {% if loop.index0 < 10 -%} {{entry.ref_name}} {{entry.short_hash}} @@ -59,6 +66,7 @@ {{entry.author.name}} {{ts_to_date(ts=entry.ts_utc, tz=entry.ts_offset)}} + {% endif -%} {% endfor -%} diff --git a/templates/tags.html b/templates/tags.html new file mode 100644 index 0000000..f846bcb --- /dev/null +++ b/templates/tags.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} + +{% block content %} + + + + + + + + + {% for entry in tags -%} + + + + + + + + {% endfor -%} +
TagCommit IDMessageAuthorDate
{{entry.ref_name}}{{entry.short_hash}}{{entry.summary}}{{entry.author.name}}{{ts_to_date(ts=entry.ts_utc, tz=entry.ts_offset)}}
+{% if page.prev_page %}PREV{% endif %}       CUR       {%if page.next_page %}NEXT{% endif %}
+{% endblock content %}