Skip to content

Commit

Permalink
Merge #204
Browse files Browse the repository at this point in the history
204: Subcommand show r=matthiasbeyer a=matthiasbeyer

Partly solves #202 

`@thomaseizinger` Please have a look at this PR. It might help you solving your problem from #202.

This PR introduces a "show" subcommand, which can be used to print information about the existing changelog entries. It currently features only a "text" backend, but it is prepared to implement also a "json" backend, which then could be used to process the structured data from a changelog.



Co-authored-by: Matthias Beyer <[email protected]>
  • Loading branch information
bors[bot] and matthiasbeyer authored Mar 3, 2023
2 parents 946a284 + 2ff4106 commit c9c5d9b
Show file tree
Hide file tree
Showing 8 changed files with 230 additions and 35 deletions.
8 changes: 8 additions & 0 deletions .changelogs/unreleased/2023-03-03T08_08_41_21078057.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
subject: '"Show" subcommand'
type: Feature

---

A "show" subcommand was added to print changelog fragments either as text or as
JSON.
22 changes: 21 additions & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -149,7 +156,7 @@ impl TextProvider {
}
}

#[derive(Debug, Subcommand)]
#[derive(Clone, Debug, Subcommand)]
pub enum VersionSpec {
Patch,
Minor,
Expand All @@ -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 },
}
37 changes: 36 additions & 1 deletion src/command/common.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::path::Path;

use crate::error::VersionError;
use crate::{
cli::VersionSpec,
error::{Error, VersionError},
};

pub fn get_version_from_path(path: &Path) -> Result<Option<semver::Version>, VersionError> {
path.components()
Expand All @@ -25,3 +28,35 @@ pub fn get_version_from_path(path: &Path) -> Result<Option<semver::Version>, Ver
})
.transpose()
}

pub fn find_version_string(workdir: &Path, version: &VersionSpec) -> Result<String, Error> {
use cargo_metadata::MetadataCommand;

if let VersionSpec::Custom { custom } = version {
Ok(custom.clone())
} else {
let metadata = MetadataCommand::new()
.manifest_path(workdir.join("./Cargo.toml"))
.exec()?;

let workspace_member_ids = &metadata.workspace_members;

let versions = metadata
.packages
.iter()
.filter(|pkg| workspace_member_ids.contains(&pkg.id))
.map(|pkg| &pkg.version)
.collect::<Vec<_>>();

if versions.is_empty() {
return Err(Error::NoVersionInCargoToml);
}

let first = versions[0];
let all_versions_same = versions.iter().all(|v| *v == first);
if !all_versions_same {
return Err(Error::WorkspaceVersionsNotEqual);
}
Ok(first.to_string())
}
}
36 changes: 3 additions & 33 deletions src/command/generate_command.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::path::{Path, PathBuf};

use crate::{cli::VersionSpec, config::Configuration, error::Error};
use crate::{
cli::VersionSpec, command::common::find_version_string, config::Configuration, error::Error,
};

#[derive(Debug, typed_builder::TypedBuilder)]
pub struct GenerateCommand {
Expand Down Expand Up @@ -41,38 +43,6 @@ impl crate::command::Command for GenerateCommand {
}
}

fn find_version_string(workdir: &Path, version: &VersionSpec) -> Result<String, Error> {
use cargo_metadata::MetadataCommand;

if let VersionSpec::Custom { custom } = version {
Ok(custom.clone())
} else {
let metadata = MetadataCommand::new()
.manifest_path(workdir.join("./Cargo.toml"))
.exec()?;

let workspace_member_ids = &metadata.workspace_members;

let versions = metadata
.packages
.iter()
.filter(|pkg| workspace_member_ids.contains(&pkg.id))
.map(|pkg| &pkg.version)
.collect::<Vec<_>>();

if versions.is_empty() {
return Err(Error::NoVersionInCargoToml);
}

let first = versions[0];
let all_versions_same = versions.iter().all(|v| *v == first);
if !all_versions_same {
return Err(Error::WorkspaceVersionsNotEqual);
}
Ok(first.to_string())
}
}

fn ensure_release_dir(
workdir: &Path,
config: &Configuration,
Expand Down
3 changes: 3 additions & 0 deletions src/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
144 changes: 144 additions & 0 deletions src/command/show.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
use std::{
collections::HashMap,
io::BufReader,
io::Write,
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) => {
log::debug!("Showing 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 }) => {
log::debug!("Showing 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 }) => {
log::debug!("Showing range from {from} until {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>>()?
}
};

log::trace!("Looking at: {pathes:?}");
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> {
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> {
let v = iter.collect::<Result<HashMap<PathBuf, Fragment>, _>>()?;
let out = std::io::stdout();
let output = out.lock();
serde_json::to_writer(output, &v).map_err(Error::from)
}
9 changes: 9 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,18 @@ pub enum Error {
#[error("Environment variable '{0}' is not unicode")]
EnvNotUnicode(String),

#[error("Specified version '{version}' does not exist")]
ExactVersionDoesNotExist { version: String },

#[error(transparent)]
SemVer(#[from] semver::Error),

#[error("Fragment Error: {}", .1.display())]
FragmentError(#[source] FragmentError, PathBuf),

#[error(transparent)]
Json(#[from] serde_json::Error),

#[error("Version error")]
Version(#[from] VersionError),

Expand Down
6 changes: 6 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
Expand Down

0 comments on commit c9c5d9b

Please sign in to comment.