diff options
author | Matthias Beyer <mail@beyermatthias.de> | 2023-03-02 10:30:23 +0100 |
---|---|---|
committer | Matthias Beyer <mail@beyermatthias.de> | 2023-03-03 09:31:42 +0100 |
commit | 0f0d1dc7bdcc45c7dc75c5f5ecac12e85a5ceba7 (patch) | |
tree | b43bea47f059c48973ca702ef80c80636b0b6758 | |
parent | 29cdf8e172d73716dfe50e0fb58a2d26033abce3 (diff) |
Add "show" command
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
-rw-r--r-- | src/cli.rs | 20 | ||||
-rw-r--r-- | src/command/mod.rs | 3 | ||||
-rw-r--r-- | src/command/show.rs | 139 | ||||
-rw-r--r-- | src/main.rs | 6 |
4 files changed, 168 insertions, 0 deletions
@@ -85,6 +85,13 @@ pub enum Command { #[clap(long, default_value_t = false)] allow_dirty: bool, }, + + Show { + #[clap(long)] + format: Option<ShowFormat>, + #[clap(subcommand)] + range: Option<ShowRange>, + }, } fn text_provider_parser(s: &str) -> Result<TextProvider, String> { @@ -159,3 +166,16 @@ pub enum VersionSpec { custom: String, }, } + +#[derive(Debug, Clone, PartialEq, Eq, clap::ValueEnum)] +pub enum ShowFormat { + Text, + Json, +} + +#[derive(Clone, Debug, Subcommand)] +pub enum ShowRange { + Unreleased, + Exact { exact: String }, + Range { from: String, until: String }, +} diff --git a/src/command/mod.rs b/src/command/mod.rs index f5c3e48..e9537fd 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -14,6 +14,9 @@ mod release_command; pub use self::release_command::ReleaseCommand; pub use self::release_command::VersionData; +mod show; +pub use self::show::Show; + mod verify_metadata_command; pub use self::verify_metadata_command::VerifyMetadataCommand; diff --git a/src/command/show.rs b/src/command/show.rs new file mode 100644 index 0000000..18250d2 --- /dev/null +++ b/src/command/show.rs @@ -0,0 +1,139 @@ +use std::{ + io::BufReader, + path::{Path, PathBuf}, +}; + +use crate::{ + cli::{ShowFormat, ShowRange}, + config::Configuration, + error::{Error, FragmentError}, + fragment::Fragment, +}; + +#[derive(Debug, typed_builder::TypedBuilder)] +pub struct Show { + format: Option<crate::cli::ShowFormat>, + range: Option<ShowRange>, +} + +impl crate::command::Command for Show { + fn execute(self, workdir: &Path, config: &Configuration) -> Result<(), Error> { + let walk_dir = |path| { + walkdir::WalkDir::new(path) + .follow_links(false) + .max_open(100) + .same_file_system(true) + .into_iter() + }; + + let result_dir_entry_to_pathbuf = |rde: Result<walkdir::DirEntry, _>| match rde { + Ok(de) => de.path().is_file().then(|| de.path().to_path_buf()).map(Ok), + Err(e) => Some(Err(Error::from(e))), + }; + + let is_gitkeep = |rpath: &Result<PathBuf, _>| match rpath { + Ok(path) => path.ends_with(".gitkeep"), + Err(_) => true, + }; + + let pathes = match self.range { + None | Some(ShowRange::Unreleased) => { + let unreleased_dir_path = workdir + .join(config.fragment_dir()) + .join(crate::consts::UNRELEASED_DIR_NAME); + walk_dir(unreleased_dir_path) + .filter_map(result_dir_entry_to_pathbuf) + .filter(|r| !is_gitkeep(r)) + .collect::<Result<Vec<PathBuf>, Error>>()? + } + Some(ShowRange::Exact { exact }) => { + let path = workdir.join(config.fragment_dir()).join(&exact); + if !path.exists() { + return Err(Error::ExactVersionDoesNotExist { version: exact }); + } + walk_dir(path) + .filter_map(result_dir_entry_to_pathbuf) + .filter(|r| !is_gitkeep(r)) + .collect::<Result<Vec<PathBuf>, Error>>()? + } + Some(ShowRange::Range { from, until }) => { + let from = semver::Version::parse(&from)?; + let until = semver::Version::parse(&until)?; + + let fragment_dir_path = workdir.join(config.fragment_dir()); + walk_dir(fragment_dir_path) + .filter_entry(|de| { + log::debug!("Looking at {de:?}"); + if de.path().is_dir() { + true + } else if de.path().is_file() { + de.path().components().any(|comp| match comp { + std::path::Component::Normal(osstr) => osstr + .to_str() + .map(|s| { + if let Ok(version) = semver::Version::parse(s) { + version > from && version < until + } else { + false + } + }) + .unwrap_or(false), + _ => false, + }) + } else { + false + } + }) + .filter_map(result_dir_entry_to_pathbuf) + .filter(|r| !is_gitkeep(r)) + .collect::<Result<Vec<PathBuf>, Error>>()? + } + }; + + let fragments = pathes.into_iter().map(|path| { + std::fs::OpenOptions::new() + .read(true) + .create(false) + .write(false) + .open(&path) + .map_err(FragmentError::from) + .map(BufReader::new) + .and_then(|mut reader| { + Fragment::from_reader(&mut reader).map(|f| (path.to_path_buf(), f)) + }) + .map_err(|e| Error::FragmentError(e, path.to_path_buf())) + }); + + match self.format { + None | Some(ShowFormat::Text) => pretty_print(fragments), + Some(ShowFormat::Json) => json_print(fragments), + } + } +} + +fn pretty_print( + mut iter: impl Iterator<Item = Result<(PathBuf, Fragment), Error>>, +) -> Result<(), Error> { + use std::io::Write; + + let out = std::io::stdout(); + let mut output = out.lock(); + + iter.try_for_each(|fragment| { + let (path, fragment) = fragment?; + writeln!(output, "{}", path.display())?; + fragment.header().iter().try_for_each(|(key, value)| { + writeln!(output, "{key}: {value}", value = value.display())?; + Ok(()) as Result<(), Error> + })?; + + writeln!(output, "{text}", text = fragment.text())?; + Ok(()) + }) +} + +fn json_print( + _iter: impl Iterator<Item = Result<(PathBuf, Fragment), Error>>, +) -> Result<(), Error> { + unimplemented!() +} diff --git a/src/main.rs b/src/main.rs index a977b7e..0802a24 100644 --- a/src/main.rs +++ b/src/main.rs @@ -88,6 +88,12 @@ fn main() -> miette::Result<()> { .allow_dirty(allow_dirty) .build() .execute(&repo_workdir_path, &config)?, + + Command::Show { format, range } => crate::command::Show::builder() + .format(format) + .range(range) + .build() + .execute(&repo_workdir_path, &config)?, } Ok(()) |