Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add simple check_contains_version function #110

Merged
merged 1 commit into from
Sep 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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_substring.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_substring(
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_substring("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_substring("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
95 changes: 83 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@
//! dependency on your crate. See [`assert_markdown_deps_updated`].
//!
//! * A `Changelog.md` file that should at least mention the current
//! version, gated behind the "regex_version" feature.
//! See [`assert_contains_regex`].
//! version, gated behind the "regex_version" feature. See
//! [`assert_contains_regex`] and [`assert_contains_substring`].
//!
//! * 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_substring`], 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 @@ -29,20 +30,27 @@
//! #[test]
//! # fn fake_hidden_test_case_1() {}
//! # #[cfg(feature = "markdown_deps_updated")]
//! fn test_readme_deps() {
//! fn test_readme_deps_updated() {
//! version_sync::assert_markdown_deps_updated!("README.md");
//! }
//!
//! #[test]
//! # fn fake_hidden_test_case_2() {}
//! fn test_readme_mentions_version() {
//! version_sync::assert_contains_substring!("README.md", "Version {version}");
//! }
//!
//! #[test]
//! # fn fake_hidden_test_case_3() {}
//! # #[cfg(feature = "html_root_url_updated")]
//! fn test_html_root_url() {
//! version_sync::assert_html_root_url_updated!("src/lib.rs");
//! }
//!
//! # fn main() {
//! # #[cfg(feature = "markdown_deps_updated")]
//! # test_readme_deps();
//! # test_readme_deps_updated();
//! # test_readme_mentions_version();
//! # #[cfg(feature = "html_root_url_updated")]
//! # test_html_root_url();
//! # }
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_substring;
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_substring::check_contains_substring;
#[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,77 @@ macro_rules! assert_html_root_url_updated {
};
}

/// Assert that versions numbers are up to date via substring 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_substring`] 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_substring!("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 template 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 substring cannot be found, `panic!` will be invoked and
/// your integration test will fail.
#[macro_export]
macro_rules! assert_contains_substring {
($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_substring($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