apply rustfmt. ugh.

This commit is contained in:
Trevor Bentley
2023-01-12 17:45:49 +01:00
parent 65982202a1
commit b84065e565
5 changed files with 417 additions and 288 deletions
+1
View File
@@ -0,0 +1 @@
max_width = 120
+175 -97
View File
@@ -1,30 +1,15 @@
use crate::{ use crate::{
error, normal, normal_noln, loud, louder, loudest, error,
git::{ git::{dir_listing, parse_repo, GitFile, GitRepo, GitsyMetadata},
dir_listing, loud, louder, loudest, normal, normal_noln,
GitFile, settings::{GitsyCli, GitsyRepoDescriptions, GitsySettings, GitsySettingsRepo},
GitRepo, template::{DirFilter, FileFilter, TsDateFn, TsTimestampFn},
GitsyMetadata,
parse_repo,
},
settings::{
GitsyCli,
GitsyRepoDescriptions,
GitsySettings,
GitsySettingsRepo,
},
template::{
DirFilter,
FileFilter,
TsDateFn,
TsTimestampFn,
},
util::GitsyError, util::GitsyError,
}; };
use git2::{Repository, Error}; use git2::{Error, Repository};
use rayon::prelude::*; use rayon::prelude::*;
use std::cmp; use std::cmp;
use std::fs::{File, create_dir}; use std::fs::{create_dir, File};
use std::io::Write; use std::io::Write;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
@@ -36,9 +21,9 @@ use pulldown_cmark::{html, Options, Parser as MdParser};
#[cfg(any(feature = "highlight", feature = "highlight_fast"))] #[cfg(any(feature = "highlight", feature = "highlight_fast"))]
use syntect::{ use syntect::{
html::{ClassedHTMLGenerator, ClassStyle, css_for_theme_with_class_style},
parsing::SyntaxSet,
highlighting::ThemeSet, highlighting::ThemeSet,
html::{css_for_theme_with_class_style, ClassStyle, ClassedHTMLGenerator},
parsing::SyntaxSet,
util::LinesWithEndings, util::LinesWithEndings,
}; };
@@ -52,7 +37,7 @@ macro_rules! size_check {
if total.saturating_add($cur) > $settings.limit_total_size.unwrap_or(usize::MAX) { if total.saturating_add($cur) > $settings.limit_total_size.unwrap_or(usize::MAX) {
$action; $action;
} }
} };
} }
pub struct GitsyGenerator { pub struct GitsyGenerator {
@@ -62,8 +47,7 @@ pub struct GitsyGenerator {
} }
impl GitsyGenerator { impl GitsyGenerator {
pub fn new(cli: GitsyCli, settings: GitsySettings, pub fn new(cli: GitsyCli, settings: GitsySettings, repo_descriptions: GitsyRepoDescriptions) -> GitsyGenerator {
repo_descriptions: GitsyRepoDescriptions) -> GitsyGenerator {
GitsyGenerator { GitsyGenerator {
cli, cli,
settings, settings,
@@ -86,16 +70,18 @@ impl GitsyGenerator {
let syntax_set = SyntaxSet::load_defaults_newlines(); let syntax_set = SyntaxSet::load_defaults_newlines();
let syntax = match syntax_set.find_syntax_by_extension(extension) { let syntax = match syntax_set.find_syntax_by_extension(extension) {
Some(s) => s, Some(s) => s,
_ => { return contents.to_string(); }, _ => {
return contents.to_string();
}
}; };
let mut html_generator = ClassedHTMLGenerator::new_with_class_style(syntax, &syntax_set, ClassStyle::Spaced); let mut html_generator = ClassedHTMLGenerator::new_with_class_style(syntax, &syntax_set, ClassStyle::Spaced);
for line in LinesWithEndings::from(contents) { for line in LinesWithEndings::from(contents) {
match html_generator.parse_html_for_line_which_includes_newline(line) { match html_generator.parse_html_for_line_which_includes_newline(line) {
Ok(_) => {}, Ok(_) => {}
Err(_) => { Err(_) => {
error!("Warning: failed to apply syntax highlighting."); error!("Warning: failed to apply syntax highlighting.");
return contents.to_string(); return contents.to_string();
}, }
} }
} }
html_generator.finalize() html_generator.finalize()
@@ -115,18 +101,22 @@ impl GitsyGenerator {
loudest!(" - rendering Markdown in {}", path.display()); loudest!(" - rendering Markdown in {}", path.display());
let (cstr, rendered, pre) = (GitsyGenerator::parse_markdown(&cstr), true, false); let (cstr, rendered, pre) = (GitsyGenerator::parse_markdown(&cstr), true, false);
(cstr, rendered, pre) (cstr, rendered, pre)
}, }
#[cfg(any(feature = "highlight", feature = "highlight_fast"))] #[cfg(any(feature = "highlight", feature = "highlight_fast"))]
Some(x) if settings.syntax_highlight.unwrap_or(false) => { Some(x) if settings.syntax_highlight.unwrap_or(false) => {
loudest!(" - syntax highlighting {}", path.display()); loudest!(" - syntax highlighting {}", path.display());
(GitsyGenerator::syntax_highlight(&cstr, x.to_string_lossy().to_string().as_str()), true, true) (
}, GitsyGenerator::syntax_highlight(&cstr, x.to_string_lossy().to_string().as_str()),
true,
true,
)
}
_ => (cstr, false, true), _ => (cstr, false, true),
}; };
file.contents_safe = rendered; file.contents_safe = rendered;
file.contents_preformatted = pre; file.contents_preformatted = pre;
Some(content) Some(content)
}, }
true => Some(format!("[Binary data ({} bytes)]", blob.content().len())), true => Some(format!("[Binary data ({} bytes)]", blob.content().len())),
}; };
} }
@@ -134,8 +124,7 @@ impl GitsyGenerator {
} }
fn write_rendered(path: &str, rendered: &str) -> usize { fn write_rendered(path: &str, rendered: &str) -> usize {
let mut file = File::create(path) let mut file = File::create(path).expect(&format!("Unable to write to output path: {}", path));
.expect(&format!("Unable to write to output path: {}", path));
file.write(rendered.as_bytes()) file.write(rendered.as_bytes())
.expect(&format!("Failed to save rendered html to path: {}", path)); .expect(&format!("Failed to save rendered html to path: {}", path));
louder!(" - wrote file: {}", path); louder!(" - wrote file: {}", path);
@@ -164,22 +153,31 @@ impl GitsyGenerator {
let generated_dt = chrono::offset::Local::now(); let generated_dt = chrono::offset::Local::now();
let mut global_bytes = 0; let mut global_bytes = 0;
let mut total_bytes = 0; let mut total_bytes = 0;
let mut repos: Vec<GitRepo> = vec!(); let mut repos: Vec<GitRepo> = vec![];
if self.repo_descriptions.len() == 0 { if self.repo_descriptions.len() == 0 {
panic!("No Git repositories defined! Please check your configuration file ({})", panic!(
self.cli.path.display()); "No Git repositories defined! Please check your configuration file ({})",
self.cli.path.display()
);
} }
// Sort the repositories by name // Sort the repositories by name
let mut repo_vec: Vec<GitsySettingsRepo> = self.repo_descriptions.iter().cloned().collect(); let mut repo_vec: Vec<GitsySettingsRepo> = self.repo_descriptions.iter().cloned().collect();
repo_vec.sort_by(|x,y| x.name.as_deref().map(|n| n.cmp(&y.name.as_deref().unwrap_or_default())) repo_vec.sort_by(|x, y| {
.unwrap_or(cmp::Ordering::Equal)); x.name
.as_deref()
.map(|n| n.cmp(&y.name.as_deref().unwrap_or_default()))
.unwrap_or(cmp::Ordering::Equal)
});
// Find the one with the longest name, for pretty printing // Find the one with the longest name, for pretty printing
let global_name = "repo list"; let global_name = "repo list";
let longest_repo_name = repo_vec.iter().fold(0, |acc, x| { let longest_repo_name = repo_vec
.iter()
.fold(0, |acc, x| {
cmp::max(acc, x.name.as_deref().map(|n| n.len()).unwrap_or(0)) cmp::max(acc, x.name.as_deref().map(|n| n.len()).unwrap_or(0))
}).max(global_name.len()); })
.max(global_name.len());
loudest!("Global settings:\n{:#?}", &self.settings); loudest!("Global settings:\n{:#?}", &self.settings);
@@ -191,29 +189,36 @@ impl GitsyGenerator {
let name = repo_desc.name.as_deref().expect("A configured repository has no name!"); let name = repo_desc.name.as_deref().expect("A configured repository has no name!");
let repo_path = match &repo_desc.path { let repo_path = match &repo_desc.path {
url if url.starts_with("https://") || url if url.starts_with("https://") || url.to_str().unwrap_or_default().contains("@") => {
url.to_str().unwrap_or_default().contains("@") => {
if self.settings.outputs.cloned_repos.is_none() { if self.settings.outputs.cloned_repos.is_none() {
error!("ERROR: Found remote repo [{}], but `cloned_repos` directory not configured.", name); error!(
"ERROR: Found remote repo [{}], but `cloned_repos` directory not configured.",
name
);
continue; continue;
}; };
let clone_path: PathBuf = [self.settings.outputs.cloned_repos.as_deref().unwrap(), let clone_path: PathBuf = [self.settings.outputs.cloned_repos.as_deref().unwrap(), name]
name].iter().collect(); .iter()
.collect();
match Repository::open(&clone_path) { match Repository::open(&clone_path) {
Ok(r) => { Ok(r) => {
// Repo already cloned, so update all refs // Repo already cloned, so update all refs
let refs: Vec<String> = r.references() let refs: Vec<String> = r
.references()
.expect(&format!("Unable to enumerate references for repo [{}]", name)) .expect(&format!("Unable to enumerate references for repo [{}]", name))
.map(|x| x.expect(&format!("Found invalid reference in repo [{}]", name)) .map(|x| {
x.expect(&format!("Found invalid reference in repo [{}]", name))
.name() .name()
.expect(&format!("Found unnamed reference in repo: [{}]", name)) .expect(&format!("Found unnamed reference in repo: [{}]", name))
.to_string()).collect(); .to_string()
})
.collect();
r.find_remote("origin") r.find_remote("origin")
.expect(&format!("Clone of repo [{}] missing `origin` remote.", name)) .expect(&format!("Clone of repo [{}] missing `origin` remote.", name))
.fetch(&refs, None, None) .fetch(&refs, None, None)
.expect(&format!("Failed to fetch updates from remote repo [{}]", name)); .expect(&format!("Failed to fetch updates from remote repo [{}]", name));
clone_path.to_string_lossy().to_string() clone_path.to_string_lossy().to_string()
}, }
Err(_) => { Err(_) => {
let mut builder = git2::build::RepoBuilder::new(); let mut builder = git2::build::RepoBuilder::new();
@@ -255,14 +260,18 @@ impl GitsyGenerator {
} }
dir => { dir => {
match dir.metadata() { match dir.metadata() {
Ok(m) if m.is_dir() => {}, Ok(m) if m.is_dir() => {}
_ => { _ => {
error!("ERROR: local repository [{}]: directory not found: {}", name, dir.display()); error!(
"ERROR: local repository [{}]: directory not found: {}",
name,
dir.display()
);
continue; continue;
}, }
} }
dir.to_string_lossy().to_string() dir.to_string_lossy().to_string()
}, }
}; };
let repo = Repository::open(&repo_path).expect("Unable to find git repository."); let repo = Repository::open(&repo_path).expect("Unable to find git repository.");
let metadata = GitsyMetadata { let metadata = GitsyMetadata {
@@ -277,7 +286,9 @@ impl GitsyGenerator {
let mut local_ctx = Context::from_serialize(&summary).unwrap(); let mut local_ctx = Context::from_serialize(&summary).unwrap();
if let Some(extra) = &self.settings.extra { if let Some(extra) = &self.settings.extra {
local_ctx.try_insert("extra", extra).expect("Failed to add extra settings to template engine."); local_ctx
.try_insert("extra", extra)
.expect("Failed to add extra settings to template engine.");
} }
if let Some(site_name) = &self.settings.site_name { if let Some(site_name) = &self.settings.site_name {
local_ctx.insert("site_name", site_name); local_ctx.insert("site_name", site_name);
@@ -294,8 +305,11 @@ impl GitsyGenerator {
if let Some(templ_file) = self.settings.templates.repo_summary.as_deref() { if let Some(templ_file) = self.settings.templates.repo_summary.as_deref() {
match tera.render(templ_file, &local_ctx) { match tera.render(templ_file, &local_ctx) {
Ok(rendered) => { Ok(rendered) => {
repo_bytes += GitsyGenerator::write_rendered(&self.settings.outputs.repo_summary(Some(&summary), None), &rendered); repo_bytes += GitsyGenerator::write_rendered(
}, &self.settings.outputs.repo_summary(Some(&summary), None),
&rendered,
);
}
Err(x) => match x.kind { Err(x) => match x.kind {
_ => error!("ERROR: {:?}", x), _ => error!("ERROR: {:?}", x),
}, },
@@ -308,8 +322,11 @@ impl GitsyGenerator {
if let Some(templ_file) = self.settings.templates.branch.as_deref() { if let Some(templ_file) = self.settings.templates.branch.as_deref() {
match tera.render(templ_file, &local_ctx) { match tera.render(templ_file, &local_ctx) {
Ok(rendered) => { Ok(rendered) => {
repo_bytes += GitsyGenerator::write_rendered(&self.settings.outputs.branch(Some(&summary), Some(branch)), &rendered); repo_bytes += GitsyGenerator::write_rendered(
}, &self.settings.outputs.branch(Some(&summary), Some(branch)),
&rendered,
);
}
Err(x) => match x.kind { Err(x) => match x.kind {
_ => error!("ERROR: {:?}", x), _ => error!("ERROR: {:?}", x),
}, },
@@ -329,8 +346,11 @@ impl GitsyGenerator {
if let Some(templ_file) = self.settings.templates.tag.as_deref() { if let Some(templ_file) = self.settings.templates.tag.as_deref() {
match tera.render(templ_file, &local_ctx) { match tera.render(templ_file, &local_ctx) {
Ok(rendered) => { Ok(rendered) => {
repo_bytes += GitsyGenerator::write_rendered(&self.settings.outputs.tag(Some(&summary), Some(tag)), &rendered); repo_bytes += GitsyGenerator::write_rendered(
}, &self.settings.outputs.tag(Some(&summary), Some(tag)),
&rendered,
);
}
Err(x) => match x.kind { Err(x) => match x.kind {
_ => error!("ERROR: {:?}", x), _ => error!("ERROR: {:?}", x),
}, },
@@ -342,12 +362,17 @@ impl GitsyGenerator {
for (_id, commit) in &summary.commits { for (_id, commit) in &summary.commits {
size_check!(repo_desc, repo_bytes, total_bytes, break); size_check!(repo_desc, repo_bytes, total_bytes, break);
local_ctx.try_insert("commit", &commit).expect("Failed to add commit to template engine."); local_ctx
.try_insert("commit", &commit)
.expect("Failed to add commit to template engine.");
if let Some(templ_file) = self.settings.templates.commit.as_deref() { if let Some(templ_file) = self.settings.templates.commit.as_deref() {
match tera.render(templ_file, &local_ctx) { match tera.render(templ_file, &local_ctx) {
Ok(rendered) => { Ok(rendered) => {
repo_bytes += GitsyGenerator::write_rendered(&self.settings.outputs.commit(Some(&summary), Some(commit)), &rendered); repo_bytes += GitsyGenerator::write_rendered(
}, &self.settings.outputs.commit(Some(&summary), Some(commit)),
&rendered,
);
}
Err(x) => match x.kind { Err(x) => match x.kind {
_ => error!("ERROR: {:?}", x), _ => error!("ERROR: {:?}", x),
}, },
@@ -359,19 +384,32 @@ impl GitsyGenerator {
#[cfg(any(feature = "highlight", feature = "highlight_fast"))] #[cfg(any(feature = "highlight", feature = "highlight_fast"))]
if self.settings.templates.file.is_some() { if self.settings.templates.file.is_some() {
let ts = ThemeSet::load_defaults(); let ts = ThemeSet::load_defaults();
let theme = ts.themes.get(repo_desc.syntax_highlight_theme.as_deref() let theme = ts
.unwrap_or("base16-ocean.light")).expect("Invalid syntax highlighting theme specified."); .themes
.get(
repo_desc
.syntax_highlight_theme
.as_deref()
.unwrap_or("base16-ocean.light"),
)
.expect("Invalid syntax highlighting theme specified.");
let css: String = css_for_theme_with_class_style(theme, syntect::html::ClassStyle::Spaced) let css: String = css_for_theme_with_class_style(theme, syntect::html::ClassStyle::Spaced)
.expect("Invalid syntax highlighting theme specified."); .expect("Invalid syntax highlighting theme specified.");
repo_bytes += GitsyGenerator::write_rendered(&self.settings.outputs.syntax_css(Some(&summary), None), css.as_str()); repo_bytes += GitsyGenerator::write_rendered(
&self.settings.outputs.syntax_css(Some(&summary), None),
css.as_str(),
);
} }
// TODO: parallelize the rest of the processing steps. This one is // TODO: parallelize the rest of the processing steps. This one is
// done first because syntax highlighting is very slow. // done first because syntax highlighting is very slow.
let files: Vec<&GitFile> = summary.all_files.iter().filter(|x| x.kind == "file").collect(); let files: Vec<&GitFile> = summary.all_files.iter().filter(|x| x.kind == "file").collect();
let atomic_bytes: AtomicUsize = AtomicUsize::new(repo_bytes); let atomic_bytes: AtomicUsize = AtomicUsize::new(repo_bytes);
let _ = files.par_iter().fold(|| Some(0), |acc, file| { let _ = files
.par_iter()
.fold(
|| Some(0),
|acc, file| {
// These two have to be recreated. Cloning the Tera context is expensive. // These two have to be recreated. Cloning the Tera context is expensive.
let repo = Repository::open(&repo_path).expect("Unable to find git repository."); let repo = Repository::open(&repo_path).expect("Unable to find git repository.");
let mut local_ctx = local_ctx.clone(); let mut local_ctx = local_ctx.clone();
@@ -380,23 +418,31 @@ impl GitsyGenerator {
let cur_repo_bytes = atomic_bytes.load(Ordering::Relaxed); let cur_repo_bytes = atomic_bytes.load(Ordering::Relaxed);
size_check!(repo_desc, cur_repo_bytes, total_bytes, return None); size_check!(repo_desc, cur_repo_bytes, total_bytes, return None);
let file = match file.size < repo_desc.limit_file_size.unwrap_or(usize::MAX) { let file = match file.size < repo_desc.limit_file_size.unwrap_or(usize::MAX) {
true => GitsyGenerator::fill_file_contents(&repo, &file, &repo_desc).expect("Failed to parse file."), true => GitsyGenerator::fill_file_contents(&repo, &file, &repo_desc)
.expect("Failed to parse file."),
false => (*file).clone(), false => (*file).clone(),
}; };
local_ctx.try_insert("file", &file).expect("Failed to add file to template engine."); local_ctx
.try_insert("file", &file)
.expect("Failed to add file to template engine.");
if let Some(templ_file) = self.settings.templates.file.as_deref() { if let Some(templ_file) = self.settings.templates.file.as_deref() {
match tera.render(templ_file, &local_ctx) { match tera.render(templ_file, &local_ctx) {
Ok(rendered) => { Ok(rendered) => {
local_bytes = GitsyGenerator::write_rendered(&self.settings.outputs.file(Some(&summary), Some(&file)), &rendered); local_bytes = GitsyGenerator::write_rendered(
&self.settings.outputs.file(Some(&summary), Some(&file)),
&rendered,
);
atomic_bytes.fetch_add(local_bytes, Ordering::Relaxed); atomic_bytes.fetch_add(local_bytes, Ordering::Relaxed);
}, }
Err(x) => match x.kind { Err(x) => match x.kind {
_ => error!("ERROR: {:?}", x), _ => error!("ERROR: {:?}", x),
}, },
} }
} }
local_ctx.remove("file"); local_ctx.remove("file");
Some(acc.unwrap() + local_bytes)}) Some(acc.unwrap() + local_bytes)
},
)
.while_some() // allow short-circuiting if size limit is reached .while_some() // allow short-circuiting if size limit is reached
.sum::<usize>(); .sum::<usize>();
repo_bytes = atomic_bytes.load(Ordering::Relaxed); repo_bytes = atomic_bytes.load(Ordering::Relaxed);
@@ -407,12 +453,17 @@ impl GitsyGenerator {
continue; continue;
} }
let listing = dir_listing(&repo, &dir).expect("Failed to parse file."); let listing = dir_listing(&repo, &dir).expect("Failed to parse file.");
local_ctx.try_insert("files", &listing).expect("Failed to add dir to template engine."); local_ctx
.try_insert("files", &listing)
.expect("Failed to add dir to template engine.");
if let Some(templ_file) = self.settings.templates.dir.as_deref() { if let Some(templ_file) = self.settings.templates.dir.as_deref() {
match tera.render(templ_file, &local_ctx) { match tera.render(templ_file, &local_ctx) {
Ok(rendered) => { Ok(rendered) => {
repo_bytes += GitsyGenerator::write_rendered(&self.settings.outputs.dir(Some(&summary), Some(dir)), &rendered); repo_bytes += GitsyGenerator::write_rendered(
}, &self.settings.outputs.dir(Some(&summary), Some(dir)),
&rendered,
);
}
Err(x) => match x.kind { Err(x) => match x.kind {
_ => error!("ERROR: {:?}", x), _ => error!("ERROR: {:?}", x),
}, },
@@ -426,12 +477,16 @@ impl GitsyGenerator {
for src_file in repo_desc.asset_files.as_ref().unwrap() { for src_file in repo_desc.asset_files.as_ref().unwrap() {
let src_file = PathBuf::from(repo_path.to_owned() + "/" + src_file); let src_file = PathBuf::from(repo_path.to_owned() + "/" + src_file);
let mut dst_file = PathBuf::from(&target_dir); let mut dst_file = PathBuf::from(&target_dir);
dst_file.push(src_file.file_name() dst_file.push(src_file.file_name().expect(&format!(
.expect(&format!("Failed to copy repo asset file: {} ({})", "Failed to copy repo asset file: {} ({})",
src_file.display(), repo_desc.name.as_deref().unwrap_or_default()))); src_file.display(),
std::fs::copy(&src_file, &dst_file) repo_desc.name.as_deref().unwrap_or_default()
.expect(&format!("Failed to copy repo asset file: {} ({})", )));
src_file.display(), repo_desc.name.as_deref().unwrap_or_default())); std::fs::copy(&src_file, &dst_file).expect(&format!(
"Failed to copy repo asset file: {} ({})",
src_file.display(),
repo_desc.name.as_deref().unwrap_or_default()
));
if let Ok(meta) = std::fs::metadata(dst_file) { if let Ok(meta) = std::fs::metadata(dst_file) {
repo_bytes += meta.len() as usize; repo_bytes += meta.len() as usize;
} }
@@ -439,22 +494,33 @@ impl GitsyGenerator {
} }
repos.push(summary); repos.push(summary);
normal!("{}done in {:.2}s ({} bytes)", normal!(
"{}done in {:.2}s ({} bytes)",
match crate::util::VERBOSITY.load(Ordering::Relaxed) > 1 { match crate::util::VERBOSITY.load(Ordering::Relaxed) > 1 {
true => " - ", true => " - ",
_ => "", _ => "",
}, },
start_repo.elapsed().as_secs_f32(), repo_bytes); start_repo.elapsed().as_secs_f32(),
repo_bytes
);
total_bytes += repo_bytes; total_bytes += repo_bytes;
size_check!(repo_desc, 0, total_bytes, break); // break if total is exceeded size_check!(repo_desc, 0, total_bytes, break); // break if total is exceeded
} }
let start_global = Instant::now(); let start_global = Instant::now();
normal_noln!("[{}{}]... ", global_name, " ".repeat(longest_repo_name - global_name.len())); normal_noln!(
"[{}{}]... ",
global_name,
" ".repeat(longest_repo_name - global_name.len())
);
let mut global_ctx = Context::new(); let mut global_ctx = Context::new();
global_ctx.try_insert("repos", &repos).expect("Failed to add repo to template engine."); global_ctx
.try_insert("repos", &repos)
.expect("Failed to add repo to template engine.");
if let Some(extra) = &self.settings.extra { if let Some(extra) = &self.settings.extra {
global_ctx.try_insert("extra", extra).expect("Failed to add extra settings to template engine."); global_ctx
.try_insert("extra", extra)
.expect("Failed to add extra settings to template engine.");
} }
if let Some(site_name) = &self.settings.site_name { if let Some(site_name) = &self.settings.site_name {
global_ctx.insert("site_name", site_name); global_ctx.insert("site_name", site_name);
@@ -471,8 +537,9 @@ impl GitsyGenerator {
if let Some(templ_file) = self.settings.templates.repo_list.as_deref() { if let Some(templ_file) = self.settings.templates.repo_list.as_deref() {
match tera.render(templ_file, &global_ctx) { match tera.render(templ_file, &global_ctx) {
Ok(rendered) => { Ok(rendered) => {
global_bytes += GitsyGenerator::write_rendered(&self.settings.outputs.repo_list(None, None), &rendered); global_bytes +=
}, GitsyGenerator::write_rendered(&self.settings.outputs.repo_list(None, None), &rendered);
}
Err(x) => match x.kind { Err(x) => match x.kind {
_ => error!("ERROR: {:?}", x), _ => error!("ERROR: {:?}", x),
}, },
@@ -483,7 +550,7 @@ impl GitsyGenerator {
match tera.render(templ_file, &global_ctx) { match tera.render(templ_file, &global_ctx) {
Ok(rendered) => { Ok(rendered) => {
global_bytes += GitsyGenerator::write_rendered(&self.settings.outputs.error(None, None), &rendered); global_bytes += GitsyGenerator::write_rendered(&self.settings.outputs.error(None, None), &rendered);
}, }
Err(x) => match x.kind { Err(x) => match x.kind {
_ => error!("ERROR: {:?}", x), _ => error!("ERROR: {:?}", x),
}, },
@@ -495,8 +562,11 @@ impl GitsyGenerator {
for src_file in self.settings.asset_files.as_ref().unwrap() { for src_file in self.settings.asset_files.as_ref().unwrap() {
let src_file = PathBuf::from(src_file); let src_file = PathBuf::from(src_file);
let mut dst_file = PathBuf::from(&target_dir); let mut dst_file = PathBuf::from(&target_dir);
dst_file.push(src_file.file_name() dst_file.push(
.expect(&format!("Failed to copy asset file: {}", src_file.display()))); src_file
.file_name()
.expect(&format!("Failed to copy asset file: {}", src_file.display())),
);
std::fs::copy(&src_file, &dst_file) std::fs::copy(&src_file, &dst_file)
.expect(&format!("Failed to copy asset file: {}", src_file.display())); .expect(&format!("Failed to copy asset file: {}", src_file.display()));
if let Ok(meta) = std::fs::metadata(dst_file) { if let Ok(meta) = std::fs::metadata(dst_file) {
@@ -506,8 +576,16 @@ impl GitsyGenerator {
} }
total_bytes += global_bytes; total_bytes += global_bytes;
normal!("done in {:.2}s ({} bytes)", start_global.elapsed().as_secs_f32(), global_bytes); normal!(
loud!("Wrote {} bytes in {:.2}s", total_bytes, start_all.elapsed().as_secs_f32()); "done in {:.2}s ({} bytes)",
start_global.elapsed().as_secs_f32(),
global_bytes
);
loud!(
"Wrote {} bytes in {:.2}s",
total_bytes,
start_all.elapsed().as_secs_f32()
);
Ok(()) Ok(())
} }
} }
+86 -47
View File
@@ -1,7 +1,7 @@
use crate::{error, loud, loudest};
use crate::settings::GitsySettingsRepo; use crate::settings::GitsySettingsRepo;
use git2::{DiffOptions, Repository, Error}; use crate::{error, loud, loudest};
use serde::{Serialize, Deserialize}; use git2::{DiffOptions, Error, Repository};
use serde::{Deserialize, Serialize};
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::rc::Rc; use std::rc::Rc;
@@ -98,7 +98,7 @@ pub struct GitDiffFile {
pub extra: String, pub extra: String,
pub additions: usize, pub additions: usize,
pub deletions: usize, pub deletions: usize,
pub hunks: Vec<GitDiffHunk> pub hunks: Vec<GitDiffHunk>,
} }
#[derive(Serialize, Default)] #[derive(Serialize, Default)]
@@ -114,8 +114,15 @@ pub struct GitDiffLine {
pub text: String, pub text: String,
} }
fn walk_file_tree(repo: &git2::Repository, rev: &str, files: &mut Vec<GitFile>, fn walk_file_tree(
depth: usize, max_depth: usize, recurse: bool, prefix: &str) -> Result<(), Error> { repo: &git2::Repository,
rev: &str,
files: &mut Vec<GitFile>,
depth: usize,
max_depth: usize,
recurse: bool,
prefix: &str,
) -> Result<(), Error> {
let obj = repo.revparse_single(rev)?; let obj = repo.revparse_single(rev)?;
let tree = obj.peel_to_tree()?; let tree = obj.peel_to_tree()?;
for entry in tree.iter() { for entry in tree.iter() {
@@ -151,23 +158,35 @@ fn walk_file_tree(repo: &git2::Repository, rev: &str, files: &mut Vec<GitFile>,
}); });
if recurse && depth < (max_depth - 1) && entry.kind() == Some(git2::ObjectType::Tree) { if recurse && depth < (max_depth - 1) && entry.kind() == Some(git2::ObjectType::Tree) {
let prefix = path + "/"; let prefix = path + "/";
walk_file_tree(repo, &entry.id().to_string(), files, walk_file_tree(
depth+1, max_depth, true, &prefix)?; repo,
&entry.id().to_string(),
files,
depth + 1,
max_depth,
true,
&prefix,
)?;
} }
} }
Ok(()) Ok(())
} }
pub fn dir_listing(repo: &Repository, file: &GitFile) -> Result<Vec<GitFile>, Error> { pub fn dir_listing(repo: &Repository, file: &GitFile) -> Result<Vec<GitFile>, Error> {
let mut files: Vec<GitFile> = vec!(); let mut files: Vec<GitFile> = vec![];
walk_file_tree(&repo, &file.id, &mut files, 0, usize::MAX, false, "")?; walk_file_tree(&repo, &file.id, &mut files, 0, usize::MAX, false, "")?;
Ok(files) Ok(files)
} }
pub fn parse_repo(repo: &Repository, name: &str, settings: &GitsySettingsRepo, metadata: GitsyMetadata) -> Result<GitRepo, Error> { pub fn parse_repo(
let mut history: Vec<GitObject> = vec!(); repo: &Repository,
let mut branches: Vec<GitObject> = vec!(); name: &str,
let mut tags: Vec<GitObject> = vec!(); settings: &GitsySettingsRepo,
metadata: GitsyMetadata,
) -> Result<GitRepo, Error> {
let mut history: Vec<GitObject> = vec![];
let mut branches: Vec<GitObject> = vec![];
let mut tags: Vec<GitObject> = vec![];
let mut commits: BTreeMap<String, GitObject> = BTreeMap::new(); let mut commits: BTreeMap<String, GitObject> = BTreeMap::new();
let mut commit_count = 0; let mut commit_count = 0;
let mut history_count = 0; let mut history_count = 0;
@@ -181,8 +200,12 @@ pub fn parse_repo(repo: &Repository, name: &str, settings: &GitsySettingsRepo, m
if let (Some(target), Some(name)) = (refr.target(), refr.shorthand()) { if let (Some(target), Some(name)) = (refr.target(), refr.shorthand()) {
let id = target.to_string(); let id = target.to_string();
match references.contains_key(&id) { match references.contains_key(&id) {
false => { references.insert(target.to_string(), vec!(name.to_string())); }, false => {
true => { references.get_mut(&id).unwrap().push(name.to_string()); }, references.insert(target.to_string(), vec![name.to_string()]);
}
true => {
references.get_mut(&id).unwrap().push(name.to_string());
}
} }
} }
} }
@@ -194,8 +217,9 @@ pub fn parse_repo(repo: &Repository, name: &str, settings: &GitsySettingsRepo, m
loudest!(" - Parsing history:"); loudest!(" - Parsing history:");
for oid in revwalk { for oid in revwalk {
let oid = oid?; let oid = oid?;
if commit_count >= settings.limit_commits.unwrap_or(usize::MAX) || if commit_count >= settings.limit_commits.unwrap_or(usize::MAX)
history_count >= settings.limit_history.unwrap_or(usize::MAX) { || history_count >= settings.limit_history.unwrap_or(usize::MAX)
{
break; break;
} }
commits.insert(oid.to_string(), parse_commit(repo, &oid.to_string())?); commits.insert(oid.to_string(), parse_commit(repo, &oid.to_string())?);
@@ -205,7 +229,7 @@ pub fn parse_repo(repo: &Repository, name: &str, settings: &GitsySettingsRepo, m
let full_hash = commit.id().to_string(); let full_hash = commit.id().to_string();
let short_hash = obj.short_id()?.as_str().unwrap_or_default().to_string(); let short_hash = obj.short_id()?.as_str().unwrap_or_default().to_string();
let mut parents: Vec<String> = vec!(); let mut parents: Vec<String> = vec![];
let a = if commit.parents().len() == 1 { let a = if commit.parents().len() == 1 {
let parent = commit.parent(0)?; let parent = commit.parent(0)?;
parents.push(parent.id().to_string()); parents.push(parent.id().to_string());
@@ -223,8 +247,10 @@ pub fn parse_repo(repo: &Repository, name: &str, settings: &GitsySettingsRepo, m
deletions: stats.deletions(), deletions: stats.deletions(),
}; };
let alt_refs: Vec<String> = references.get(&commit.id().to_string()) let alt_refs: Vec<String> = references
.map(|x| x.to_owned()).unwrap_or_default(); .get(&commit.id().to_string())
.map(|x| x.to_owned())
.unwrap_or_default();
if history_count < settings.limit_history.unwrap_or(usize::MAX) { if history_count < settings.limit_history.unwrap_or(usize::MAX) {
loudest!(" + {} {}", full_hash, first_line(commit.message_bytes())); loudest!(" + {} {}", full_hash, first_line(commit.message_bytes()));
@@ -267,7 +293,7 @@ pub fn parse_repo(repo: &Repository, name: &str, settings: &GitsySettingsRepo, m
// this is a bad idea? // this is a bad idea?
match refr.kind() { match refr.kind() {
Some(k) if k == git2::ReferenceType::Symbolic => continue, Some(k) if k == git2::ReferenceType::Symbolic => continue,
_ => {}, _ => {}
} }
let commit = repo.find_commit(obj.id())?; let commit = repo.find_commit(obj.id())?;
let full_hash = obj.id().to_string(); let full_hash = obj.id().to_string();
@@ -278,7 +304,7 @@ pub fn parse_repo(repo: &Repository, name: &str, settings: &GitsySettingsRepo, m
short_hash, short_hash,
ts_utc: commit.author().when().seconds(), ts_utc: commit.author().when().seconds(),
ts_offset: (commit.author().when().offset_minutes() as i64) * 60, ts_offset: (commit.author().when().offset_minutes() as i64) * 60,
parents: vec!(), parents: vec![],
ref_name: Some(name.to_string()), ref_name: Some(name.to_string()),
author: GitAuthor { author: GitAuthor {
name: commit.author().name().map(|x| x.to_owned()), name: commit.author().name().map(|x| x.to_owned()),
@@ -324,8 +350,7 @@ pub fn parse_repo(repo: &Repository, name: &str, settings: &GitsySettingsRepo, m
_ => (0, 0), _ => (0, 0),
}; };
let (author, email) = match commit.tagger() { let (author, email) = match commit.tagger() {
Some(t) => (t.name().map(|x| x.to_owned()), Some(t) => (t.name().map(|x| x.to_owned()), t.email().map(|x| x.to_owned())),
t.email().map(|x| x.to_owned())),
_ => (None, None), _ => (None, None),
}; };
let summary = match commit.message_bytes() { let summary = match commit.message_bytes() {
@@ -339,10 +364,7 @@ pub fn parse_repo(repo: &Repository, name: &str, settings: &GitsySettingsRepo, m
ts_utc: ts, ts_utc: ts,
ts_offset: tz, ts_offset: tz,
ref_name: Some(tag.to_string()), ref_name: Some(tag.to_string()),
author: GitAuthor { author: GitAuthor { name: author, email },
name: author,
email,
},
tagged_id: Some(commit.target_id().to_string()), tagged_id: Some(commit.target_id().to_string()),
message: commit.message().map(|x| x.to_string()), message: commit.message().map(|x| x.to_string()),
summary, summary,
@@ -352,8 +374,8 @@ pub fn parse_repo(repo: &Repository, name: &str, settings: &GitsySettingsRepo, m
} }
loud!(" - parsed {} tags", tag_count); loud!(" - parsed {} tags", tag_count);
let mut root_files: Vec<GitFile> = vec!(); let mut root_files: Vec<GitFile> = vec![];
let mut all_files: Vec<GitFile> = vec!(); let mut all_files: Vec<GitFile> = vec![];
let max_depth = settings.limit_tree_depth.unwrap_or(usize::MAX); let max_depth = settings.limit_tree_depth.unwrap_or(usize::MAX);
if max_depth > 0 { if max_depth > 0 {
loudest!(" - Walking root files"); loudest!(" - Walking root files");
@@ -380,23 +402,21 @@ pub fn parse_repo(repo: &Repository, name: &str, settings: &GitsySettingsRepo, m
pub fn parse_commit(repo: &Repository, refr: &str) -> Result<GitObject, Error> { pub fn parse_commit(repo: &Repository, refr: &str) -> Result<GitObject, Error> {
let obj = repo.revparse_single(refr)?; let obj = repo.revparse_single(refr)?;
let commit = repo.find_commit(obj.id())?; let commit = repo.find_commit(obj.id())?;
let mut parents: Vec<String> = vec!(); let mut parents: Vec<String> = vec![];
let a = match commit.parents().len() { let a = match commit.parents().len() {
x if x == 1 => { x if x == 1 => {
let parent = commit.parent(0).unwrap(); let parent = commit.parent(0).unwrap();
parents.push(parent.id().to_string()); parents.push(parent.id().to_string());
Some(parent.tree()?) Some(parent.tree()?)
}, }
x if x > 1 => { x if x > 1 => {
for parent in commit.parents() { for parent in commit.parents() {
parents.push(parent.id().to_string()); parents.push(parent.id().to_string());
} }
None None
}, }
_ => { _ => None,
None
},
}; };
let b = commit.tree()?; let b = commit.tree()?;
let mut diffopts = DiffOptions::new(); let mut diffopts = DiffOptions::new();
@@ -409,22 +429,38 @@ pub fn parse_commit(repo: &Repository, refr: &str) -> Result<GitObject, Error> {
deletions: stats.deletions(), deletions: stats.deletions(),
..Default::default() ..Default::default()
}; };
let files: Rc<RefCell<Vec<GitDiffFile>>> = Rc::new(RefCell::new(vec!())); let files: Rc<RefCell<Vec<GitDiffFile>>> = Rc::new(RefCell::new(vec![]));
diff.foreach( diff.foreach(
&mut |file, _progress| { &mut |file, _progress| {
let mut file_diff: GitDiffFile = Default::default(); let mut file_diff: GitDiffFile = Default::default();
file_diff.newfile = match file.status() { file_diff.newfile = match file.status() {
git2::Delta::Deleted => "/dev/null".to_owned(), git2::Delta::Deleted => "/dev/null".to_owned(),
_ => file.new_file().path().map(|x| "b/".to_string() + &x.to_string_lossy()).unwrap_or("/dev/null".to_string()), _ => file
.new_file()
.path()
.map(|x| "b/".to_string() + &x.to_string_lossy())
.unwrap_or("/dev/null".to_string()),
}; };
file_diff.oldfile = match file.status() { file_diff.oldfile = match file.status() {
git2::Delta::Added => "/dev/null".to_owned(), git2::Delta::Added => "/dev/null".to_owned(),
_ => file.old_file().path().map(|x| "a/".to_string() + &x.to_string_lossy()).unwrap_or("/dev/null".to_string()), _ => file
.old_file()
.path()
.map(|x| "a/".to_string() + &x.to_string_lossy())
.unwrap_or("/dev/null".to_string()),
}; };
file_diff.basefile = match file.status() { file_diff.basefile = match file.status() {
git2::Delta::Added => file.new_file().path().map(|x| x.to_string_lossy().to_string()).unwrap_or("/dev/null".to_string()), git2::Delta::Added => file
_ => file.old_file().path().map(|x| x.to_string_lossy().to_string()).unwrap_or("/dev/null".to_string()), .new_file()
.path()
.map(|x| x.to_string_lossy().to_string())
.unwrap_or("/dev/null".to_string()),
_ => file
.old_file()
.path()
.map(|x| x.to_string_lossy().to_string())
.unwrap_or("/dev/null".to_string()),
}; };
file_diff.oldid = file.old_file().id().to_string(); file_diff.oldid = file.old_file().id().to_string();
file_diff.newid = file.new_file().id().to_string(); file_diff.newid = file.new_file().id().to_string();
@@ -443,7 +479,10 @@ pub fn parse_commit(repo: &Repository, refr: &str) -> Result<GitObject, Error> {
Some(&mut |_file, _hunk, line| { Some(&mut |_file, _hunk, line| {
let mut files = files.borrow_mut(); let mut files = files.borrow_mut();
let file_diff: &mut GitDiffFile = files.last_mut().expect("Diff hunk not associated with a file!"); let file_diff: &mut GitDiffFile = files.last_mut().expect("Diff hunk not associated with a file!");
let hunk_diff: &mut GitDiffHunk = file_diff.hunks.last_mut().expect("Diff line not associated with a hunk!"); let hunk_diff: &mut GitDiffHunk = file_diff
.hunks
.last_mut()
.expect("Diff line not associated with a hunk!");
let (kind, prefix) = match line.origin() { let (kind, prefix) = match line.origin() {
' ' => ("ctx", " "), ' ' => ("ctx", " "),
'-' => ("del", "-"), '-' => ("del", "-"),
@@ -453,7 +492,7 @@ pub fn parse_commit(repo: &Repository, refr: &str) -> Result<GitObject, Error> {
match line.origin() { match line.origin() {
'-' => file_diff.deletions += 1, '-' => file_diff.deletions += 1,
'+' => file_diff.additions += 1, '+' => file_diff.additions += 1,
_ => {}, _ => {}
} }
let line_diff = GitDiffLine { let line_diff = GitDiffLine {
text: String::from_utf8_lossy(line.content()).to_string(), text: String::from_utf8_lossy(line.content()).to_string(),
@@ -462,15 +501,15 @@ pub fn parse_commit(repo: &Repository, refr: &str) -> Result<GitObject, Error> {
}; };
hunk_diff.lines.push(line_diff); hunk_diff.lines.push(line_diff);
true true
}) }),
)?; )?;
match Rc::try_unwrap(files) { match Rc::try_unwrap(files) {
Ok(files) => { Ok(files) => {
let files: Vec<GitDiffFile> = files.into_inner(); let files: Vec<GitDiffFile> = files.into_inner();
commit_diff.files = files; commit_diff.files = files;
}, }
Err(_) => {}, Err(_) => {}
} }
let tree = obj.peel_to_tree()?; let tree = obj.peel_to_tree()?;
@@ -483,7 +522,7 @@ pub fn parse_commit(repo: &Repository, refr: &str) -> Result<GitObject, Error> {
tree_id: Some(tree.id().to_string()), tree_id: Some(tree.id().to_string()),
parents, parents,
ref_name: None, ref_name: None,
alt_refs: vec!(), alt_refs: vec![],
author: GitAuthor { author: GitAuthor {
name: commit.author().name().map(|x| x.to_string()), name: commit.author().name().map(|x| x.to_string()),
email: commit.author().email().map(|x| x.to_string()), email: commit.author().email().map(|x| x.to_string()),
+34 -25
View File
@@ -1,10 +1,6 @@
use clap::Parser;
use crate::error; use crate::error;
use crate::git::{ use crate::git::{GitFile, GitObject, GitRepo};
GitFile, use clap::Parser;
GitObject,
GitRepo,
};
use serde::Deserialize; use serde::Deserialize;
use std::collections::{BTreeMap, HashMap, HashSet}; use std::collections::{BTreeMap, HashMap, HashSet};
use std::fs::{create_dir_all, read_dir, read_to_string}; use std::fs::{create_dir_all, read_dir, read_to_string};
@@ -39,7 +35,10 @@ impl GitsyCli {
pub fn new() -> Self { pub fn new() -> Self {
let cli = CliArgs::parse(); let cli = CliArgs::parse();
let config_path = cli.config.as_deref().unwrap_or(Path::new("config.toml")).to_owned(); let config_path = cli.config.as_deref().unwrap_or(Path::new("config.toml")).to_owned();
let config_dir = config_path.parent().expect("Config file not in valid directory.").to_owned(); let config_dir = config_path
.parent()
.expect("Config file not in valid directory.")
.to_owned();
let config_dir = match config_dir.to_str().unwrap_or_default().len() > 0 { let config_dir = match config_dir.to_str().unwrap_or_default().len() > 0 {
true => config_dir, true => config_dir,
false => PathBuf::from("."), false => PathBuf::from("."),
@@ -52,10 +51,13 @@ impl GitsyCli {
Ok(d) => d, Ok(d) => d,
_ => config_dir.clone(), _ => config_dir.clone(),
}; };
crate::util::VERBOSITY.store(match cli.quiet { crate::util::VERBOSITY.store(
match cli.quiet {
true => 0, true => 0,
false => (cli.verbose + 1).into(), false => (cli.verbose + 1).into(),
}, Ordering::Relaxed); },
Ordering::Relaxed,
);
GitsyCli { GitsyCli {
path: config_path, path: config_path,
dir: config_dir, dir: config_dir,
@@ -63,7 +65,6 @@ impl GitsyCli {
} }
} }
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct GitsySettingsTemplates { pub struct GitsySettingsTemplates {
pub path: PathBuf, pub path: PathBuf,
@@ -102,7 +103,7 @@ macro_rules! output_path_fn {
(true, true) => { (true, true) => {
let name = repo.map(|x| &x.name).unwrap(); let name = repo.map(|x| &x.name).unwrap();
tmpl_str.replace("%REPO%", name) tmpl_str.replace("%REPO%", name)
}, }
(true, false) => { (true, false) => {
panic!("%REPO% variable not available for output path: {}", tmpl_str); panic!("%REPO% variable not available for output path: {}", tmpl_str);
} }
@@ -112,7 +113,7 @@ macro_rules! output_path_fn {
(true, true) => { (true, true) => {
let name = obj.map(|x| &x.$id).unwrap(); let name = obj.map(|x| &x.$id).unwrap();
tmpl_str.replace("%ID%", name) tmpl_str.replace("%ID%", name)
}, }
(true, false) => { (true, false) => {
panic!("%ID% variable not available for output path: {}", tmpl_str); panic!("%ID% variable not available for output path: {}", tmpl_str);
} }
@@ -124,12 +125,12 @@ macro_rules! output_path_fn {
match $is_dir { match $is_dir {
true => { true => {
let _ = create_dir_all(&path); let _ = create_dir_all(&path);
}, }
false => { false => {
if let Some(dir) = path.parent() { if let Some(dir) = path.parent() {
let _ = create_dir_all(dir); let _ = create_dir_all(dir);
} }
}, }
} }
path.to_str() path.to_str()
.expect(&format!("Output is not a valid path: {}", path.display())) .expect(&format!("Output is not a valid path: {}", path.display()))
@@ -222,22 +223,26 @@ impl GitsySettings {
let settings: GitsySettings = toml::from_str(&toml).expect("Configuration file is invalid."); let settings: GitsySettings = toml::from_str(&toml).expect("Configuration file is invalid.");
// Settings are valid, so let's move into the directory with the config file // Settings are valid, so let's move into the directory with the config file
if cli.dir.to_str().unwrap_or_default().len() > 0 { // empty string means current directory if cli.dir.to_str().unwrap_or_default().len() > 0 {
// empty string means current directory
std::env::set_current_dir(&cli.dir) std::env::set_current_dir(&cli.dir)
.expect(&format!("Unable to set working directory to: {}", cli.dir.display())); .expect(&format!("Unable to set working directory to: {}", cli.dir.display()));
} }
// Get a list of all remaining TOML "tables" in the file. // Get a list of all remaining TOML "tables" in the file.
// These are the user-supplied individual repositories. // These are the user-supplied individual repositories.
let reserved_keys = vec!("gitsy_templates", "gitsy_outputs", "gitsy_extra"); let reserved_keys = vec!["gitsy_templates", "gitsy_outputs", "gitsy_extra"];
let settings_raw: HashMap<String, toml::Value> = toml::from_str(&toml).expect("blah"); let settings_raw: HashMap<String, toml::Value> = toml::from_str(&toml).expect("blah");
let table_keys: Vec<String> = settings_raw.iter().filter_map(|x| match x.1.is_table() { let table_keys: Vec<String> = settings_raw
.iter()
.filter_map(|x| match x.1.is_table() {
true => match reserved_keys.contains(&x.0.as_str()) { true => match reserved_keys.contains(&x.0.as_str()) {
false => Some(x.0.clone()), false => Some(x.0.clone()),
true => None, true => None,
}, },
false => None false => None,
}).collect(); })
.collect();
// Try to convert each unknown "table" into a repo struct, and // Try to convert each unknown "table" into a repo struct, and
// save the ones that are successful. If no repo name is // save the ones that are successful. If no repo name is
@@ -245,14 +250,18 @@ impl GitsySettings {
let mut repo_descriptions: HashSet<GitsySettingsRepo> = HashSet::new(); let mut repo_descriptions: HashSet<GitsySettingsRepo> = HashSet::new();
macro_rules! global_to_repo { macro_rules! global_to_repo {
($settings:ident, $repo:ident, $field:ident) => { ($settings:ident, $repo:ident, $field:ident) => {
if $repo.$field.is_none() { $repo.$field = $settings.$field.clone() } if $repo.$field.is_none() {
$repo.$field = $settings.$field.clone()
} }
};
} }
for k in &table_keys { for k in &table_keys {
let v = settings_raw.get(k).unwrap(); let v = settings_raw.get(k).unwrap();
match toml::from_str::<GitsySettingsRepo>(&v.to_string()) { match toml::from_str::<GitsySettingsRepo>(&v.to_string()) {
Ok(mut repo) => { Ok(mut repo) => {
if repo.name.is_none() { repo.name = Some(k.clone()); } if repo.name.is_none() {
repo.name = Some(k.clone());
}
global_to_repo!(settings, repo, render_markdown); global_to_repo!(settings, repo, render_markdown);
global_to_repo!(settings, repo, syntax_highlight); global_to_repo!(settings, repo, syntax_highlight);
global_to_repo!(settings, repo, syntax_highlight_theme); global_to_repo!(settings, repo, syntax_highlight_theme);
@@ -266,10 +275,10 @@ impl GitsySettings {
global_to_repo!(settings, repo, limit_total_size); global_to_repo!(settings, repo, limit_total_size);
repo_descriptions.insert(repo); repo_descriptions.insert(repo);
}, }
Err(e) => { Err(e) => {
error!("Failed to parse repo [{}]: {:?}", k, e); error!("Failed to parse repo [{}]: {:?}", k, e);
}, }
} }
} }
@@ -297,8 +306,8 @@ impl GitsySettings {
}); });
} }
} }
}, }
_ => {}, _ => {}
} }
(settings, repo_descriptions) (settings, repo_descriptions)
} }
+18 -16
View File
@@ -1,16 +1,13 @@
use chrono::{
DateTime,
offset::FixedOffset,
naive::NaiveDateTime,
};
use crate::git::GitFile; use crate::git::GitFile;
use chrono::{naive::NaiveDateTime, offset::FixedOffset, DateTime};
use std::collections::HashMap; use std::collections::HashMap;
use tera::{Filter, Function, Value, to_value, try_get_value}; use tera::{to_value, try_get_value, Filter, Function, Value};
fn ts_to_date(ts: i64, offset: Option<i64>, format: Option<String>) -> String { fn ts_to_date(ts: i64, offset: Option<i64>, format: Option<String>) -> String {
let offset = offset.unwrap_or(0); let offset = offset.unwrap_or(0);
let dt = NaiveDateTime::from_timestamp_opt(ts + offset, 0).expect("Invalid timestamp"); let dt = NaiveDateTime::from_timestamp_opt(ts + offset, 0).expect("Invalid timestamp");
let dt_tz: DateTime<FixedOffset> = DateTime::from_local(dt, FixedOffset::east_opt(offset as i32).expect("Invalid timezone")); let dt_tz: DateTime<FixedOffset> =
DateTime::from_local(dt, FixedOffset::east_opt(offset as i32).expect("Invalid timezone"));
match format { match format {
Some(f) => dt_tz.format(&f).to_string(), Some(f) => dt_tz.format(&f).to_string(),
None => dt_tz.format("%Y-%m-%d").to_string(), None => dt_tz.format("%Y-%m-%d").to_string(),
@@ -20,32 +17,37 @@ fn ts_to_date(ts: i64, offset: Option<i64>, format: Option<String>) -> String {
fn ts_to_git_timestamp(ts: i64, offset: Option<i64>) -> String { fn ts_to_git_timestamp(ts: i64, offset: Option<i64>) -> String {
let offset = offset.unwrap_or(0); let offset = offset.unwrap_or(0);
let dt = chrono::naive::NaiveDateTime::from_timestamp_opt(ts + offset, 0).expect("invalid timestamp"); let dt = chrono::naive::NaiveDateTime::from_timestamp_opt(ts + offset, 0).expect("invalid timestamp");
let dt_tz: DateTime<FixedOffset> = DateTime::from_local(dt, FixedOffset::east_opt(offset as i32).expect("Invalid timezone")); let dt_tz: DateTime<FixedOffset> =
DateTime::from_local(dt, FixedOffset::east_opt(offset as i32).expect("Invalid timezone"));
dt_tz.format("%a %b %e %T %Y %z").to_string() dt_tz.format("%a %b %e %T %Y %z").to_string()
} }
pub struct FileFilter; pub struct FileFilter;
impl Filter for FileFilter { impl Filter for FileFilter {
fn filter(&self, value: &Value, _args: &HashMap<String, Value> fn filter(&self, value: &Value, _args: &HashMap<String, Value>) -> Result<Value, tera::Error> {
) -> Result<Value, tera::Error> {
let file_list: Vec<GitFile> = try_get_value!("only_files", "value", Vec<GitFile>, value); let file_list: Vec<GitFile> = try_get_value!("only_files", "value", Vec<GitFile>, value);
let file_list: Vec<GitFile> = file_list.iter().filter_map(|x| match x.kind.as_str() { let file_list: Vec<GitFile> = file_list
.iter()
.filter_map(|x| match x.kind.as_str() {
"file" => Some(x.clone()), "file" => Some(x.clone()),
_ => None, _ => None,
}).collect(); })
.collect();
Ok(to_value(file_list).unwrap()) Ok(to_value(file_list).unwrap())
} }
} }
pub struct DirFilter; pub struct DirFilter;
impl Filter for DirFilter { impl Filter for DirFilter {
fn filter(&self, value: &Value, _args: &HashMap<String, Value> fn filter(&self, value: &Value, _args: &HashMap<String, Value>) -> Result<Value, tera::Error> {
) -> Result<Value, tera::Error> {
let file_list: Vec<GitFile> = try_get_value!("only_dirs", "value", Vec<GitFile>, value); let file_list: Vec<GitFile> = try_get_value!("only_dirs", "value", Vec<GitFile>, value);
let file_list: Vec<GitFile> = file_list.iter().filter_map(|x| match x.kind.as_str() { let file_list: Vec<GitFile> = file_list
.iter()
.filter_map(|x| match x.kind.as_str() {
"dir" => Some(x.clone()), "dir" => Some(x.clone()),
_ => None, _ => None,
}).collect(); })
.collect();
Ok(to_value(file_list).unwrap()) Ok(to_value(file_list).unwrap())
} }
} }