- Commit
- 87c47414954c3830e5ebb81f40cd65c5658bf68f
- Parent
- a028ff9a02d7dd7c908d305af9a6e55f59a1385d
- Author
- Pablo <pablo-pie@riseup.net>
- Date
Improved the documentation
Implemented better usage messages and added a man page
Yet another static site generator for Git 🙀️
Improved the documentation
Implemented better usage messages and added a man page
6 files changed, 210 insertions, 78 deletions
Status | Name | Changes | Insertions | Deletions |
Modified | README.md | 2 files changed | 37 | 12 |
Modified | src/command.rs | 2 files changed | 45 | 24 |
Modified | src/config.rs | 2 files changed | 1 | 8 |
Modified | src/log.rs | 2 files changed | 33 | 10 |
Modified | src/main.rs | 2 files changed | 23 | 24 |
Added | yagit.1 | 1 file changed | 71 | 0 |
diff --git a/README.md b/README.md @@ -13,14 +13,20 @@ simple feature set: For a live example please see <https://git.pablopie.xyz>! -### Customizing the HTML Output +## Usage -The user is expected to modify the source code to customize the HTML output, -_no templating system is provided_. The idea is that instead of relying in a -complex and inflexible HTML templating systems, users should fork the -application to adapt it for their own needs. +yagit maintains a store of Git repositories at `REPOS_DIR/` and +renders HTML pages for such repositories at the location `OUTPUT_DIR/`. -## Usage +By default, yagit renders HTML pages in incremental mode: pages for Git +commits and blobs are only renderer if the relevant commits are newer than the +page's last modification date. This option can be disabled with the +`--full-build` flag. + +yagit also maintains a store of Git repositories at `PRIVATE_REPOS_DIR/`, +which can be switched on using the `--private` flag. The HTML pages for +repositories at `PRIVATE_REPOS_DIR/` are rendered at +`OUTPUT_PATH/PRIVATE_OUTPUT_ROOT/`. To render the HTML pages for a single repository using yagit run: @@ -28,23 +34,42 @@ To render the HTML pages for a single repository using yagit run: $ yagit render REPO_NAME ``` -yagit will generate the HTML pages for `REPOS_DIR/REPO_NAME` at -`OUTPUT_PATH/REPO_NAME`. yagit will also generate an index of all git -repositories in `REPOS_DIR` at `OUTPUT_PATH/index.html`. - To render HTML pages for all repositories at `REPOS_DIR` run: ```console $ yagit render-batch ``` +To initiliaze an empty repository at repository store run + +```console +$ yagit init REPO_NAME +``` + +For more information check the `yagit.1` man page. + +## Configuration + +A number of configuration options is provided at compile-time. See +`src/config.rs`. + +### Customizing the HTML Output + +The user is expected to modify the source code to customize the HTML output, +_no templating system is provided_. The idea is that instead of relying in a +complex and inflexible HTML templating systems, users should fork the +application to adapt it for their own needs. + ## Installation -yagit can be installed via Cargo by cloning this repository, as in: +yagit can be installed from source using the following commands: ```console $ git clone git://git.pablopie.xyz/yagit -$ cargo install --path ./yagit +$ cargo build --release +# install -m 755 ./target/release/yagit /usr/bin/yagit +# install -m 644 ./yagit.1 /usr/share/man/man1/yagit.1 +# mandb ``` ### Build Dependencies
diff --git a/src/command.rs b/src/command.rs @@ -1,5 +1,11 @@ use std::{env, ops::BitOrAssign}; -use crate::log; + +const RENDER_BATCH_CMD: &str = "render-batch"; +const RENDER_CMD: &str = "render"; +const INIT_CMD: &str = "init"; + +const FULL_BUILD_FLAG: &str = "--full-build"; +const PRIVATE_FLAG: &str = "--private"; #[derive(Clone, Debug)] pub struct Cmd { @@ -7,6 +13,13 @@ pub struct Cmd { pub flags: Flags, } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum CmdTag { + RenderBatch, + Render, + Init, +} + #[derive(Clone, Debug)] pub enum SubCmd { RenderBatch, @@ -22,50 +35,41 @@ pub enum SubCmd { impl Cmd { pub fn parse() -> Result<(Self, String), ()> { let mut args = env::args(); - - #[derive(Clone, Copy, Debug, PartialEq, Eq)] - enum CmdTag { - RenderBatch, - Render, - Init, - } - let program_name = args.next().unwrap(); let mut flags = Flags::EMPTY; - let cmd = loop { + let tag = loop { match args.next() { - Some(arg) if arg == "render-batch" => break CmdTag::RenderBatch, - Some(arg) if arg == "render" => break CmdTag::Render, - Some(arg) if arg == "init" => break CmdTag::Init, + Some(arg) if arg == RENDER_BATCH_CMD => break CmdTag::RenderBatch, + Some(arg) if arg == RENDER_CMD => break CmdTag::Render, + Some(arg) if arg == INIT_CMD => break CmdTag::Init, - // TODO: documment these flags - Some(arg) if arg == "--full-build" => { + Some(arg) if arg == FULL_BUILD_FLAG => { flags |= Flags::FULL_BUILD; } - Some(arg) if arg == "--private" => { + Some(arg) if arg == PRIVATE_FLAG => { flags |= Flags::PRIVATE; } Some(arg) if arg.starts_with("--") => { errorln!("Unknown flag {arg:?}"); - log::usage(&program_name); + usage(&program_name, None); return Err(()); } Some(arg) => { errorln!("Unknown subcommand {arg:?}"); - log::usage(&program_name); + usage(&program_name, None); return Err(()); } None => { errorln!("No subcommand provided"); - log::usage(&program_name); + usage(&program_name, None); return Err(()); } } }; - let sub_cmd = match cmd { + let sub_cmd = match tag { CmdTag::RenderBatch => { SubCmd::RenderBatch } @@ -74,7 +78,7 @@ impl Cmd { name } else { errorln!("No repository name providade"); - log::usage(&program_name); + usage(&program_name, Some(tag)); return Err(()); }; @@ -85,7 +89,7 @@ impl Cmd { name } else { errorln!("No repository name providade"); - log::usage(&program_name); + usage(&program_name, Some(tag)); return Err(()); }; @@ -93,7 +97,7 @@ impl Cmd { dsc } else { errorln!("No description providade"); - log::usage(&program_name); + usage(&program_name, Some(tag)); return Err(()); }; @@ -103,7 +107,7 @@ impl Cmd { if args.next().is_some() { warnln!("Additional command line arguments provided. Ignoring trailing arguments..."); - log::usage(&program_name); + usage(&program_name, Some(tag)); } Ok((Self { sub_cmd, flags, }, program_name)) @@ -135,3 +139,20 @@ impl BitOrAssign for Flags { self.0 |= rhs.0; } } + +fn usage(program_name: &str, tag: Option<CmdTag>) { + match tag { + None => { + usageln!("{program_name} [{FULL_BUILD_FLAG}] [{PRIVATE_FLAG}] <command> [<args>]"); + } + Some(CmdTag::RenderBatch) => { + usageln!("{program_name} [{FULL_BUILD_FLAG}] [{PRIVATE_FLAG}] {RENDER_BATCH_CMD}"); + } + Some(CmdTag::Render) => { + usageln!("{program_name} [{FULL_BUILD_FLAG}] [{PRIVATE_FLAG}] {RENDER_CMD} <repo-name>"); + } + Some(CmdTag::Init) => { + usageln!("{program_name} [{PRIVATE_FLAG}] {INIT_CMD} <repo-name>"); + } + } +}
diff --git a/src/config.rs b/src/config.rs @@ -21,14 +21,7 @@ pub const OUTPUT_PATH: &str = "/var/www/git"; #[cfg(debug_assertions)] pub const OUTPUT_PATH: &str = "./site"; - -#[cfg(not(debug_assertions))] -pub const PRIVATE_OUTPUT_PATH: &str = "/var/www/git/private"; - -#[cfg(debug_assertions)] -pub const PRIVATE_OUTPUT_PATH: &str = "./site/private"; - -pub const PRIVATE_OUTPUT_ROOT: &str = "/private"; +pub const PRIVATE_OUTPUT_ROOT: &str = "private/"; #[cfg(not(debug_assertions))] pub const GIT_USER: &str = "git";
diff --git a/src/log.rs b/src/log.rs @@ -9,6 +9,7 @@ const BOLD_WHITE: &str = "\u{001b}[1;37m"; const BOLD_BLUE: &str = "\u{001b}[1;34m"; const BOLD_RED: &str = "\u{001b}[1;31m"; const BOLD_YELLOW: &str = "\u{001b}[1;33m"; +const UNDERLINE: &str = "\u{001b}[4m"; const RESET: &str = "\u{001b}[0m"; static mut NEEDS_NEWLINE: bool = false; @@ -23,6 +24,7 @@ pub(crate) enum Level { Error, Info, Warn, + Usage, } struct Counter { @@ -81,6 +83,23 @@ pub(crate) fn log(level: Level, args: &Arguments<'_>, newline: bool) { } if !newline { let _ = stdout.flush(); } } + Level::Usage => unsafe { + let mut stdout = io::stdout(); + + if NEEDS_NEWLINE { + let _ = writeln!(stdout); + } + + let _ = write!(stdout, "{BOLD_YELLOW}USAGE:{RESET} "); + if newline { + let _ = writeln!(stdout, "{}", args); + let _ = writeln!(stdout, " For more information check the {UNDERLINE}yagit{RESET} man page."); + log_job_counter(); + } else { + let _ = write!(stdout, "{}", args); + } + if !newline { let _ = stdout.flush(); } + } } if !newline { @@ -174,11 +193,12 @@ macro_rules! infoln { #[macro_export] macro_rules! info_done { + // info_done!(); () => ({ $crate::log::info_done(None); }); - // infoln!("a {} event", "log"); + // info_done!("terminator"); ($($arg:tt)+) => ({ $crate::log::info_done(Some(&std::format_args!($($arg)+))); }); @@ -200,7 +220,7 @@ macro_rules! job_counter_increment { #[macro_export] macro_rules! error { - // info!("a {} event", "log"); + // error!("a {} event", "log"); ($($arg:tt)+) => ({ $crate::log::log( $crate::log::Level::Error, @@ -224,7 +244,7 @@ macro_rules! errorln { #[macro_export] macro_rules! warnln { - // info!("a {} event", "log"); + // warnln!("a {} event", "log"); ($($arg:tt)+) => ({ $crate::log::log( $crate::log::Level::Warn, @@ -234,11 +254,14 @@ macro_rules! warnln { }); } -pub fn usage(program_name: &str) { - let mut stderr = io::stderr(); - let _ = writeln!( - stderr, - r#"{BOLD_YELLOW}USAGE:{RESET} {program_name} render REPO_PATH OUTPUT_PATH - {program_name} render-batch BATCH_PATH OUTPUT_PATH"# - ); +#[macro_export] +macro_rules! usageln { + // usageln!("a {} event", "log"); + ($($arg:tt)+) => ({ + $crate::log::log( + $crate::log::Level::Usage, + &std::format_args!($($arg)+), + true, + ); + }); }
diff --git a/src/main.rs b/src/main.rs @@ -334,7 +334,9 @@ impl<'repo> RepoRenderer<'repo> { } let (output_path, output_root) = if flags.private() { - (PathBuf::from(config::PRIVATE_OUTPUT_PATH), config::PRIVATE_OUTPUT_ROOT) + let mut output_path = PathBuf::from(config::OUTPUT_PATH); + output_path.push(config::PRIVATE_OUTPUT_ROOT); + (output_path, config::PRIVATE_OUTPUT_ROOT) } else { (PathBuf::from(config::OUTPUT_PATH), "") }; @@ -381,20 +383,20 @@ impl<'repo> RepoRenderer<'repo> { } writeln!(f, "<nav>")?; writeln!(f, "<ul>")?; - writeln!(f, "<li{class}><a href=\"{root}/{name}/index.html\">summary</a></li>", + writeln!(f, "<li{class}><a href=\"/{root}{name}/index.html\">summary</a></li>", root = self.output_root, name = Escaped(&self.name), class = if matches!(title, PageTitle::Summary { .. }) { " class=\"nav-selected\"" } else { "" })?; - writeln!(f, "<li{class}><a href=\"{root}/{name}/{COMMIT_SUBDIR}/index.html\">log</a></li>", + writeln!(f, "<li{class}><a href=\"/{root}{name}/{COMMIT_SUBDIR}/index.html\">log</a></li>", root = self.output_root, name = Escaped(&self.name), class = if matches!(title, PageTitle::Log { .. } | PageTitle::Commit { .. }) { " class=\"nav-selected\"" } else { "" })?; - writeln!(f, "<li{class}><a href=\"{root}/{name}/{TREE_SUBDIR}/index.html\">tree</a></li>", + writeln!(f, "<li{class}><a href=\"/{root}{name}/{TREE_SUBDIR}/index.html\">tree</a></li>", root = self.output_root, name = Escaped(&self.name), class = if matches!(title, PageTitle::TreeEntry { .. }) { " class=\"nav-selected\"" } else { "" })?; if self.license.is_some() { - writeln!(f, "<li{class}><a href=\"{root}/{name}/license.html\">license</a></li>", + writeln!(f, "<li{class}><a href=\"/{root}{name}/license.html\">license</a></li>", root = self.output_root, name = Escaped(&self.name), class = if matches!(title, PageTitle::License { .. }) { " class=\"nav-selected\"" } else { "" })?; @@ -518,7 +520,7 @@ impl<'repo> RepoRenderer<'repo> { writeln!( &mut f, - "<tr><td><a href=\"{root}/{name}/{TREE_SUBDIR}/{path}.html\">{path}</a></td></tr>", + "<tr><td><a href=\"/{root}{name}/{TREE_SUBDIR}/{path}.html\">{path}</a></td></tr>", root = self.output_root, name = Escaped(&self.name), path = Escaped(&path.to_string_lossy()), @@ -541,7 +543,7 @@ impl<'repo> RepoRenderer<'repo> { writeln!( &mut f, - "<tr><td><a href=\"{root}/{name}/{TREE_SUBDIR}/{path}/index.html\" class=\"subtree\">{path}/</a></td></tr>", + "<tr><td><a href=\"/{root}{name}/{TREE_SUBDIR}/{path}/index.html\" class=\"subtree\">{path}/</a></td></tr>", root = self.output_root, name = Escaped(&self.name), path = Escaped(&path.to_string_lossy()), @@ -656,7 +658,7 @@ impl<'repo> RepoRenderer<'repo> { writeln!(&mut f, "<td align=\"right\"></td>")?; writeln!(&mut f, "</tr>")?; writeln!(&mut f, "<tr>")?; - writeln!(&mut f, "<td><a href=\"{root}/{name}/{BLOB_SUBDIR}/{path}\">{path}</a></td>", + writeln!(&mut f, "<td><a href=\"/{root}{name}/{BLOB_SUBDIR}/{path}\">{path}</a></td>", root = self.output_root, name = Escaped(&self.name), path = Escaped(&path.to_string_lossy()))?; @@ -759,7 +761,7 @@ impl<'repo> RepoRenderer<'repo> { writeln!(&mut f, "<div>")?; writeln!( &mut f, - "<span class=\"commit-heading\"><a href=\"{root}/{name}/{COMMIT_SUBDIR}/{id}.html\">{shorthand_id}</a> — {author}</span>", + "<span class=\"commit-heading\"><a href=\"/{root}{name}/{COMMIT_SUBDIR}/{id}.html\">{shorthand_id}</a> — {author}</span>", root = self.output_root, name = Escaped(&self.name), )?; @@ -927,7 +929,7 @@ impl<'repo> RepoRenderer<'repo> { writeln!(&mut f, "<dl>")?; writeln!(&mut f, "<dt>Commit</dt>")?; - writeln!(&mut f, "<dd><a href=\"{root}/{name}/{COMMIT_SUBDIR}/{id}.html\">{id}<a/><dd>", + writeln!(&mut f, "<dd><a href=\"/{root}{name}/{COMMIT_SUBDIR}/{id}.html\">{id}<a/><dd>", root = self.output_root, name = Escaped(&self.name), id = commit.id())?; @@ -935,7 +937,7 @@ impl<'repo> RepoRenderer<'repo> { writeln!(&mut f, "<dt>Parent</dt>")?; writeln!( &mut f, - "<dd><a href=\"{root}/{name}/{COMMIT_SUBDIR}/{id}.html\">{id}<a/><dd>", + "<dd><a href=\"/{root}{name}/{COMMIT_SUBDIR}/{id}.html\">{id}<a/><dd>", root = self.output_root, name = Escaped(&self.name), id = parent.id() @@ -1037,7 +1039,7 @@ impl<'repo> RepoRenderer<'repo> { Delta::Added => { writeln!( &mut f, - "<pre><b>diff --git /dev/null b/<a href=\"{root}/{name}/{TREE_SUBDIR}/{new_path}.html\">{new_path}</a></b>", + "<pre><b>diff --git /dev/null b/<a href=\"/{root}{name}/{TREE_SUBDIR}/{new_path}.html\">{new_path}</a></b>", root = self.output_root, name = Escaped(&self.name), new_path = delta_info.new_path.to_string_lossy(), @@ -1053,7 +1055,7 @@ impl<'repo> RepoRenderer<'repo> { _ => { writeln!( &mut f, - "<pre><b>diff --git a/<a id=\"d#{delta_id}\" href=\"{root}/{name}/{TREE_SUBDIR}/{new_path}.html\">{old_path}</a> b/<a href=\"{root}/{name}/{TREE_SUBDIR}/{new_path}.html\">{new_path}</a></b>", + "<pre><b>diff --git a/<a id=\"d#{delta_id}\" href=\"/{root}{name}/{TREE_SUBDIR}/{new_path}.html\">{old_path}</a> b/<a href=\"/{root}{name}/{TREE_SUBDIR}/{new_path}.html\">{new_path}</a></b>", root = self.output_root, name = Escaped(&self.name), new_path = delta_info.new_path.to_string_lossy(), @@ -1391,11 +1393,11 @@ fn render_footer(f: &mut File) -> io::Result<()> { } fn render_index(repos: &[RepoInfo], private: bool) -> io::Result<()> { - let output_path = if private { - config::PRIVATE_OUTPUT_PATH - } else { - config::OUTPUT_PATH - }; + let mut path = PathBuf::from(config::OUTPUT_PATH); + if private { + path.push(config::PRIVATE_OUTPUT_ROOT); + } + path.push("index.html"); let output_root = if private { config::PRIVATE_OUTPUT_ROOT @@ -1403,9 +1405,6 @@ fn render_index(repos: &[RepoInfo], private: bool) -> io::Result<()> { "" }; - let mut path = PathBuf::from(&output_path); - path.push("index.html"); - let mut f = match File::create(&path) { Ok(f) => f, Err(e) => { @@ -1423,7 +1422,7 @@ fn render_index(repos: &[RepoInfo], private: bool) -> io::Result<()> { writeln!(&mut f, "<article>")?; writeln!(&mut f, "<h4>")?; - writeln!(&mut f, "<a href=\"{root}/{repo}/index.html\">{repo}</a>", + writeln!(&mut f, "<a href=\"/{root}{repo}/index.html\">{repo}</a>", root = output_root, repo = Escaped(&repo.name))?; writeln!(&mut f, "</h4>")?; @@ -1556,8 +1555,8 @@ fn main() -> ExitCode { }; info!("Updating global repository index..."); - if let Err(e) = render_index(&repos, cmd.flags.private()) { - errorln!("Failed rendering global repository index: {e}"); + if render_index(&repos, cmd.flags.private()).is_err() { + return ExitCode::FAILURE; } info_done!();
diff --git /dev/null b/yagit.1 @@ -0,0 +1,71 @@ +.Dd April 2, 2025 +.Dt yagit 1 +.Au Pablo +.Sh NAME +.Nm yagit +.Nd Yet another static site generator for Git +.Sh SYNOPSIS +.Nm +.Op Fl \-\-private +.Op Fl \-\-full\-build +render-batch +.Nm +.Op Fl \-\-private +.Op Fl \-\-full\-build +render +.Ar repo-name +.Nm +.Op Fl \-\-private +\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ init\ \ +.Ar repo-name +.Sh DESCRIPTION +.Nm +maintains a store of Git repositories at +.Ar REPOS_DIR/ +and renders HTML pages for such repositories at the location +.Ar OUTPUT_DIR/ +\. + +By default, +.Nm +renders HTML pages in incremental mode: pages for Git +commits and blobs are only renderer if the relevant commits are newer than the +page's last modification date. This option can be disabled with the +.Fl --full-build +flag. + +.Nm +also maintains a store of Git repositories at +.Ar PRIVATE_REPOS_DIR/ +, which can be switched on using the +.Fl --private +flag. The HTML pages for repositories at +.Ar PRIVATE_REPOS_DIR/ +are rendered at +.Ar OUTPUT_PATH/PRIVATE_OUTPUT_ROOT/ +.Sh COMMANDS +.Bl -tag -width Ds +.It \fBrender\-batch\fR +Renders the HTML pages for all repositories at +.Ar REPOS_DIR/ +and updates the index page +.It \fBrender\fR Ar repo\-name +Renders the HTML pages for a repository at +.Ar REPOS_DIR/repo\-name +and updates the index page +.It \fBinit\fR Ar repo\-name +Initializes a bare Git repo at +.Ar REPOS_DIR/repo\-name +.El +.Sh FLAGS +.Bl -tag -width Ds +.It Fl --full-build +Disables incremental builds (re\-renders all HTML pages) +.It Fl --private +Use the +.Ar PRIVATE_REPOS_DIR/ +store instead of +.Ar REPOS_DIR/ +.El +.Sh AUTHORS +.An Pablo Aq Mt pablo-pie@riseup.net