From 0966bb534f5997a62dc3b9f3f6ca0b5bc1931c5f Mon Sep 17 00:00:00 2001 From: Trevor Bentley Date: Fri, 13 Jan 2023 04:37:40 +0100 Subject: [PATCH] add a files output template --- config.toml | 11 ++++++++++- src/generate.rs | 25 +++++++++++++++++++++++-- src/main.rs | 1 + src/settings.rs | 10 ++++++++++ src/template.rs | 42 +++++++++++++++++++++++++++++++++++++++++- 5 files changed, 85 insertions(+), 4 deletions(-) diff --git a/config.toml b/config.toml index bc59d03..68c111e 100644 --- a/config.toml +++ b/config.toml @@ -115,7 +115,7 @@ syntax_highlight = true # - `base16-ocean.dark` # - `base16-eighties.dark` # - `base16-mocha.dark` -# - `InspiredGithub` +# - `InspiredGitHub` # - `Solarized (dark)` # - `Solarized (light)` # @@ -317,6 +317,14 @@ tags = "tags.html" # This template executes many times. tag = "tag.html" +# Template responsible for displaying file tree. +# +# Called with the same variables as the `summary` page, this simply gives an +# alternative locate to list the files in the root of the repository. +# +# This template executes once per repository. +files = "files.html" + # Template responsible for displaying a single file. # # Called once per parsed file, with both the whole repository and the current @@ -373,6 +381,7 @@ branches = "%REPO%/branches%PAGE%.html" branch = "%REPO%/branch/%ID%.html" tags = "%REPO%/tags%PAGE%.html" tag = "%REPO%/tag/%ID%.html" +files = "%REPO%/files.html" file = "%REPO%/file/%ID%.html" syntax_css = "%REPO%/file/syntax.css" dir = "%REPO%/dir/%ID%.html" diff --git a/src/generate.rs b/src/generate.rs index 04a1e2b..aa12ff7 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, Pagination, TsDateFn, TsTimestampFn}, + template::{DirFilter, FileFilter, HexFilter, MaskFilter, OctFilter, Pagination, TsDateFn, TsTimestampFn}, util::GitsyError, }; use git2::{Error, Repository}; @@ -169,6 +169,9 @@ impl GitsyGenerator { let mut tera = Tera::new(&template_path.to_string_lossy().to_string())?; tera.register_filter("only_files", FileFilter {}); tera.register_filter("only_dirs", DirFilter {}); + tera.register_filter("hex", HexFilter {}); + tera.register_filter("oct", OctFilter {}); + tera.register_filter("mask", MaskFilter {}); tera.register_function("ts_to_date", TsDateFn {}); tera.register_function("ts_to_git_timestamp", TsTimestampFn {}); Ok(tera) @@ -330,6 +333,8 @@ impl GitsyGenerator { if let Some(site_description) = &self.settings.site_description { local_ctx.insert("site_description", site_description); } + local_ctx.insert("site_dir", &self.settings.outputs.output_dir()); + local_ctx.insert("site_assets", &self.settings.outputs.global_assets(None, None)); local_ctx.insert("site_generated_ts", &generated_dt.timestamp()); local_ctx.insert("site_generated_offset", &generated_dt.offset().local_minus_utc()); @@ -488,7 +493,7 @@ impl GitsyGenerator { repo_desc .syntax_highlight_theme .as_deref() - .unwrap_or("base16-ocean.light"), + .unwrap_or("base16-ocean.dark"), ) .expect("Invalid syntax highlighting theme specified."); let css: String = css_for_theme_with_class_style(theme, syntect::html::ClassStyle::Spaced) @@ -549,6 +554,7 @@ impl GitsyGenerator { continue; } let listing = dir_listing(&repo, &dir).expect("Failed to parse file."); + local_ctx.insert("dir", dir); local_ctx .try_insert("files", &listing) .expect("Failed to add dir to template engine."); @@ -564,6 +570,19 @@ impl GitsyGenerator { } } local_ctx.remove("files"); + local_ctx.remove("dir"); + } + + if let Some(templ_file) = self.settings.templates.files.as_deref() { + match tera.render(templ_file, &local_ctx) { + Ok(rendered) => { + repo_bytes += + self.write_rendered(&self.settings.outputs.files(Some(&summary), None), &rendered); + } + Err(x) => match x.kind { + _ => error!("ERROR: {:?}", x), + }, + } } if repo_desc.asset_files.is_some() { @@ -625,6 +644,8 @@ impl GitsyGenerator { if let Some(site_description) = &self.settings.site_description { global_ctx.insert("site_description", site_description); } + global_ctx.insert("site_dir", &self.settings.outputs.output_dir()); + global_ctx.insert("site_assets", &self.settings.outputs.global_assets(None, None)); global_ctx.insert("site_generated_ts", &generated_dt.timestamp()); global_ctx.insert("site_generated_offset", &generated_dt.offset().local_minus_utc()); diff --git a/src/main.rs b/src/main.rs index 7f2659e..984fcb6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ use settings::{GitsyCli, GitsySettings}; // TODO: // // * basic, light, dark, and fancy default themes +// * specify default branch, use instead of HEAD // * better error propagation // * automated tests // * documentation + examples diff --git a/src/settings.rs b/src/settings.rs index 9c11833..4e873ff 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -76,6 +76,7 @@ pub struct GitsySettingsTemplates { pub branch: Option, pub tags: Option, pub tag: Option, + pub files: Option, pub file: Option, pub dir: Option, pub error: Option, @@ -93,6 +94,7 @@ pub struct GitsySettingsOutputs { pub branch: Option, pub tags: Option, pub tag: Option, + pub files: Option, pub file: Option, pub dir: Option, pub error: Option, @@ -159,12 +161,20 @@ impl GitsySettingsOutputs { 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!(files, GitObject, full_hash, false, "%REPO%/files.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/"); + + pub fn output_dir(&self) -> String { + self.path.clone().canonicalize() + .expect(&format!("ERROR: unable to canonicalize output path: {}", self.path.display())) + .to_str().expect(&format!("ERROR: unable to parse output path: {}", self.path.display())) + .to_string() + } } #[derive(Clone, Deserialize, Default, Debug)] diff --git a/src/template.rs b/src/template.rs index 64d0232..5b59865 100644 --- a/src/template.rs +++ b/src/template.rs @@ -3,7 +3,7 @@ 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}; +use tera::{from_value, to_value, try_get_value, Filter, Function, Value}; fn ts_to_date(ts: i64, offset: Option, format: Option) -> String { let offset = offset.unwrap_or(0); @@ -54,6 +54,46 @@ impl Filter for DirFilter { } } +pub struct HexFilter; +impl Filter for HexFilter { + fn filter(&self, value: &Value, _args: &HashMap) -> Result { + let v: i64 = try_get_value!("hex", "value", i64, value); + Ok(to_value(format!("{:x}", v)).unwrap()) + } +} + +pub struct OctFilter; +impl Filter for OctFilter { + fn filter(&self, value: &Value, _args: &HashMap) -> Result { + let v: i64 = try_get_value!("oct", "value", i64, value); + Ok(to_value(format!("{:o}", v)).unwrap()) + } +} + +pub struct MaskFilter; +impl Filter for MaskFilter { + fn filter(&self, value: &Value, args: &HashMap) -> Result { + let v: u64 = try_get_value!("mask", "value", u64, value); + let mask: String = from_value( + args.get("mask") + .expect("ERROR: Tera mask filter called without `mask` parameter.") + .clone(), + ) + .expect("ERROR: Tera `mask` parameter is not valid."); + let mask: u64 = match mask.starts_with("0x") { + true => { + let hexstr = mask.strip_prefix("0x").unwrap(); + u64::from_str_radix(hexstr, 16) + .expect("ERROR: Tera `mask` parameter is invalid hex.") + }, + false => { + str::parse::(&mask).expect("ERROR: Tera `mask` parameter is not valid.") + }, + }; + Ok(to_value(v & mask).unwrap()) + } +} + pub struct TsDateFn; impl Function for TsDateFn { fn call(&self, args: &HashMap) -> Result {