summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias Beyer <mail@beyermatthias.de>2023-03-02 10:30:23 +0100
committerMatthias Beyer <mail@beyermatthias.de>2023-03-03 09:31:42 +0100
commit0f0d1dc7bdcc45c7dc75c5f5ecac12e85a5ceba7 (patch)
treeb43bea47f059c48973ca702ef80c80636b0b6758
parent29cdf8e172d73716dfe50e0fb58a2d26033abce3 (diff)
Add "show" command
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
-rw-r--r--src/cli.rs20
-rw-r--r--src/command/mod.rs3
-rw-r--r--src/command/show.rs139
-rw-r--r--src/main.rs6
4 files changed, 168 insertions, 0 deletions
diff --git a/src/cli.rs b/src/cli.rs
index 08f941c..c0b1301 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -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(())