support paginated history, branches, tags
This commit is contained in:
+76
-1
@@ -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
|
||||
|
||||
@@ -9,7 +9,6 @@ use settings::{GitsyCli, GitsySettings};
|
||||
|
||||
// TODO:
|
||||
//
|
||||
// * pagination
|
||||
// * basic, light, dark, and fancy default themes
|
||||
// * better error propagation
|
||||
// * automated tests
|
||||
|
||||
+54
-11
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user