use crate::{
- error, normal, normal_noln, loud, louder, loudest,
- git::{
- dir_listing,
- GitFile,
- GitRepo,
- GitsyMetadata,
- parse_repo,
- },
- settings::{
- GitsyCli,
- GitsyRepoDescriptions,
- GitsySettings,
- GitsySettingsRepo,
- },
- template::{
- DirFilter,
- FileFilter,
- TsDateFn,
- TsTimestampFn,
- },
+ error,
+ git::{dir_listing, parse_repo, GitFile, GitRepo, GitsyMetadata},
+ loud, louder, loudest, normal, normal_noln,
+ settings::{GitsyCli, GitsyRepoDescriptions, GitsySettings, GitsySettingsRepo},
+ template::{DirFilter, FileFilter, TsDateFn, TsTimestampFn},
util::GitsyError,
};
-use git2::{Repository, Error};
+use git2::{Error, Repository};
use rayon::prelude::*;
use std::cmp;
-use std::fs::{File, create_dir};
+use std::fs::{create_dir, File};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicUsize, Ordering};
#[cfg(any(feature = "highlight", feature = "highlight_fast"))]
use syntect::{
- html::{ClassedHTMLGenerator, ClassStyle, css_for_theme_with_class_style},
- parsing::SyntaxSet,
highlighting::ThemeSet,
+ html::{css_for_theme_with_class_style, ClassStyle, ClassedHTMLGenerator},
+ parsing::SyntaxSet,
util::LinesWithEndings,
};
if total.saturating_add($cur) > $settings.limit_total_size.unwrap_or(usize::MAX) {
$action;
}
- }
+ };
}
pub struct GitsyGenerator {
}
impl GitsyGenerator {
- pub fn new(cli: GitsyCli, settings: GitsySettings,
- repo_descriptions: GitsyRepoDescriptions) -> GitsyGenerator {
+ pub fn new(cli: GitsyCli, settings: GitsySettings, repo_descriptions: GitsyRepoDescriptions) -> GitsyGenerator {
GitsyGenerator {
cli,
settings,
let syntax_set = SyntaxSet::load_defaults_newlines();
let syntax = match syntax_set.find_syntax_by_extension(extension) {
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);
for line in LinesWithEndings::from(contents) {
match html_generator.parse_html_for_line_which_includes_newline(line) {
- Ok(_) => {},
+ Ok(_) => {}
Err(_) => {
error!("Warning: failed to apply syntax highlighting.");
return contents.to_string();
- },
+ }
}
}
html_generator.finalize()
loudest!(" - rendering Markdown in {}", path.display());
let (cstr, rendered, pre) = (GitsyGenerator::parse_markdown(&cstr), true, false);
(cstr, rendered, pre)
- },
+ }
#[cfg(any(feature = "highlight", feature = "highlight_fast"))]
Some(x) if settings.syntax_highlight.unwrap_or(false) => {
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),
};
file.contents_safe = rendered;
file.contents_preformatted = pre;
Some(content)
- },
+ }
true => Some(format!("[Binary data ({} bytes)]", blob.content().len())),
};
}
}
fn write_rendered(path: &str, rendered: &str) -> usize {
- let mut file = File::create(path)
- .expect(&format!("Unable to write to output path: {}", path));
+ let mut file = File::create(path).expect(&format!("Unable to write to output path: {}", path));
file.write(rendered.as_bytes())
.expect(&format!("Failed to save rendered html to path: {}", path));
louder!(" - wrote file: {}", path);
template_path.push("**");
template_path.push("*.html");
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_function("ts_to_date", TsDateFn{});
- tera.register_function("ts_to_git_timestamp", TsTimestampFn{});
+ tera.register_filter("only_files", FileFilter {});
+ tera.register_filter("only_dirs", DirFilter {});
+ tera.register_function("ts_to_date", TsDateFn {});
+ tera.register_function("ts_to_git_timestamp", TsTimestampFn {});
Ok(tera)
}
let generated_dt = chrono::offset::Local::now();
let mut global_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 {
- panic!("No Git repositories defined! Please check your configuration file ({})",
- self.cli.path.display());
+ panic!(
+ "No Git repositories defined! Please check your configuration file ({})",
+ self.cli.path.display()
+ );
}
// Sort the repositories by name
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()))
- .unwrap_or(cmp::Ordering::Equal));
+ repo_vec.sort_by(|x, y| {
+ 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
let global_name = "repo list";
- 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))
- }).max(global_name.len());
+ 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))
+ })
+ .max(global_name.len());
loudest!("Global settings:\n{:#?}", &self.settings);
let name = repo_desc.name.as_deref().expect("A configured repository has no name!");
let repo_path = match &repo_desc.path {
- url if url.starts_with("https://") ||
- url.to_str().unwrap_or_default().contains("@") => {
- if self.settings.outputs.cloned_repos.is_none() {
- error!("ERROR: Found remote repo [{}], but `cloned_repos` directory not configured.", name);
- continue;
- };
- let clone_path: PathBuf = [self.settings.outputs.cloned_repos.as_deref().unwrap(),
- name].iter().collect();
- match Repository::open(&clone_path) {
- Ok(r) => {
- // Repo already cloned, so update all refs
- let refs: Vec<String> = r.references()
- .expect(&format!("Unable to enumerate references for repo [{}]", name))
- .map(|x| x.expect(&format!("Found invalid reference in repo [{}]", name))
- .name()
- .expect(&format!("Found unnamed reference in repo: [{}]", name))
- .to_string()).collect();
- r.find_remote("origin")
- .expect(&format!("Clone of repo [{}] missing `origin` remote.", name))
- .fetch(&refs, None, None)
- .expect(&format!("Failed to fetch updates from remote repo [{}]", name));
- clone_path.to_string_lossy().to_string()
- },
- Err(_) => {
- let mut builder = git2::build::RepoBuilder::new();
-
- // TODO: git2-rs's ssh support just doesn't seem to
- // work. It finds the repo, but fails to either
- // decrypt or use the private key.
- //
- //if !url.starts_with("https://") {
- // use secrecy::ExposeSecret;
- // // this must be SSH, which needs credentials.
- // let mut callbacks = git2::RemoteCallbacks::new();
- // callbacks.credentials(|_url, username_from_url, _allowed_types| {
- // //git2::Cred::ssh_key_from_agent(username_from_url.unwrap())
- //
- // let keyfile = format!("{}/.ssh/id_rsa", std::env::var("HOME").unwrap());
- // let passphrase = pinentry::PassphraseInput::with_default_binary().unwrap()
- // .with_description(&format!("Enter passphrase for SSH key {} (repo: {})",
- // keyfile, url.display()))
- // .with_prompt("Passphrase:")
- // .interact().unwrap();
- // git2::Cred::ssh_key(
- // username_from_url.unwrap(),
- // None,
- // Path::new(&keyfile),
- // Some(passphrase.expose_secret()),
- // )
- // });
- // let mut options = git2::FetchOptions::new();
- // options.remote_callbacks(callbacks);
- // builder.fetch_options(options);
- //}
- builder
- .bare(true)
- .clone(&url.to_string_lossy().to_string(), &clone_path)
- .expect(&format!("Failed to clone remote repo [{}]", name));
- clone_path.to_string_lossy().to_string()
- }
+ url if url.starts_with("https://") || url.to_str().unwrap_or_default().contains("@") => {
+ if self.settings.outputs.cloned_repos.is_none() {
+ error!(
+ "ERROR: Found remote repo [{}], but `cloned_repos` directory not configured.",
+ name
+ );
+ continue;
+ };
+ let clone_path: PathBuf = [self.settings.outputs.cloned_repos.as_deref().unwrap(), name]
+ .iter()
+ .collect();
+ match Repository::open(&clone_path) {
+ Ok(r) => {
+ // Repo already cloned, so update all refs
+ let refs: Vec<String> = r
+ .references()
+ .expect(&format!("Unable to enumerate references for repo [{}]", name))
+ .map(|x| {
+ x.expect(&format!("Found invalid reference in repo [{}]", name))
+ .name()
+ .expect(&format!("Found unnamed reference in repo: [{}]", name))
+ .to_string()
+ })
+ .collect();
+ r.find_remote("origin")
+ .expect(&format!("Clone of repo [{}] missing `origin` remote.", name))
+ .fetch(&refs, None, None)
+ .expect(&format!("Failed to fetch updates from remote repo [{}]", name));
+ clone_path.to_string_lossy().to_string()
+ }
+ Err(_) => {
+ let mut builder = git2::build::RepoBuilder::new();
+
+ // TODO: git2-rs's ssh support just doesn't seem to
+ // work. It finds the repo, but fails to either
+ // decrypt or use the private key.
+ //
+ //if !url.starts_with("https://") {
+ // use secrecy::ExposeSecret;
+ // // this must be SSH, which needs credentials.
+ // let mut callbacks = git2::RemoteCallbacks::new();
+ // callbacks.credentials(|_url, username_from_url, _allowed_types| {
+ // //git2::Cred::ssh_key_from_agent(username_from_url.unwrap())
+ //
+ // let keyfile = format!("{}/.ssh/id_rsa", std::env::var("HOME").unwrap());
+ // let passphrase = pinentry::PassphraseInput::with_default_binary().unwrap()
+ // .with_description(&format!("Enter passphrase for SSH key {} (repo: {})",
+ // keyfile, url.display()))
+ // .with_prompt("Passphrase:")
+ // .interact().unwrap();
+ // git2::Cred::ssh_key(
+ // username_from_url.unwrap(),
+ // None,
+ // Path::new(&keyfile),
+ // Some(passphrase.expose_secret()),
+ // )
+ // });
+ // let mut options = git2::FetchOptions::new();
+ // options.remote_callbacks(callbacks);
+ // builder.fetch_options(options);
+ //}
+ builder
+ .bare(true)
+ .clone(&url.to_string_lossy().to_string(), &clone_path)
+ .expect(&format!("Failed to clone remote repo [{}]", name));
+ clone_path.to_string_lossy().to_string()
}
}
+ }
dir => {
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;
- },
+ }
}
dir.to_string_lossy().to_string()
- },
+ }
};
let repo = Repository::open(&repo_path).expect("Unable to find git repository.");
let metadata = GitsyMetadata {
let mut local_ctx = Context::from_serialize(&summary).unwrap();
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 {
local_ctx.insert("site_name", site_name);
if let Some(templ_file) = self.settings.templates.repo_summary.as_deref() {
match tera.render(templ_file, &local_ctx) {
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 {
_ => error!("ERROR: {:?}", x),
},
if let Some(templ_file) = self.settings.templates.branch.as_deref() {
match tera.render(templ_file, &local_ctx) {
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 {
_ => error!("ERROR: {:?}", x),
},
if let Some(templ_file) = self.settings.templates.tag.as_deref() {
match tera.render(templ_file, &local_ctx) {
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 {
_ => error!("ERROR: {:?}", x),
},
for (_id, commit) in &summary.commits {
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() {
match tera.render(templ_file, &local_ctx) {
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 {
_ => error!("ERROR: {:?}", x),
},
#[cfg(any(feature = "highlight", feature = "highlight_fast"))]
if self.settings.templates.file.is_some() {
let ts = ThemeSet::load_defaults();
- let theme = ts.themes.get(repo_desc.syntax_highlight_theme.as_deref()
- .unwrap_or("base16-ocean.light")).expect("Invalid syntax highlighting theme specified.");
+ let theme = ts
+ .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)
.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
// done first because syntax highlighting is very slow.
let files: Vec<&GitFile> = summary.all_files.iter().filter(|x| x.kind == "file").collect();
let atomic_bytes: AtomicUsize = AtomicUsize::new(repo_bytes);
- let _ = files.par_iter().fold(|| Some(0), |acc, file| {
- // 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 mut local_ctx = local_ctx.clone();
-
- let mut local_bytes = 0;
- let cur_repo_bytes = atomic_bytes.load(Ordering::Relaxed);
- 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) {
- true => GitsyGenerator::fill_file_contents(&repo, &file, &repo_desc).expect("Failed to parse file."),
- false => (*file).clone(),
- };
- 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() {
- match tera.render(templ_file, &local_ctx) {
- Ok(rendered) => {
- local_bytes = GitsyGenerator::write_rendered(&self.settings.outputs.file(Some(&summary), Some(&file)), &rendered);
- atomic_bytes.fetch_add(local_bytes, Ordering::Relaxed);
- },
- Err(x) => match x.kind {
- _ => error!("ERROR: {:?}", x),
- },
- }
- }
- local_ctx.remove("file");
- Some(acc.unwrap() + local_bytes)})
+ let _ = files
+ .par_iter()
+ .fold(
+ || Some(0),
+ |acc, file| {
+ // 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 mut local_ctx = local_ctx.clone();
+
+ let mut local_bytes = 0;
+ let cur_repo_bytes = atomic_bytes.load(Ordering::Relaxed);
+ 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) {
+ true => GitsyGenerator::fill_file_contents(&repo, &file, &repo_desc)
+ .expect("Failed to parse file."),
+ false => (*file).clone(),
+ };
+ 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() {
+ match tera.render(templ_file, &local_ctx) {
+ Ok(rendered) => {
+ local_bytes = GitsyGenerator::write_rendered(
+ &self.settings.outputs.file(Some(&summary), Some(&file)),
+ &rendered,
+ );
+ atomic_bytes.fetch_add(local_bytes, Ordering::Relaxed);
+ }
+ Err(x) => match x.kind {
+ _ => error!("ERROR: {:?}", x),
+ },
+ }
+ }
+ local_ctx.remove("file");
+ Some(acc.unwrap() + local_bytes)
+ },
+ )
.while_some() // allow short-circuiting if size limit is reached
.sum::<usize>();
repo_bytes = atomic_bytes.load(Ordering::Relaxed);
continue;
}
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() {
match tera.render(templ_file, &local_ctx) {
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 {
_ => error!("ERROR: {:?}", x),
},
for src_file in repo_desc.asset_files.as_ref().unwrap() {
let src_file = PathBuf::from(repo_path.to_owned() + "/" + src_file);
let mut dst_file = PathBuf::from(&target_dir);
- dst_file.push(src_file.file_name()
- .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()));
+ dst_file.push(src_file.file_name().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) {
repo_bytes += meta.len() as usize;
}
}
repos.push(summary);
- normal!("{}done in {:.2}s ({} bytes)",
- match crate::util::VERBOSITY.load(Ordering::Relaxed) > 1 {
- true => " - ",
- _ => "",
- },
- start_repo.elapsed().as_secs_f32(), repo_bytes);
+ normal!(
+ "{}done in {:.2}s ({} bytes)",
+ match crate::util::VERBOSITY.load(Ordering::Relaxed) > 1 {
+ true => " - ",
+ _ => "",
+ },
+ start_repo.elapsed().as_secs_f32(),
+ repo_bytes
+ );
total_bytes += repo_bytes;
size_check!(repo_desc, 0, total_bytes, break); // break if total is exceeded
}
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();
- 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 {
- 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 {
global_ctx.insert("site_name", site_name);
if let Some(templ_file) = self.settings.templates.repo_list.as_deref() {
match tera.render(templ_file, &global_ctx) {
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 {
_ => error!("ERROR: {:?}", x),
},
match tera.render(templ_file, &global_ctx) {
Ok(rendered) => {
global_bytes += GitsyGenerator::write_rendered(&self.settings.outputs.error(None, None), &rendered);
- },
+ }
Err(x) => match x.kind {
_ => error!("ERROR: {:?}", x),
},
for src_file in self.settings.asset_files.as_ref().unwrap() {
let src_file = PathBuf::from(src_file);
let mut dst_file = PathBuf::from(&target_dir);
- dst_file.push(src_file.file_name()
- .expect(&format!("Failed to copy asset file: {}", src_file.display())));
+ dst_file.push(
+ src_file
+ .file_name()
+ .expect(&format!("Failed to copy asset file: {}", src_file.display())),
+ );
std::fs::copy(&src_file, &dst_file)
.expect(&format!("Failed to copy asset file: {}", src_file.display()));
if let Ok(meta) = std::fs::metadata(dst_file) {
}
total_bytes += global_bytes;
- normal!("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());
+ normal!(
+ "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(())
}
}
-use crate::{error, loud, loudest};
use crate::settings::GitsySettingsRepo;
-use git2::{DiffOptions, Repository, Error};
-use serde::{Serialize, Deserialize};
+use crate::{error, loud, loudest};
+use git2::{DiffOptions, Error, Repository};
+use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;
pub extra: String,
pub additions: usize,
pub deletions: usize,
- pub hunks: Vec<GitDiffHunk>
+ pub hunks: Vec<GitDiffHunk>,
}
#[derive(Serialize, Default)]
pub text: String,
}
-fn walk_file_tree(repo: &git2::Repository, rev: &str, files: &mut Vec<GitFile>,
- depth: usize, max_depth: usize, recurse: bool, prefix: &str) -> Result<(), Error> {
+fn walk_file_tree(
+ 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 tree = obj.peel_to_tree()?;
for entry in tree.iter() {
});
if recurse && depth < (max_depth - 1) && entry.kind() == Some(git2::ObjectType::Tree) {
let prefix = path + "/";
- walk_file_tree(repo, &entry.id().to_string(), files,
- depth+1, max_depth, true, &prefix)?;
+ walk_file_tree(
+ repo,
+ &entry.id().to_string(),
+ files,
+ depth + 1,
+ max_depth,
+ true,
+ &prefix,
+ )?;
}
}
Ok(())
}
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, "")?;
Ok(files)
}
-pub fn parse_repo(repo: &Repository, name: &str, 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!();
+pub fn parse_repo(
+ repo: &Repository,
+ name: &str,
+ 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 commit_count = 0;
let mut history_count = 0;
if let (Some(target), Some(name)) = (refr.target(), refr.shorthand()) {
let id = target.to_string();
match references.contains_key(&id) {
- false => { references.insert(target.to_string(), vec!(name.to_string())); },
- true => { references.get_mut(&id).unwrap().push(name.to_string()); },
+ false => {
+ references.insert(target.to_string(), vec![name.to_string()]);
+ }
+ true => {
+ references.get_mut(&id).unwrap().push(name.to_string());
+ }
}
}
}
loudest!(" - Parsing history:");
for oid in revwalk {
let oid = oid?;
- if commit_count >= settings.limit_commits.unwrap_or(usize::MAX) ||
- history_count >= settings.limit_history.unwrap_or(usize::MAX) {
- break;
+ if commit_count >= settings.limit_commits.unwrap_or(usize::MAX)
+ || history_count >= settings.limit_history.unwrap_or(usize::MAX)
+ {
+ break;
}
commits.insert(oid.to_string(), parse_commit(repo, &oid.to_string())?);
commit_count += 1;
let full_hash = commit.id().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 parent = commit.parent(0)?;
parents.push(parent.id().to_string());
deletions: stats.deletions(),
};
- let alt_refs: Vec<String> = references.get(&commit.id().to_string())
- .map(|x| x.to_owned()).unwrap_or_default();
+ let alt_refs: Vec<String> = references
+ .get(&commit.id().to_string())
+ .map(|x| x.to_owned())
+ .unwrap_or_default();
if history_count < settings.limit_history.unwrap_or(usize::MAX) {
loudest!(" + {} {}", full_hash, first_line(commit.message_bytes()));
ref_name: None,
alt_refs,
author: GitAuthor {
- name: commit.author().name().map(|x| x.to_owned()),
+ name: commit.author().name().map(|x| x.to_owned()),
email: commit.author().email().map(|x| x.to_owned()),
},
summary: Some(first_line(commit.message_bytes())),
// this is a bad idea?
match refr.kind() {
Some(k) if k == git2::ReferenceType::Symbolic => continue,
- _ => {},
+ _ => {}
}
let commit = repo.find_commit(obj.id())?;
let full_hash = obj.id().to_string();
short_hash,
ts_utc: commit.author().when().seconds(),
ts_offset: (commit.author().when().offset_minutes() as i64) * 60,
- parents: vec!(),
+ parents: vec![],
ref_name: Some(name.to_string()),
author: GitAuthor {
name: commit.author().name().map(|x| x.to_owned()),
Some(t) => (t.when().seconds(), (t.when().offset_minutes() as i64) * 60),
_ => (0, 0),
};
- let (author,email) = match commit.tagger() {
- Some(t) => (t.name().map(|x| x.to_owned()),
- t.email().map(|x| x.to_owned())),
+ let (author, email) = match commit.tagger() {
+ Some(t) => (t.name().map(|x| x.to_owned()), t.email().map(|x| x.to_owned())),
_ => (None, None),
};
let summary = match commit.message_bytes() {
ts_utc: ts,
ts_offset: tz,
ref_name: Some(tag.to_string()),
- author: GitAuthor {
- name: author,
- email,
- },
+ author: GitAuthor { name: author, email },
tagged_id: Some(commit.target_id().to_string()),
message: commit.message().map(|x| x.to_string()),
summary,
}
loud!(" - parsed {} tags", tag_count);
- let mut root_files: Vec<GitFile> = vec!();
- let mut all_files: Vec<GitFile> = vec!();
+ let mut root_files: Vec<GitFile> = vec![];
+ let mut all_files: Vec<GitFile> = vec![];
let max_depth = settings.limit_tree_depth.unwrap_or(usize::MAX);
if max_depth > 0 {
loudest!(" - Walking root files");
pub fn parse_commit(repo: &Repository, refr: &str) -> Result<GitObject, Error> {
let obj = repo.revparse_single(refr)?;
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() {
x if x == 1 => {
let parent = commit.parent(0).unwrap();
parents.push(parent.id().to_string());
Some(parent.tree()?)
- },
+ }
x if x > 1 => {
for parent in commit.parents() {
parents.push(parent.id().to_string());
}
None
- },
- _ => {
- None
- },
+ }
+ _ => None,
};
let b = commit.tree()?;
let mut diffopts = DiffOptions::new();
deletions: stats.deletions(),
..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(
&mut |file, _progress| {
let mut file_diff: GitDiffFile = Default::default();
file_diff.newfile = match file.status() {
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() {
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() {
- git2::Delta::Added => file.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()),
+ git2::Delta::Added => file
+ .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.newid = file.new_file().id().to_string();
Some(&mut |_file, _hunk, line| {
let mut files = files.borrow_mut();
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() {
' ' => ("ctx", " "),
'-' => ("del", "-"),
match line.origin() {
'-' => file_diff.deletions += 1,
'+' => file_diff.additions += 1,
- _ => {},
+ _ => {}
}
let line_diff = GitDiffLine {
text: String::from_utf8_lossy(line.content()).to_string(),
};
hunk_diff.lines.push(line_diff);
true
- })
+ }),
)?;
match Rc::try_unwrap(files) {
Ok(files) => {
let files: Vec<GitDiffFile> = files.into_inner();
commit_diff.files = files;
- },
- Err(_) => {},
+ }
+ Err(_) => {}
}
let tree = obj.peel_to_tree()?;
tree_id: Some(tree.id().to_string()),
parents,
ref_name: None,
- alt_refs: vec!(),
+ alt_refs: vec![],
author: GitAuthor {
name: commit.author().name().map(|x| x.to_string()),
email: commit.author().email().map(|x| x.to_string()),
-use clap::Parser;
use crate::error;
-use crate::git::{
- GitFile,
- GitObject,
- GitRepo,
-};
+use crate::git::{GitFile, GitObject, GitRepo};
+use clap::Parser;
use serde::Deserialize;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fs::{create_dir_all, read_dir, read_to_string};
pub fn new() -> Self {
let cli = CliArgs::parse();
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 {
true => config_dir,
false => PathBuf::from("."),
Ok(d) => d,
_ => config_dir.clone(),
};
- crate::util::VERBOSITY.store(match cli.quiet {
- true => 0,
- false => (cli.verbose + 1).into(),
- }, Ordering::Relaxed);
+ crate::util::VERBOSITY.store(
+ match cli.quiet {
+ true => 0,
+ false => (cli.verbose + 1).into(),
+ },
+ Ordering::Relaxed,
+ );
GitsyCli {
path: config_path,
dir: config_dir,
}
}
-
#[derive(Deserialize, Debug)]
pub struct GitsySettingsTemplates {
pub path: PathBuf,
(true, true) => {
let name = repo.map(|x| &x.name).unwrap();
tmpl_str.replace("%REPO%", name)
- },
+ }
(true, false) => {
panic!("%REPO% variable not available for output path: {}", tmpl_str);
}
(true, true) => {
let name = obj.map(|x| &x.$id).unwrap();
tmpl_str.replace("%ID%", name)
- },
+ }
(true, false) => {
panic!("%ID% variable not available for output path: {}", tmpl_str);
}
match $is_dir {
true => {
let _ = create_dir_all(&path);
- },
+ }
false => {
if let Some(dir) = path.parent() {
let _ = create_dir_all(dir);
}
- },
+ }
}
path.to_str()
.expect(&format!("Output is not a valid path: {}", path.display()))
//step_map_first!(boil_in_wort, Boil, Wort, |b: &Boil| { b.wort_start() });
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!(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/");
}
#[derive(Clone, Deserialize, Default, Debug)]
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
- 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)
.expect(&format!("Unable to set working directory to: {}", cli.dir.display()));
}
// Get a list of all remaining TOML "tables" in the file.
// 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 table_keys: Vec<String> = settings_raw.iter().filter_map(|x| match x.1.is_table() {
- true => match reserved_keys.contains(&x.0.as_str()) {
- false => Some(x.0.clone()),
- true => None,
- },
- false => None
- }).collect();
+ 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()) {
+ false => Some(x.0.clone()),
+ true => None,
+ },
+ false => None,
+ })
+ .collect();
// Try to convert each unknown "table" into a repo struct, and
// save the ones that are successful. If no repo name is
let mut repo_descriptions: HashSet<GitsySettingsRepo> = HashSet::new();
macro_rules! global_to_repo {
($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 {
let v = settings_raw.get(k).unwrap();
match toml::from_str::<GitsySettingsRepo>(&v.to_string()) {
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, syntax_highlight);
global_to_repo!(settings, repo, syntax_highlight_theme);
global_to_repo!(settings, repo, limit_total_size);
repo_descriptions.insert(repo);
- },
+ }
Err(e) => {
error!("Failed to parse repo [{}]: {:?}", k, e);
- },
+ }
}
}
});
}
}
- },
- _ => {},
+ }
+ _ => {}
}
(settings, repo_descriptions)
}
-use chrono::{
- DateTime,
- offset::FixedOffset,
- naive::NaiveDateTime,
-};
use crate::git::GitFile;
+use chrono::{naive::NaiveDateTime, offset::FixedOffset, DateTime};
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 {
let offset = offset.unwrap_or(0);
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 {
Some(f) => dt_tz.format(&f).to_string(),
None => dt_tz.format("%Y-%m-%d").to_string(),
fn ts_to_git_timestamp(ts: i64, offset: Option<i64>) -> String {
let offset = offset.unwrap_or(0);
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()
}
pub struct FileFilter;
impl Filter for FileFilter {
- fn filter(&self, value: &Value, _args: &HashMap<String, Value>
- ) -> Result<Value, tera::Error> {
+ fn filter(&self, value: &Value, _args: &HashMap<String, Value>) -> Result<Value, tera::Error> {
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() {
- "file" => Some(x.clone()),
- _ => None,
- }).collect();
+ let file_list: Vec<GitFile> = file_list
+ .iter()
+ .filter_map(|x| match x.kind.as_str() {
+ "file" => Some(x.clone()),
+ _ => None,
+ })
+ .collect();
Ok(to_value(file_list).unwrap())
}
}
pub struct DirFilter;
impl Filter for DirFilter {
- fn filter(&self, value: &Value, _args: &HashMap<String, Value>
- ) -> Result<Value, tera::Error> {
+ fn filter(&self, value: &Value, _args: &HashMap<String, Value>) -> Result<Value, tera::Error> {
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() {
- "dir" => Some(x.clone()),
- _ => None,
- }).collect();
+ let file_list: Vec<GitFile> = file_list
+ .iter()
+ .filter_map(|x| match x.kind.as_str() {
+ "dir" => Some(x.clone()),
+ _ => None,
+ })
+ .collect();
Ok(to_value(file_list).unwrap())
}
}