Skip to content

Commit

Permalink
contains-regex: new macro for more general regex checks
Browse files Browse the repository at this point in the history
The new assert_contains_regex macro makes it easy to check that the
current version number is mentioned in a given file.

Fixes #17.
  • Loading branch information
mgeisler committed Nov 20, 2018
1 parent 644441b commit 6ccbb4b
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 1 deletion.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ syn = { version = "0.15", features = ["full"] }
toml = "0.4"
url = "1.5.1"
itertools = "0.7"
regex = "1.0"
122 changes: 121 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
//!
//! When making a release of a Rust project, you typically need to
//! adjust some version numbers in your code and documentation. This
//! crate gives you macros that covers the two usual cases where
//! crate gives you macros that covers some typical cases where
//! version numbers need updating:
//!
//! * TOML examples in the `README.md` files that show how to add a
//! dependency on your crate. See [`assert_markdown_deps_updated`].
//!
//! * A `Changelog.md` file that should at least mention the current
//! version. See [`assert_contains_regex`].
//!
//! * The [`html_root_url`] attribute that tells other crates where to
//! find your documentation. See [`assert_html_root_url_updated`].
//!
Expand Down Expand Up @@ -44,12 +47,14 @@
//! [`html_root_url`]: https://rust-lang-nursery.github.io/api-guidelines/documentation.html#crate-sets-html_root_url-attribute-c-html-root
//! [`assert_markdown_deps_updated`]: macro.assert_markdown_deps_updated.html
//! [`assert_html_root_url_updated`]: macro.assert_html_root_url_updated.html
//! [`assert_contains_regex`]: macro.assert_contains_regex.html
#![doc(html_root_url = "https://docs.rs/version-sync/0.5.0")]
#![deny(missing_docs)]

extern crate itertools;
extern crate pulldown_cmark;
extern crate regex;
extern crate semver_parser;
extern crate syn;
extern crate toml;
Expand All @@ -61,6 +66,7 @@ use std::result;

use itertools::join;
use pulldown_cmark::{Event, Parser, Tag};
use regex::Regex;
use semver_parser::range::parse as parse_request;
use semver_parser::range::{Op, VersionReq};
use semver_parser::version::parse as parse_version;
Expand Down Expand Up @@ -341,6 +347,120 @@ macro_rules! assert_markdown_deps_updated {
};
}

/// Assert that versions numbers are up to date via a regex.
///
/// This macro allows you verify that the current version number is
/// mentioned in a particular file, such as a changelog file. You do
/// this by specifying a regular expression which will be matched
/// against the file.
///
/// The macro calls [`check_contains_regex`] on the file name given.
/// The package name and current package version is automatically
/// taken from the `$CARGO_PKG_NAME` and `$CARGO_PKG_VERSION`
/// environment variables. These environment variables are
/// automatically set by Cargo when compiling your crate.
///
/// # Usage
///
/// The typical way to use this macro is from an integration test:
///
/// ```rust
/// #[macro_use]
/// extern crate version_sync;
///
/// #[test]
/// # fn fake_hidden_test_case() {}
/// # // The above function ensures test_readme_mentions_version is
/// # // compiled.
/// fn test_readme_mentions_version() {
/// assert_contains_regex!("README.md", "^### Version {version}");
/// }
///
/// # fn main() {
/// # test_readme_mentions_version();
/// # }
/// ```
///
/// Tests are run with the current directory set to directory where
/// your `Cargo.toml` file is, so this will find a `README.md` file
/// next to your `Cargo.toml` file. It will then check that there is a
/// heading mentioning the current version of your crate.
///
/// The regular expression can contain placeholders which are replaced
/// before the regular expression search begins:
///
/// * `{version}`: the current version number of your package.
/// * `{name}`: the name of your package.
///
/// This way you can search for things like `"Latest version is:
/// {version}"` and make sure you update your READMEs and changelogs
/// consistently.
///
/// # Panics
///
/// If the regular expression cannot be found, `panic!` will be
/// invoked and your integration test will fail.
///
/// [`check_contains_regex`]: fn.check_contains_regex.html
#[macro_export]
macro_rules! assert_contains_regex {
($path:expr, $format:expr) => {
let pkg_name = env!("CARGO_PKG_NAME");
let pkg_version = env!("CARGO_PKG_VERSION");
if let Err(err) = $crate::check_contains_regex($path, $format, pkg_name, pkg_version) {
panic!(err);
}
};
}

/// Check that `path` contain the regular expression given by
/// `template`.
///
/// The placeholders `{name}` and `{version}` will be replaced with
/// `pkg_name` and `pkg_version`, if they are present in `template`.
/// It is okay if `template` do not contain these placeholders.
///
/// The matching is done in multi-line mode, which means that `^` in
/// the regular expression will match the beginning of any line in the
/// file, not just the very beginning of the file.
///
/// # Errors
///
/// If the regular expression cannot be found, an `Err` is returned
/// with a succinct error message. Status information has then already
/// been printed on `stdout`.
pub fn check_contains_regex(
path: &str,
template: &str,
pkg_name: &str,
pkg_version: &str,
) -> Result<()> {
// Expand the optional {name} and {version} placeholders in the
// template. This is almost like
//
// format!(template, name = pkg_name, version = pkg_version)
//
// but allows the user to leave out unnecessary placeholders.
let regex = template
.replace("{name}", pkg_name)
.replace("{version}", pkg_version);

// We enable multi-line mode by default.
let re = Regex::new(&(String::from("(?m)") + &regex))
.map_err(|err| format!("invalid regular expression: {}", err))?;
let text = read_file(path).map_err(|err| format!("could not read {}: {}", path, err))?;

println!("Searching for {:?} in {}...", regex, path);
match re.find(&text) {
Some(m) => {
let line_no = text[..m.start()].lines().count();
println!("{} (line {}) ... ok", path, line_no + 1);
Ok(())
}
None => Err(format!("could not find {:?} in {}", regex, path)),
}
}

fn url_matches(value: &str, pkg_name: &str, version: &Version) -> Result<()> {
let url = Url::parse(value).map_err(|err| format!("parse error: {}", err))?;

Expand Down
5 changes: 5 additions & 0 deletions tests/version-numbers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ fn test_readme_deps() {
assert_markdown_deps_updated!("README.md");
}

#[test]
fn test_readme_changelog() {
assert_contains_regex!("README.md", "^### Version {version}");
}

#[test]
fn test_html_root_url() {
assert_html_root_url_updated!("src/lib.rs");
Expand Down

0 comments on commit 6ccbb4b

Please sign in to comment.