From 6ab59fc4e9b6339afd863da66c7d02fc7405cd4b Mon Sep 17 00:00:00 2001 From: Trevor Bentley Date: Wed, 11 Jan 2023 19:39:14 +0100 Subject: [PATCH] add configurable limits --- settings.toml | 351 ++++++++++++++++++++++++++++++++++++++++-- src/main.rs | 177 ++++++++++++++++----- templates/footer.html | 2 +- templates/header.html | 2 +- templates/repos.html | 2 +- 5 files changed, 475 insertions(+), 59 deletions(-) diff --git a/settings.toml b/settings.toml index 3208f93..7c75188 100644 --- a/settings.toml +++ b/settings.toml @@ -1,26 +1,283 @@ +############################################################################### +## +## Global, site-wide configuration settings +## +############################################################################### + +# A friendly name for the generated site. +# +# This is accessible in all templates. site_name = "Trevor's Repos" + +# The URL where this generated site is hosted. +# +# This is accessible in all templates. site_url = "https://git.trevorbentley.com" + +# A description of the generated site. +# +# This is accessible in all templates. site_description = "A bunch of git repos in a stupid format." -render_markdown = true -syntax_highlight = true -syntax_highlight_theme = "base16-ocean.light" - -asset_files = ["test.html"] - +# List of directories, each of which contains Git repositories to +# index. +# +# For each directory listed here, Itsy-Gitsy imports all of the +# immediate subdirectories as repositories. Each repository is +# imported with its name set to the name of the subdirectory it is in. +# +# Every repository imported in this way has the global setting from +# this file applied. It is not possible to apply per-repository +# settings for bulk-imported repositories. Repositories that require +# specific configuration can be explicitly specified later in this +# file. recursive_repo_dirs = ["repos/"] +# List of files to copy to the site's `global_assets` directory. +# +# The files in this list are copied unmodified, and with the same name, to the +# configured `global_asset` output directory. Use for including global, +# site-wide resources, such as stylesheets, images, and icons. +# +# Currently does NOT support globbing or directories. Each file must be listed +# individually. +# +# This can also be set per-repository, in which case the `repo_assets` output +# directory is used. +asset_files = ["test.html"] + +# Whether to render Markdown files in repos into HTML. +# +# Rendering Markdown can make site generation take more time. It has potential +# security implications, as user-generated input (files in the repo) are +# formatted into HTML and served unescaped. Use with caution. +# +# This can also be set per-repository. +render_markdown = true + +# Whether render files with syntax highlighting, as HTML. +# +# Applying syntax highlighting is CPU-intensive, and greatly increases site +# generation time. It has potential security implications, as user-generated +# input (files in the repo) are formatted into HTML and served unescaped. Use +# with caution. +# +# This can also be set per-repository. +syntax_highlight = true + +# Which theme to use for syntax highlighting colors. +# +# The default themes from Syntect are available, namely: +# +# - `base16-ocean.light` +# - `base16-ocean.dark` +# - `base16-eighties.dark` +# - `base16-mocha.dark` +# - `InspiredGithub` +# - `Solarized (dark)` +# - `Solarized (light)` +# +syntax_highlight_theme = "base16-ocean.light" + +# Limits maximum number of history items (i.e. git log) to parse. +# +# After the limit is reached, no more history items will be processed or stored +# in memory, and no more commits will be output to disk. For large +# repositories, this can increase processing speed and decrease memory and disk +# usage. +# +# This can also be set per-repository. +limit_history = 500 + +# Limits maximum number of commits to output. +# +# When the limit is reached, no more commits are written to disk. Similar to +# `limit_history`, but the entire git log is still read into RAM and provided +# to the templates. +# +# This can also be set per-repository. +limit_commits = 500 + +# Limits maximum number of branches to parse. +# +# When the limit is reached, no more branches are parsed in memory or provided +# to the `branch` template. +# +# Since branch order is unsorted and non-deterministic, the most sensible +# values for this are very large, or 0 (to disable branches). +# +# This can also be set per-repository. +limit_branches = 500 + +# Limits maximum number of tags to parse. +# +# When the limit is reached, no more tags are parsed in memory or provided to +# the `tag` template. +# +# Since tag order is unsorted and non-deterministic, the most sensible values +# for this are very large, or 0 (to disable tags). +# +# This can also be set per-repository. +limit_tags = 500 + +# Limits directory depth to traverse when parsing files. +# +# Limits the number of directories traversed when enumerating files in the +# `all_files` entry, which is passed as part of the repository to each +# template. This can help reduce RAM usage, and potentially disk usage, for +# repositories with a very large number of files or directories. +# +# Set to 0 to disable both `all_files` and `root_files`, i.e. do not parse any +# file listing. If set to 1 or greater, only applies to `all_files`. +# +# This can also be set per-repository. +limit_tree_depth = 20 + +# Limits size of files in repo with content previews. +# +# Only non-binary files smaller than this limit will have their contents +# provided to the `file` template. Large files are still processed, but do not +# include the text contents. +# +# This can also be set per-repository. +limit_file_size = 2097152 + +# Limits size of a single produced repository. +# +# Limits the size of a generated repository preview. This is a low-precision +# limit, terminating generation wherever it happens to be, and may lead to many +# dead links in the final output. +# +# This limit is not strict: generation is terminated after the first file that +# exceeds this limit. The output size might somewhat overflow this limit. +# Also, static repository assets specified in this configuration are NOT +# included in this limit. +# +# This is intended merely as a safety mechanism to prevent massive run-away +# disk usage. +# +# This can also be set per-repository. +limit_repo_size = 52428800 + +# Limits total size of all output. +# +# Limits the size of the total run: the sum of all repositories. This is a +# low-precision limit, terminating generation wherever it happens to be, and +# may lead to many dead links or entirely missing repositories. +# +# This limit is not strict: generation is terminated after the first file that +# exceeds this limit. The output size might somewhat overflow this limit. +# Also, static repository assets specified in this configuration are NOT +# included in this limit. +# +# This is intended merely as a safety mechanism to prevent massive run-away +# disk usage. +# +# This can also be set per-repository. +limit_total_size = 524288000 + + + +############################################################################### +## +## Subsection specifying which files to use as templates. +## +## The individual templates are relative to the `path` directory. Each +## template is a single file using the Tera template engine's format for text +## substitution. +## +############################################################################### [gitsy_templates] + +# Path to a folder containing Tera templates. +# +# All files with a .html extension found under this directory, and its +# immediate children directories, are imported into the Tera template engine. path = "templates/" + +# Template responsible for the list of repositories. +# +# This template is evaluated with the list of all configured repositories. It +# is intended for providing an overview of the available repositories, but the +# full details of each repository are also included. +# +# This template executes one time. repo_list = "repos.html" + +# Template responsible for summarizing a single repository. +# +# This template is evaluated with a single parsed repository, with all repo +# data (commits, branches, etc) available. +# +# This template executes one time per repository. repo_summary = "summary.html" + +# Template responsible for displaying a single commit. +# +# Called once per parsed commit, with both the whole repository and the current +# commit available to the template. +# +# This template executes many times. commit = "commit.html" + +# Template responsible for displaying a single branch. +# +# Called once per parsed branch, with both the whole repository and the current +# branch available to the template. +# +# This template executes many times. branch = "branch.html" + +# Template responsible for displaying a single tag. +# +# Called once per parsed tag, with both the whole repository and the current +# tag available to the template. +# +# This template executes many times. tag = "tag.html" + +# Template responsible for displaying a single file. +# +# Called once per parsed file, with both the whole repository and the current +# file available to the template. +# +# This template executes many times. file = "file.html" + +# Template responsible for displaying a single directory. +# +# Called once per parsed directory, with both the whole repository and the +# current directory available to the template. +# +# This template executes many times. dir = "dir.html" + +# Template responsible for displaying a site-wide error. +# +# Intended for 404 (page not found) errors. If used, you must configure your +# webserver to redirect HTTP errors to the generated error page. +# +# This template executes one time. error = "404.html" + + +############################################################################### +## +## Subsection specifying names of output files/directories. +## +## Each entry pairs with one of the entries in `gitsy_templates` above, +## specifying the directories and filenames for the rendered output, after the +## template engine has performed its substitutions. +## +## All outputs are relative to the `path` directory. +## +## There are currently two supported variables for paths: +## +## * "%REPO%" -- replaced with the name of the currently processing repository +## * "%ID%" -- replaced with the ID of the currently processing object +## +############################################################################### [gitsy_outputs] path = "gen/" repo_list = "repos.html" @@ -32,18 +289,84 @@ file = "%REPO%/file/%ID%.html" syntax_css = "%REPO%/file/syntax.css" dir = "%REPO%/dir/%ID%.html" error = "404.html" + +# Output directory for files specified in global `asset_files`. +# +# Each input file is copied to this directory unmodified. global_assets = "assets/" + +# Output directory for files specified in per-repo `asset_files`. +# +# Each input file is copied to this directory unmodified. repo_assets = "%REPO%/assets/" -[gitsy_extra] -global_user_defined_vars = "whatever" -these_can_also_be_numbers = 5 -or_bools = true + +############################################################################### +## +## Subsection for arbitrary, global user data. +## +## These variables are available to every page template. Add extra metadata +## that you want available site-wide here. +## +############################################################################### +[gitsy_extra] +generated_by = "Itsy-Gitsy" +#global_user_defined_var = "whatever" +#these_can_also_be_numbers = 5 +#or_bools = true +#or_other_toml_types = {like_dicts = "yep, those too"} + + + +############################################################################### +## +## Individual repository configurations. +## +############################################################################### + +# The section name is used as the repository name if no `name` attribute is +# provided. [circadian] +# Path to the Git repository. path = "more_repos/circadian" + +# Name of this repository, if different from the section name. +name = "circadian" + +# A description of this repository. +description = "A repository full of code" + +# URL of website associated with this repository. website = "https://circadian.trevorbentley.com" -attributes = {some_extra_thing = "user defined", visible = false, number_of_bananas = 3} -render_markdown = false -syntax_highlighting = false -asset_files = ["LICENSE"] + +# Dictionary of arbitrary, user-defined attributes associated with the repo. +# +# Specifying as an inline-table requires all keys to be on one line. +# +# Available in all repo-specific page templates. +# +attributes = { status = "active", type = "daemon" } + +# Per-repository settings, same as the global versions described above: + +#render_markdown = false +#syntax_highlighting = false +#syntax_highlight_theme = "base16-ocean.dark" +#limit_history = 500 +#limit_commits = 500 +#limit_branches = 500 +#limit_tags = 500 +#limit_tree_depth = 20 +#limit_file_size = 2097152 +#limit_repo_size = 52428800 +#limit_total_size = 524288000 +#asset_files = ["LICENSE"] + +# An alternative way to specify the user-defined attributes. +# +# This method allows keys to be on their own lines. +# +#[circadian.attributes] +#status = "active" +#type = "daemon" diff --git a/src/main.rs b/src/main.rs index cc5ad0b..67208dc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,12 +25,16 @@ use syntect::{ // TODO: // -// * specify limits +// * verbose output +// * pagination +// * remote repositories // * extra metadata for recursive repo listings? // * all relative paths should be relative to settings.toml // * basic, light, dark, and fancy default themes +// * split into modules +// * parallelize output generation // * automated tests -// * documentation +// * documentation + examples // fn ts_to_date(ts: i64, offset: Option, format: Option) -> String { @@ -117,6 +121,7 @@ struct GitFile { kind: String, is_binary: bool, size: usize, + tree_depth: usize, contents: Option, contents_safe: bool, contents_preformatted: bool, @@ -157,7 +162,7 @@ struct GitDiffLine { } fn walk_file_tree(repo: &git2::Repository, rev: &str, files: &mut Vec, - depth: usize, recurse: bool, prefix: &str) -> Result<(), Error> { + 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() { @@ -186,30 +191,41 @@ fn walk_file_tree(repo: &git2::Repository, rev: &str, files: &mut Vec, mode: entry.filemode(), is_binary, size, + tree_depth: depth, contents: None, contents_safe: false, contents_preformatted: true, }); - if recurse && entry.kind() == Some(git2::ObjectType::Tree) { + if recurse && depth < (max_depth - 1) && entry.kind() == Some(git2::ObjectType::Tree) { let prefix = name + "/"; - walk_file_tree(repo, &entry.id().to_string(), files, depth+1, true, &prefix)?; + walk_file_tree(repo, &entry.id().to_string(), files, + depth+1, max_depth, true, &prefix)?; } } Ok(()) } -fn parse_repo(repo: &Repository, name: &str, metadata: GitsyMetadata) -> Result { +fn parse_repo(repo: &Repository, name: &str, settings: &GitsySettingsRepo, metadata: GitsyMetadata) -> Result { let mut history: Vec = vec!(); let mut branches: Vec = vec!(); let mut tags: Vec = vec!(); let mut commits: BTreeMap = BTreeMap::new(); + let mut commit_count = 0; + let mut history_count = 0; + let mut branch_count = 0; + let mut tag_count = 0; let mut revwalk = repo.revwalk()?; revwalk.set_sorting(git2::Sort::TOPOLOGICAL)?; revwalk.push_head()?; 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; + } commits.insert(oid.to_string(), parse_commit(repo, &oid.to_string())?); + commit_count += 1; let commit = repo.find_commit(oid)?; let obj = repo.revparse_single(&commit.id().to_string())?; let full_hash = commit.id().to_string(); @@ -249,25 +265,35 @@ fn parse_repo(repo: &Repository, name: &str, metadata: GitsyMetadata) -> Result< } } - history.push(GitObject { - full_hash, - short_hash, - ts_utc: commit.author().when().seconds(), - ts_offset: (commit.author().when().offset_minutes() as i64) * 60, - parents, - ref_name: None, - alt_refs, - author: GitAuthor { - 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())), - stats: Some(stats), - ..Default::default() - }); + if history_count < settings.limit_history.unwrap_or(usize::MAX) { + // TODO: this is basically a duplicate of the commit + // array, and really should be pointers to that array + // instead. But it's not a quick task to switch to + // self-referential data structures in Rust. + history.push(GitObject { + full_hash, + short_hash, + ts_utc: commit.author().when().seconds(), + ts_offset: (commit.author().when().offset_minutes() as i64) * 60, + parents, + ref_name: None, + alt_refs, + author: GitAuthor { + 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())), + stats: Some(stats), + ..Default::default() + }); + history_count += 1; + } } for branch in repo.branches(None)? { + if branch_count >= settings.limit_branches.unwrap_or(usize::MAX) { + break; + } let (branch, _branch_type) = branch?; let refr = branch.get(); let name = branch.name()?.unwrap_or("[unnamed]"); @@ -300,8 +326,12 @@ fn parse_repo(repo: &Repository, name: &str, metadata: GitsyMetadata) -> Result< message: commit.message().map(|x| x.to_string()), ..Default::default() }); + branch_count += 1; } for tag in repo.tag_names(None)?.iter() { + if tag_count >= settings.limit_tags.unwrap_or(usize::MAX) { + break; + } let tag = tag.unwrap_or("[unnamed]"); let obj = repo.revparse_single(tag)?; let commit = repo.find_tag(obj.id())?; @@ -335,14 +365,18 @@ fn parse_repo(repo: &Repository, name: &str, metadata: GitsyMetadata) -> Result< summary, ..Default::default() }); + tag_count += 1; } let mut root_files: Vec = vec!(); let mut all_files: Vec = vec!(); - walk_file_tree(&repo, "HEAD", &mut root_files, 0, false, "")?; - // TODO: maybe this should be optional? Walking the whole tree - // could be slow on huge repos. - walk_file_tree(&repo, "HEAD", &mut all_files, 0, true, "")?; + let max_depth = settings.limit_tree_depth.unwrap_or(usize::MAX); + if max_depth > 0 { + walk_file_tree(&repo, "HEAD", &mut root_files, 0, usize::MAX, false, "")?; + // TODO: maybe this should be optional? Walking the whole tree + // could be slow on huge repos. + walk_file_tree(&repo, "HEAD", &mut all_files, 0, max_depth, true, "")?; + } Ok(GitRepo { name: name.to_string(), @@ -543,7 +577,7 @@ fn fill_file_contents(repo: &Repository, file: &GitFile, settings: &GitsySetting fn dir_listing(repo: &Repository, file: &GitFile) -> Result, Error> { let mut files: Vec = vec!(); - walk_file_tree(&repo, &file.id, &mut files, 0, false, "")?; + walk_file_tree(&repo, &file.id, &mut files, 0, usize::MAX, false, "")?; Ok(files) } @@ -646,6 +680,14 @@ struct GitsySettings { templates: GitsySettingsTemplates, #[serde(rename(deserialize = "gitsy_outputs"))] outputs: GitsySettingsOutputs, + limit_history: Option, + limit_commits: Option, + limit_branches: Option, + limit_tags: Option, + limit_tree_depth: Option, + limit_file_size: Option, + limit_repo_size: Option, + limit_total_size: Option, render_markdown: Option, syntax_highlight: Option, syntax_highlight_theme: Option, @@ -751,7 +793,15 @@ struct GitsySettingsRepo { render_markdown: Option, syntax_highlight: Option, syntax_highlight_theme: Option, - attributes: BTreeMap, + attributes: Option>, + limit_history: Option, + limit_commits: Option, + limit_branches: Option, + limit_tags: Option, + limit_tree_depth: Option, + limit_file_size: Option, + limit_repo_size: Option, + limit_total_size: Option, } use std::hash::{Hash, Hasher}; @@ -767,11 +817,12 @@ impl PartialEq for GitsySettingsRepo { } impl Eq for GitsySettingsRepo {} -fn write_rendered(path: &str, rendered: &str) { +fn write_rendered(path: &str, rendered: &str) -> usize { 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)); + rendered.as_bytes().len() } fn main() { @@ -823,6 +874,14 @@ fn main() { render_markdown: settings.render_markdown.clone(), syntax_highlight: settings.syntax_highlight.clone(), syntax_highlight_theme: settings.syntax_highlight_theme.clone(), + limit_history: settings.limit_history.clone(), + limit_commits: settings.limit_commits.clone(), + limit_branches: settings.limit_branches.clone(), + limit_tags: settings.limit_tags.clone(), + limit_tree_depth: settings.limit_tree_depth.clone(), + limit_file_size: settings.limit_file_size.clone(), + limit_repo_size: settings.limit_repo_size.clone(), + limit_total_size: settings.limit_total_size.clone(), ..Default::default() }); } @@ -846,13 +905,25 @@ fn main() { tera.register_function("ts_to_date", TsDateFn{}); tera.register_function("ts_to_git_timestamp", TsTimestampFn{}); + macro_rules! size_check { + ($settings:ident, $cur:ident, $total:expr) => { + if $cur > $settings.limit_repo_size.unwrap_or(usize::MAX) { + break; + } + if $total + $cur > $settings.limit_total_size.unwrap_or(usize::MAX) { + break; + } + } + } + // Create output directory let _ = std::fs::create_dir(settings.outputs.path.to_str().expect("Output path invalid.")); let generated_dt = chrono::offset::Local::now(); - + let mut total_bytes = 0; let mut repos: Vec = vec!(); for repo_desc in &repo_descriptions { + let mut repo_bytes = 0; let dir = &repo_desc.path; match dir.metadata() { Ok(m) if m.is_dir() => {}, @@ -866,9 +937,9 @@ fn main() { description: repo_desc.description.clone(), website: repo_desc.website.clone(), clone: None, - attributes: repo_desc.attributes.clone(), + attributes: repo_desc.attributes.clone().unwrap_or_default(), }; - let summary = parse_repo(&repo, &name, metadata).expect("Failed to analyze repo HEAD."); + let summary = parse_repo(&repo, &name, &repo_desc, metadata).expect("Failed to analyze repo HEAD."); let mut local_ctx = Context::from_serialize(&summary).unwrap(); if let Some(extra) = &settings.extra { @@ -888,7 +959,7 @@ fn main() { match tera.render(&settings.templates.repo_summary.as_deref().unwrap_or("summary.html"), &local_ctx) { Ok(rendered) => { - write_rendered(&settings.outputs.repo_summary(Some(&summary), None), &rendered); + repo_bytes += write_rendered(&settings.outputs.repo_summary(Some(&summary), None), &rendered); }, Err(x) => match x.kind { tera::ErrorKind::TemplateNotFound(_) if settings.templates.repo_summary.is_none() => {}, @@ -897,10 +968,11 @@ fn main() { } for branch in &summary.branches { + size_check!(repo_desc, repo_bytes, total_bytes); local_ctx.insert("branch", branch); match tera.render(&settings.templates.branch.as_deref().unwrap_or("branch.html"), &local_ctx) { Ok(rendered) => { - write_rendered(&settings.outputs.branch(Some(&summary), Some(branch)), &rendered); + repo_bytes += write_rendered(&settings.outputs.branch(Some(&summary), Some(branch)), &rendered); }, Err(x) => match x.kind { tera::ErrorKind::TemplateNotFound(_) if settings.templates.branch.is_none() => {}, @@ -911,13 +983,14 @@ fn main() { } for tag in &summary.tags { + size_check!(repo_desc, repo_bytes, total_bytes); local_ctx.insert("tag", tag); if let Some(commit) = summary.commits.get(tag.tagged_id.as_ref().unwrap()) { local_ctx.insert("commit", &commit); } match tera.render(&settings.templates.tag.as_deref().unwrap_or("tag.html"), &local_ctx) { Ok(rendered) => { - write_rendered(&settings.outputs.tag(Some(&summary), Some(tag)), &rendered); + repo_bytes += write_rendered(&settings.outputs.tag(Some(&summary), Some(tag)), &rendered); }, Err(x) => match x.kind { tera::ErrorKind::TemplateNotFound(_) if settings.templates.tag.is_none() => {}, @@ -929,10 +1002,11 @@ fn main() { } for (_id, commit) in &summary.commits { + size_check!(repo_desc, repo_bytes, total_bytes); local_ctx.try_insert("commit", &commit).expect("Failed to add commit to template engine."); match tera.render(&settings.templates.commit.as_deref().unwrap_or("commit.html"), &local_ctx) { Ok(rendered) => { - write_rendered(&settings.outputs.commit(Some(&summary), Some(commit)), &rendered); + repo_bytes += write_rendered(&settings.outputs.commit(Some(&summary), Some(commit)), &rendered); }, Err(x) => match x.kind { tera::ErrorKind::TemplateNotFound(_) if settings.templates.commit.is_none() => {}, @@ -954,15 +1028,19 @@ fn main() { .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."); - write_rendered(&settings.outputs.syntax_css(Some(&summary), None), css.as_str()); + repo_bytes += write_rendered(&settings.outputs.syntax_css(Some(&summary), None), css.as_str()); } for file in summary.all_files.iter().filter(|x| x.kind == "file") { - let file = fill_file_contents(&repo, &file, &repo_desc).expect("Failed to parse file."); + size_check!(repo_desc, repo_bytes, total_bytes); + let file = match file.size < repo_desc.limit_file_size.unwrap_or(usize::MAX) { + true => 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."); match tera.render(&settings.templates.file.as_deref().unwrap_or("file.html"), &local_ctx) { Ok(rendered) => { - write_rendered(&settings.outputs.file(Some(&summary), Some(&file)), &rendered); + repo_bytes += write_rendered(&settings.outputs.file(Some(&summary), Some(&file)), &rendered); }, Err(x) => match x.kind { tera::ErrorKind::TemplateNotFound(_) if settings.templates.file.is_none() => {}, @@ -973,11 +1051,15 @@ fn main() { } for dir in summary.all_files.iter().filter(|x| x.kind == "dir") { + size_check!(repo_desc, repo_bytes, total_bytes); + if dir.tree_depth >= repo_desc.limit_tree_depth.unwrap_or(usize::MAX) - 1 { + 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."); match tera.render(&settings.templates.dir.as_deref().unwrap_or("dir.html"), &local_ctx) { Ok(rendered) => { - write_rendered(&settings.outputs.dir(Some(&summary), Some(dir)), &rendered); + repo_bytes += write_rendered(&settings.outputs.dir(Some(&summary), Some(dir)), &rendered); }, Err(x) => match x.kind { tera::ErrorKind::TemplateNotFound(_) if settings.templates.dir.is_none() => {}, @@ -998,10 +1080,16 @@ fn main() { 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); + println!("Wrote repo: {} ({} bytes)", name, repo_bytes); + total_bytes += repo_bytes; + size_check!(repo_desc, repo_bytes, total_bytes); } let mut global_ctx = Context::new(); @@ -1023,7 +1111,7 @@ fn main() { match tera.render(&settings.templates.repo_list.as_deref().unwrap_or("repos.html"), &global_ctx) { Ok(rendered) => { - write_rendered(&settings.outputs.repo_list(None, None), &rendered); + total_bytes += write_rendered(&settings.outputs.repo_list(None, None), &rendered); }, Err(x) => match x.kind { tera::ErrorKind::TemplateNotFound(_) if settings.templates.repo_list.is_none() => {}, @@ -1033,7 +1121,7 @@ fn main() { match tera.render(&settings.templates.error.as_deref().unwrap_or("404.html"), &global_ctx) { Ok(rendered) => { - write_rendered(&settings.outputs.error(None, None), &rendered); + total_bytes += write_rendered(&settings.outputs.error(None, None), &rendered); }, Err(x) => match x.kind { tera::ErrorKind::TemplateNotFound(_) if settings.templates.error.is_none() => {}, @@ -1050,6 +1138,11 @@ fn main() { .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 += meta.len() as usize; + } } } + + println!("Total bytes written: {}", total_bytes); } diff --git a/templates/footer.html b/templates/footer.html index f29b415..9d302a7 100644 --- a/templates/footer.html +++ b/templates/footer.html @@ -1,2 +1,2 @@
-Generated by Itsy-Gitsy on {{ts_to_git_timestamp(ts=site_generated_ts, tz=site_generated_offset)}}
+Generated by {{extra | get(key="generated_by", default="Itsy-Gitsy")}} on {{ts_to_git_timestamp(ts=site_generated_ts, tz=site_generated_offset)}}
diff --git a/templates/header.html b/templates/header.html index 24dc11d..3968b07 100644 --- a/templates/header.html +++ b/templates/header.html @@ -1,3 +1,3 @@ Site: {{site_name}}
-Extra settings: {{extra.global_user_defined_vars}}
+Description: {{site_description}}

diff --git a/templates/repos.html b/templates/repos.html index fa0a79d..70b1eb6 100644 --- a/templates/repos.html +++ b/templates/repos.html @@ -2,6 +2,6 @@ {% block content %} {% for repo in repos | sort(attribute="name") -%} -{{ repo.name }} ({{repo.metadata.website}}) [{{repo.metadata.attributes | get(key="some_extra_thing", default="")}}] ({{ts_to_date(ts=repo.history[0].ts_utc, tz=repo.history[0].ts_offset)}})
+{{ repo.name }} ({{repo.metadata.website}}) [{{repo.metadata.attributes | get(key="status", default="unknown")}}] ({{ts_to_date(ts=repo.history[0].ts_utc, tz=repo.history[0].ts_offset)}})
{% endfor -%} {% endblock content %}