Skip to content

Commit

Permalink
Add simple check_contains_version function
Browse files Browse the repository at this point in the history
This function searches for the a template using a simple substring
match. It is always enabled since it doesn’t need anything outside the
standard library.
  • Loading branch information
mgeisler committed Sep 20, 2021
1 parent 14d811f commit 73270f9
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 21 deletions.
7 changes: 2 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,9 @@ jobs:

# Verify that features work by themselves
# Features should not interfere with each other
# Should only need to check 0, 1, and (all) 3 features enabled

# Use if statement to invert exit code: this should fail
- name: Check failure to build with no features
run: if cargo check --no-default-features; then false; else true; fi
shell: bash
- name: Build and test with no default feature
run: cargo test --no-default-features

- name: Build and test with markdown_deps_updated feature
run: cargo test --no-default-features --features markdown_deps_updated
Expand Down
67 changes: 67 additions & 0 deletions src/contains_version.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use crate::helpers::{read_file, Result};

/// Check that `path` contain the substring 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.
///
/// See [`check_contains_regex`](crate::check_contains_regex) if you
/// want to match with a regular expression instead.
///
/// # Errors
///
/// If the template 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_version(
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 pattern = template
.replace("{name}", &pkg_name)
.replace("{version}", &pkg_version);

let text = read_file(path).map_err(|err| format!("could not read {}: {}", path, err))?;

println!("Searching for \"{}\" in {}...", template, path);
match text.find(&pattern) {
Some(idx) => {
let line_no = text[..idx].lines().count();
println!("{} (line {}) ... ok", path, line_no + 1);
Ok(())
}
None => Err(format!("could not find \"{}\" in {}", pattern, path)),
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn pattern_not_found() {
assert_eq!(
check_contains_version("README.md", "should not be found", "foobar", "1.2.3"),
Err(String::from(
"could not find \"should not be found\" in README.md"
))
)
}

#[test]
fn pattern_found() {
assert_eq!(
check_contains_version("README.md", "{name}", "version-sync", "1.2.3"),
Ok(())
)
}
}
15 changes: 8 additions & 7 deletions src/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Imports are inside the function bodies to scope them in a cfg block.
use std::fs::File;
use std::io::{self, Read};

/// The common result type, our errors will be simple strings.
pub type Result<T> = std::result::Result<T, String>;
Expand Down Expand Up @@ -28,10 +29,8 @@ where
/// Return all data from `path`. Line boundaries are normalized from
/// "\r\n" to "\n" to make sure "^" and "$" will match them. See
/// https://github.com/rust-lang/regex/issues/244 for details.
pub fn read_file(path: &str) -> std::io::Result<String> {
use std::io::Read;

let mut file = std::fs::File::open(path)?;
pub fn read_file(path: &str) -> io::Result<String> {
let mut file = File::open(path)?;
let mut buf = String::new();
file.read_to_string(&mut buf)?;
Ok(buf.replace("\r\n", "\n"))
Expand All @@ -45,8 +44,10 @@ pub fn indent(text: &str) -> String {

/// Verify that the version range request matches the given version.
#[cfg(any(feature = "html_root_url_updated", feature = "markdown_deps_updated"))]
pub fn version_matches_request(version: &semver_parser::version::Version,
request: &semver_parser::range::VersionReq) -> Result<()> {
pub fn version_matches_request(
version: &semver_parser::version::Version,
request: &semver_parser::range::VersionReq,
) -> Result<()> {
use semver_parser::range::Op;
if request.predicates.len() != 1 {
// Can only handle simple dependencies
Expand Down
90 changes: 81 additions & 9 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@
//!
//! * A `Changelog.md` file that should at least mention the current
//! version, gated behind the "regex_version" feature.
//! See [`assert_contains_regex`].
//! See [`assert_contains_regex`] and [`assert_contains_version`].
//!
//! * The [`html_root_url`] attribute that tells other crates where to
//! find your documentation, gated behind the "html_root_url" feature.
//! See [`assert_html_root_url_updated`].
//!
//! The macros are gated behind individual features, as detailed below.
//! Except for [`assert_contains_version`], the macros are gated
//! behind individual features, as detailed below.
//!
//! A typical configuration will use an integration test to verify
//! that all version numbers are in sync. Create a
Expand All @@ -40,11 +41,18 @@
//! version_sync::assert_html_root_url_updated!("src/lib.rs");
//! }
//!
//! #[test]
//! # fn fake_hidden_test_case_3() {}
//! fn test_readme_mentions_version() {
//! version_sync::assert_contains_version!("README.md", "Version {version}");
//! }
//!
//! # fn main() {
//! # #[cfg(feature = "markdown_deps_updated")]
//! # test_readme_deps();
//! # #[cfg(feature = "html_root_url_updated")]
//! # test_html_root_url();
//! # test_readme_mentions_version();
//! # }
//! ```
//!
Expand All @@ -53,7 +61,8 @@
//!
//! # Cargo Features
//!
//! Each of the macros above are gated behind a feature:
//! In case you only need some of the macros above, you can disable
//! them individually using Cargo features. The features are:
//!
//! * `markdown_deps_updated` enables [`assert_markdown_deps_updated`].
//! * `html_root_url_updated` enables [`assert_html_root_url_updated`].
Expand All @@ -71,17 +80,14 @@
#![deny(missing_docs)]

mod contains_regex;
mod contains_version;
mod helpers;
mod html_root_url;
mod markdown_deps;

// Ensure that at least one feature is enabled
#[cfg(not(any(feature = "contains_regex", feature = "html_root_url_updated",
feature = "markdown_deps_updated")))]
std::compile_error!("Please select at least one feature.");

#[cfg(feature = "contains_regex")]
pub use crate::contains_regex::check_contains_regex;
pub use crate::contains_version::check_contains_version;
#[cfg(feature = "html_root_url_updated")]
pub use crate::html_root_url::check_html_root_url;
#[cfg(feature = "markdown_deps_updated")]
Expand Down Expand Up @@ -192,12 +198,78 @@ macro_rules! assert_html_root_url_updated {
};
}

/// Assert that versions numbers are up to date via pattern matching.
///
/// 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 template which will be matched against the
/// content of the file.
///
/// The macro calls [`check_contains_version`] 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
/// #[test]
/// # fn fake_hidden_test_case() {}
/// # // The above function ensures test_readme_mentions_version is
/// # // compiled.
/// fn test_readme_mentions_version() {
/// version_sync::assert_contains_version!("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 pattern can contain placeholders which are replaced before the
/// 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 of {name}
/// is: {version}"` and make sure you update your READMEs and
/// changelogs consistently.
///
/// See [`assert_contains_regex`] if you want to search for a regular
/// expression instead.
///
/// # Panics
///
/// If the pattern cannot be found, `panic!` will be invoked and your
/// integration test will fail.
///
/// [`check_contains_version`]: fn.check_contains_version.html
#[macro_export]
macro_rules! assert_contains_version {
($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_version($path, $format, pkg_name, pkg_version) {
panic!("{}", err);
}
};
}

/// 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.
/// against the contents of the file.
///
/// The macro calls [`check_contains_regex`] on the file name given.
/// The package name and current package version is automatically
Expand Down

0 comments on commit 73270f9

Please sign in to comment.