support paginated history, branches, tags

This commit is contained in:
Trevor Bentley
2023-01-12 20:19:48 +01:00
parent c60b1d26a0
commit 273d62c645
9 changed files with 404 additions and 25 deletions
+76 -1
View File
@@ -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
-1
View File
@@ -9,7 +9,6 @@ use settings::{GitsyCli, GitsySettings};
// TODO:
//
// * pagination
// * basic, light, dark, and fancy default themes
// * better error propagation
// * automated tests
+54 -11
View File
@@ -70,8 +70,11 @@ pub struct GitsySettingsTemplates {
pub path: PathBuf,
pub repo_list: Option<String>,
pub repo_summary: Option<String>,
pub history: Option<String>,
pub commit: Option<String>,
pub branches: Option<String>,
pub branch: Option<String>,
pub tags: Option<String>,
pub tag: Option<String>,
pub file: Option<String>,
pub dir: Option<String>,
@@ -84,8 +87,11 @@ pub struct GitsySettingsOutputs {
pub cloned_repos: Option<String>,
pub repo_list: Option<String>,
pub repo_summary: Option<String>,
pub history: Option<String>,
pub commit: Option<String>,
pub branches: Option<String>,
pub branch: Option<String>,
pub tags: Option<String>,
pub tag: Option<String>,
pub file: Option<String>,
pub dir: Option<String>,
@@ -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<bool>,
pub syntax_highlight_theme: Option<String>,
pub attributes: Option<BTreeMap<String, toml::Value>>,
pub paginate_history: Option<usize>,
pub paginate_branches: Option<usize>,
pub paginate_tags: Option<usize>,
pub limit_history: Option<usize>,
pub limit_commits: Option<usize>,
pub limit_branches: Option<usize>,
@@ -201,6 +214,9 @@ pub struct GitsySettings {
pub templates: GitsySettingsTemplates,
#[serde(rename(deserialize = "gitsy_outputs"))]
pub outputs: GitsySettingsOutputs,
pub paginate_history: Option<usize>,
pub paginate_branches: Option<usize>,
pub paginate_tags: Option<usize>,
pub limit_history: Option<usize>,
pub limit_commits: Option<usize>,
pub limit_branches: Option<usize>,
@@ -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,
}
}
}
+96
View File
@@ -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<i64>, format: Option<String>) -> 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<String>,
pub prev_page: Option<String>,
}
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,
}
}
}