Add bin/gen command to diff generated content

The following command will print a diff between HEAD and HEAD^:

    cargo run --package gen diff

type: development
This commit is contained in:
Casey Rodarmor 2020-04-29 01:24:07 -07:00
parent 342266853e
commit 8dfdbe43df
No known key found for this signature in database
GPG Key ID: 556186B153EC6FE0
5 changed files with 134 additions and 10 deletions

3
Cargo.lock generated
View File

@ -364,6 +364,8 @@ dependencies = [
"fehler", "fehler",
"git2", "git2",
"globset", "globset",
"ignore",
"lexiclean",
"libc", "libc",
"log", "log",
"pretty_env_logger", "pretty_env_logger",
@ -376,6 +378,7 @@ dependencies = [
"strum_macros", "strum_macros",
"tempfile", "tempfile",
"url", "url",
"walkdir",
] ]
[[package]] [[package]]

View File

@ -12,6 +12,7 @@ chrono = "0.4.11"
fehler = "1.0.0" fehler = "1.0.0"
git2 = "0.13.1" git2 = "0.13.1"
globset = "0.4.5" globset = "0.4.5"
ignore = "0.4.14"
libc = "0.2.69" libc = "0.2.69"
log = "0.4.8" log = "0.4.8"
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
@ -22,6 +23,8 @@ structopt = "0.3.12"
strum = "0.18.0" strum = "0.18.0"
strum_macros = "0.18.0" strum_macros = "0.18.0"
tempfile = "3.1.0" tempfile = "3.1.0"
walkdir = "2.3.1"
lexiclean = "0.0.1"
[dependencies.serde] [dependencies.serde]
version = "1.0.106" version = "1.0.106"

View File

@ -6,7 +6,7 @@ pub(crate) use std::{
fs::{self, File}, fs::{self, File},
io, io,
ops::Deref, ops::Deref,
path::{Path, PathBuf}, path::{Path, PathBuf, StripPrefixError},
process::{self, Command, ExitStatus, Stdio}, process::{self, Command, ExitStatus, Stdio},
str, str,
}; };
@ -16,6 +16,8 @@ pub(crate) use cargo_toml::Manifest;
pub(crate) use chrono::{DateTime, NaiveDateTime, Utc}; pub(crate) use chrono::{DateTime, NaiveDateTime, Utc};
pub(crate) use fehler::{throw, throws}; pub(crate) use fehler::{throw, throws};
pub(crate) use git2::{Commit, Oid, Repository}; pub(crate) use git2::{Commit, Oid, Repository};
pub(crate) use ignore::overrides::OverrideBuilder;
pub(crate) use lexiclean::Lexiclean;
pub(crate) use libc::EXIT_FAILURE; pub(crate) use libc::EXIT_FAILURE;
pub(crate) use log::info; pub(crate) use log::info;
pub(crate) use regex::Regex; pub(crate) use regex::Regex;
@ -26,6 +28,7 @@ pub(crate) use structopt::StructOpt;
pub(crate) use strum::VariantNames; pub(crate) use strum::VariantNames;
pub(crate) use strum_macros::{EnumVariantNames, IntoStaticStr}; pub(crate) use strum_macros::{EnumVariantNames, IntoStaticStr};
pub(crate) use url::Url; pub(crate) use url::Url;
pub(crate) use walkdir::WalkDir;
// modules // modules
pub(crate) use crate::error; pub(crate) use crate::error;

View File

@ -52,6 +52,12 @@ pub(crate) enum Error {
}, },
#[snafu(display("I/O error at `{}`: {}", path.display(), source))] #[snafu(display("I/O error at `{}`: {}", path.display(), source))]
Filesystem { path: PathBuf, source: io::Error }, Filesystem { path: PathBuf, source: io::Error },
#[snafu(display("I/O error copying `{}` to `{}`: {}", src.display(), dst.display(), source))]
FilesystemCopy {
src: PathBuf,
dst: PathBuf,
source: io::Error,
},
#[snafu(display("Git error: {}", source))] #[snafu(display("Git error: {}", source))]
Git { source: git2::Error }, Git { source: git2::Error },
#[snafu(display("Regex compilation error: {}", source))] #[snafu(display("Regex compilation error: {}", source))]
@ -67,6 +73,12 @@ pub(crate) enum Error {
TemplateRender { source: askama::Error }, TemplateRender { source: askama::Error },
#[snafu(display("Failed to get workdir for repo at `{}`", repo.display()))] #[snafu(display("Failed to get workdir for repo at `{}`", repo.display()))]
Workdir { repo: PathBuf }, Workdir { repo: PathBuf },
#[snafu(display("Failed to build overrides: {}", source))]
Ignore { source: ignore::Error },
#[snafu(display("Failed to traverse worktree: {}", source))]
Walkdir { source: walkdir::Error },
#[snafu(display("Failed to strip path prefix: {}", source))]
StripPrefix { source: StripPrefixError },
} }
impl From<regex::Error> for Error { impl From<regex::Error> for Error {
@ -86,3 +98,15 @@ impl From<cargo_toml::Error> for Error {
Self::CargoToml { source } Self::CargoToml { source }
} }
} }
impl From<ignore::Error> for Error {
fn from(source: ignore::Error) -> Self {
Self::Ignore { source }
}
}
impl From<walkdir::Error> for Error {
fn from(source: walkdir::Error) -> Self {
Self::Walkdir { source }
}
}

View File

@ -4,6 +4,8 @@ use crate::common::*;
pub(crate) enum Opt { pub(crate) enum Opt {
#[structopt(about("Update all generated docs"))] #[structopt(about("Update all generated docs"))]
All, All,
#[structopt(about("Generate book"))]
Book,
#[structopt(about("Generate the changelog"))] #[structopt(about("Generate the changelog"))]
Changelog, Changelog,
#[structopt(about("Print a commit template to standard output"))] #[structopt(about("Print a commit template to standard output"))]
@ -12,10 +14,10 @@ pub(crate) enum Opt {
CommitTypes, CommitTypes,
#[structopt(about("Generate completion scripts"))] #[structopt(about("Generate completion scripts"))]
CompletionScripts, CompletionScripts,
#[structopt(about("Diff generated content between commits"))]
Diff,
#[structopt(about("Generate readme"))] #[structopt(about("Generate readme"))]
Readme, Readme,
#[structopt(about("Generate book"))]
Book,
#[structopt(about("Generate man pages"))] #[structopt(about("Generate man pages"))]
Man, Man,
} }
@ -70,16 +72,20 @@ impl Opt {
Self::Readme => Self::readme(&project)?, Self::Readme => Self::readme(&project)?,
Self::Book => Self::book(&project)?, Self::Book => Self::book(&project)?,
Self::Man => Self::man(&project)?, Self::Man => Self::man(&project)?,
Self::All => { Self::Diff => Self::diff(&project)?,
Self::changelog(&project)?; Self::All => Self::all(&project)?,
Self::completion_scripts(&project)?;
Self::readme(&project)?;
Self::book(&project)?;
Self::man(&project)?;
}
} }
} }
#[throws]
pub(crate) fn all(project: &Project) {
Self::changelog(&project)?;
Self::completion_scripts(&project)?;
Self::readme(&project)?;
Self::book(&project)?;
Self::man(&project)?;
}
#[throws] #[throws]
pub(crate) fn changelog(project: &Project) { pub(crate) fn changelog(project: &Project) {
info!("Generating changelog…"); info!("Generating changelog…");
@ -109,6 +115,91 @@ impl Opt {
.status_into_result()? .status_into_result()?
} }
#[throws]
pub(crate) fn diff(project: &Project) {
let tmp = tempfile::tempdir().context(error::Tempdir)?;
let generated = &[
"/CHANGELOG.md",
"/README.md",
"/book/src/SUMMARY.md",
"/book/src/bittorrent.md",
"/book/src/commands.md",
"/book/src/commands/*",
"/book/src/faq.md",
"/book/src/introduction.md",
"/book/src/references.md",
"/book/src/references/*",
"/completions/*",
"/man/*",
];
let gen = |name: &str| -> Result<(), Error> {
cmd!("cargo", "run", "--package", "gen", "all").status_into_result()?;
let dir = tmp.path().join(name);
fs::create_dir(&dir).context(error::Filesystem { path: &dir })?;
let mut builder = OverrideBuilder::new(&project.root);
for pattern in generated {
builder.add(pattern)?;
}
let overrides = builder.build()?;
for result in WalkDir::new(&project.root) {
let entry = result?;
let src = entry.path();
if src.is_dir() {
continue;
}
if !overrides.matched(&src, false).is_whitelist() {
continue;
}
let relative = src
.strip_prefix(&project.root)
.context(error::StripPrefix)?;
let dst = dir.join(relative);
let dst_dir = dst.join("..").lexiclean();
fs::create_dir_all(&dst_dir).context(error::Filesystem { path: dst_dir })?;
fs::copy(&src, &dst).context(error::FilesystemCopy { src, dst })?;
}
Ok(())
};
const HEAD: &str = "HEAD";
gen(HEAD)?;
let head = project.repo.head()?.peel_to_commit()?;
let parent = head.parent(0)?;
let parent_hash = parent.id().to_string();
cmd!("git", "checkout", &parent_hash).status_into_result()?;
gen(&parent_hash)?;
cmd!("diff", "-r", parent_hash, HEAD)
.current_dir(tmp.path())
.status_into_result()
.ok();
cmd!("git", "checkout", &head.id().to_string()).status_into_result()?;
}
#[throws] #[throws]
pub(crate) fn readme(project: &Project) { pub(crate) fn readme(project: &Project) {
info!("Generating readme…"); info!("Generating readme…");