- Commit
- 9adc7abcae52ce57ca0c3148b1c453dd5c782e4a
- Parent
- bf0f1d1d21ed78ae47cb99f156d9dd1d7f6e60d0
- Author
- Pablo <pablo-pie@riseup.net>
- Date
Merge branch 'delete-feature'
Yet another static site generator for Git 🙀️
Merge branch 'delete-feature'
6 files changed, 155 insertions, 30 deletions
Status | Name | Changes | Insertions | Deletions |
Modified | README.md | 2 files changed | 6 | 0 |
Modified | TODO.md | 2 files changed | 1 | 2 |
Modified | src/command.rs | 2 files changed | 22 | 2 |
Modified | src/log.rs | 2 files changed | 35 | 8 |
Modified | src/main.rs | 2 files changed | 81 | 16 |
Modified | yagit.1 | 2 files changed | 10 | 2 |
diff --git a/README.md b/README.md @@ -46,6 +46,12 @@ To initiliaze an empty repository at repository store run $ yagit init REPO_NAME ``` +Repositories managed by yagit can be deleted using the `delete` command: + +```console +$ yagit delete REPO_NAME +``` + For more information check the `yagit.1` man page. ## Limitations
diff --git a/TODO.md b/TODO.md @@ -1,4 +1,3 @@ # TODO -* Add functionality: - * subcommand for deleting an existing repo +* add a testing framework
diff --git a/src/command.rs b/src/command.rs @@ -3,6 +3,7 @@ use std::{env, ops::BitOrAssign}; const RENDER_BATCH_CMD: &str = "render-batch"; const RENDER_CMD: &str = "render"; const INIT_CMD: &str = "init"; +const DELETE_CMD: &str = "delete"; const FULL_BUILD_FLAG: &str = "--full-build"; const PRIVATE_FLAG: &str = "--private"; @@ -18,6 +19,7 @@ enum CmdTag { RenderBatch, Render, Init, + Delete, } #[derive(Clone, Debug)] @@ -29,7 +31,10 @@ pub enum SubCmd { Init { repo_name: String, description: String, - } + }, + Delete { + repo_name: String, + }, } impl Cmd { @@ -40,6 +45,7 @@ impl Cmd { 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, + Some(arg) if arg == DELETE_CMD => break CmdTag::Delete, Some(arg) if arg == FULL_BUILD_FLAG => { flags |= Flags::FULL_BUILD; @@ -100,6 +106,17 @@ impl Cmd { SubCmd::Init { repo_name, description, } } + CmdTag::Delete => { + let repo_name = if let Some(name) = args.next() { + name + } else { + errorln!("No repository name providade"); + usage(program_name, Some(tag)); + return Err(()); + }; + + SubCmd::Delete { repo_name, } + } }; if args.next().is_some() { @@ -149,7 +166,10 @@ fn usage(program_name: &str, tag: Option) { usageln!("{program_name} [{FULL_BUILD_FLAG}] [{PRIVATE_FLAG}] {RENDER_CMD} <repo-name>"); } Some(CmdTag::Init) => { - usageln!("{program_name} [{PRIVATE_FLAG}] {INIT_CMD} <repo-name>"); + usageln!("{program_name} [{PRIVATE_FLAG}] {INIT_CMD} <repo-name> <description>"); + } + Some(CmdTag::Delete) => { + usageln!("{program_name} [{PRIVATE_FLAG}] {DELETE_CMD} <repo-name>"); } } }
diff --git a/src/log.rs b/src/log.rs @@ -39,29 +39,48 @@ struct Counter { pub(crate) fn log(level: Level, args: &Arguments<'_>) { match level { Level::Error => { - eprint!("{BOLD_RED} Error{RESET} "); + eprint!("{BOLD_RED} Error{RESET} "); eprintln!("{}", args); // shouldn't print the job counter because we are about to die } Level::Info => { - print!("{BOLD_BLUE} Info{RESET} "); + print!("{BOLD_BLUE} Info{RESET} "); println!("{}", args); log_current_job(); } Level::Warn => { - print!("{BOLD_YELLOW} Warning{RESET} "); + print!("{BOLD_YELLOW} Warning{RESET} "); println!("{}", args); log_current_job(); } Level::Usage => { - print!("{BOLD_YELLOW} Usage{RESET} "); + print!("{BOLD_YELLOW} Usage{RESET} "); println!("{}", args); - println!(" For more information check the {UNDERLINE}yagit(1){RESET} man page."); + println!(" For more information check the {UNDERLINE}yagit(1){RESET} man page."); log_current_job(); } } } +pub(crate) fn query(args: &Arguments<'_>) -> String { + let mut stdout = io::stdout(); + let stdin = io::stdin(); + let mut result = String::new(); + + let _ = write!(stdout, "{BOLD_YELLOW} Confirm{RESET} {} ", args); + let _ = stdout.flush(); + + if stdin.read_line(&mut result).is_err() { + result.clear(); + } else if result.ends_with('\n') { + let _ = result.pop(); + } + + // shouldn't print the job counter because we are should be running the + // 'delete' command, so there are no jobs + result +} + pub fn set_job_count(total: usize) { unsafe { COUNTER.total = total; @@ -87,7 +106,7 @@ pub fn render_done() { let space_padding = "... [/]".len() + 2 * crate::log_floor(COUNTER.total); println!( - "{BOLD_GREEN} Rendered{RESET} {name}{empty:space_padding$}", + "{BOLD_GREEN} Rendered{RESET} {name}{empty:space_padding$}", name = COUNTER.current_repo_name, empty = "", ); @@ -104,7 +123,7 @@ fn log_current_job() { let _ = write!( stdout, - "{BOLD_CYAN}Rendering{RESET} {name}... {BOLD_WHITE}[{count:>padding$}/{total}]{RESET}\r", + "{BOLD_CYAN} Rendering{RESET} {name}... {BOLD_WHITE}[{count:>padding$}/{total}]{RESET}\r", count = COUNTER.count, total = COUNTER.total, padding = crate::log_floor(COUNTER.total), @@ -158,12 +177,20 @@ macro_rules! usageln { }); } +#[macro_export] +macro_rules! query { + // query!("a {}", "question?"); + ($($arg:tt)+) => ({ + $crate::log::query(&std::format_args!($($arg)+)) + }); +} + pub fn finished(duration: Duration) { let duration = duration.as_millis() / 100; let secs = duration / 10; let dsecs = duration % 10; - println!("{BOLD_GREEN} Finished{RESET} Rendering took {secs}.{dsecs}s"); + println!("{BOLD_GREEN} Finished{RESET} Rendering took {secs}.{dsecs}s"); } #[cfg(target_arch = "x86_64")]
diff --git a/src/main.rs b/src/main.rs @@ -5,7 +5,6 @@ use std::{ mem, env, fmt::{self, Display}, - ffi::OsStr, collections::HashMap, time::{Duration, SystemTime, Instant}, process::ExitCode, @@ -28,7 +27,13 @@ use git2::{ use time::{DateTime, Date, FullDate}; use command::{Cmd, SubCmd, Flags}; -use config::{TREE_SUBDIR, BLOB_SUBDIR, COMMIT_SUBDIR}; +use config::{ + OUTPUT_PATH, + PRIVATE_OUTPUT_ROOT, + TREE_SUBDIR, + BLOB_SUBDIR, + COMMIT_SUBDIR +}; use escape::Escaped; #[cfg(not(debug_assertions))] @@ -60,6 +65,7 @@ struct RepoInfo { pub name: String, pub owner: String, pub description: Option<String>, + pub path: PathBuf, pub repo: Repository, pub last_commit: Time, @@ -67,9 +73,8 @@ struct RepoInfo { } impl RepoInfo { - fn open<P, S>(path: P, name: S) -> Result<Self, ()> + fn open<S>(path: PathBuf, name: S) -> Result<Self, ()> where - P: AsRef<Path> + AsRef<OsStr> + fmt::Debug, S: AsRef<str>, { let repo = match Repository::open(&path) { @@ -112,13 +117,9 @@ impl RepoInfo { return Err(()); } - let mut path = PathBuf::from(&path); - if !repo.is_bare() { - path.push(".git"); - } - let owner = { let mut owner_path = path.clone(); + if !repo.is_bare() { owner_path.push(".git"); } owner_path.push("owner"); let mut owner = String::with_capacity(32); @@ -140,6 +141,7 @@ impl RepoInfo { let description = { let mut dsc_path = path.clone(); + if !repo.is_bare() { dsc_path.push(".git"); } dsc_path.push("description"); let mut dsc = String::with_capacity(512); @@ -163,6 +165,7 @@ impl RepoInfo { name: String::from(name.as_ref()), owner, description, + path, repo, first_commit, last_commit, @@ -188,7 +191,7 @@ impl RepoInfo { let repo_name = entry.file_name(); result.push( - RepoInfo::open(&repo_path, repo_name.to_string_lossy())? + RepoInfo::open(repo_path, repo_name.to_string_lossy())? ); } _ => continue, @@ -1638,6 +1641,8 @@ fn main() -> ExitCode { } log::render_done(); } + + log::finished(start.elapsed()); } SubCmd::Render { repo_name } => { let repos = if let Ok(repos) = RepoInfo::index(cmd.flags.private()) { @@ -1672,18 +1677,18 @@ fn main() -> ExitCode { log::render_start("repository index"); if let Err(e) = render_index(&repos, cmd.flags.private()) { - errorln!("Failed rendering global repository index: {e}"); + errorln!("Failed rendering repository index: {e}"); } log::render_done(); log::render_start(&repo.name); - if let Err(e) = renderer.render() { errorln!("Failed rendering pages for {name:?}: {e}", name = renderer.name); } - log::render_done(); + + log::finished(start.elapsed()); } SubCmd::Init { repo_name, description } => { let mut repo_path = if cmd.flags.private() { @@ -1696,8 +1701,6 @@ fn main() -> ExitCode { let mut opts = RepositoryInitOptions::new(); opts.bare(false).no_reinit(true); - infoln!("Initializing empty {repo_name:?} repository in {repo_path:?}"); - if let Err(e) = Repository::init_opts(&repo_path, &opts) { errorln!("Couldn't initialize {repo_name:?}: {e}", e = e.message()); return ExitCode::FAILURE; @@ -1707,9 +1710,71 @@ fn main() -> ExitCode { .is_err() { return ExitCode::FAILURE; } + + infoln!("Initialized empty repository in {repo_path:?}"); + } + SubCmd::Delete { repo_name } => { + let mut repos = if let Ok(repos) = RepoInfo::index(cmd.flags.private()) { + repos + } else { + return ExitCode::FAILURE; + }; + + let mut repo = None; + for i in 0..repos.len() { + if repos[i].name == *repo_name { + repo = Some(repos.remove(i)); + break; + } + } + + if repo.is_none() { + errorln!("Couldnt' find repository {repo_name:?} in {repos_dir:?}"); + return ExitCode::FAILURE; + } + let repo = repo.unwrap(); + + let answer = query!( + "Would you like to remove {repo_path:?}?", + repo_path = repo.path + ); + + if answer != "y" && answer != "Y" { + infoln!("Not deleting {repo_name:?}", repo_name = repo.name); + return ExitCode::SUCCESS; + } + + log::set_job_count(1); // tasks: render index + + if let Err(e) = fs::remove_dir_all(&repo.path) { + errorln!("Couldnt' remove {repo_path:?}: {e}", repo_path = repo.path); + return ExitCode::FAILURE; + } + + log::render_start("repository index"); + infoln!("Removed {repo_path:?}", repo_path = repo.path); + log::render_done(); + + if let Err(e) = render_index(&repos, cmd.flags.private()) { + errorln!("Failed rendering repository index: {e}"); + return ExitCode::FAILURE; + } + + if cmd.flags.private() { + warnln!( + "Did not remove \"{OUTPUT_PATH}/{PRIVATE_OUTPUT_ROOT}{repo_name}\". Run \" rm '{OUTPUT_PATH}/{PRIVATE_OUTPUT_ROOT}{repo_name}' if necessary", + repo_name = repo.name, + ); + } else { + warnln!( + "Did not remove \"{OUTPUT_PATH}/{repo_name}\". Run \"rm '{OUTPUT_PATH}/{repo_name}'\" if necessary", + repo_name = repo.name, + ); + } + + log::finished(start.elapsed()); } } - log::finished(start.elapsed()); ExitCode::SUCCESS }
diff --git a/yagit.1 b/yagit.1 @@ -18,6 +18,10 @@ render .Op Fl \-\-private \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ init\ \ .Ar repo-name +.Nm +.Op Fl \-\-private +\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ delete +.Ar repo-name .Sh DESCRIPTION .Nm maintains a store of Git repositories at @@ -52,9 +56,13 @@ and updates the index page 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 +.It \fBinit\fR Ar repo\-name Ar description +Initializes and configures a Git repo at +.Ar REPOS_DIR/repo\-name +.It \fBdelete\fR Ar repo\-name +Deletes the Git repository at .Ar REPOS_DIR/repo\-name +and re-renders the global repository index .El .Sh FLAGS .Bl -tag -width Ds