diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8e05fd9..12f8fc9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,8 +32,26 @@ jobs: - name: Install ${{ matrix.rust }} Rust run: rustup default ${{ matrix.rust }} - - name: Build and test - run: cargo test + # 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 markdown_deps_updated feature + run: cargo test --no-default-features --features markdown_deps_updated + + - name: Build and test with html_root_url_updated feature + run: cargo test --no-default-features --features html_root_url_updated + + - name: Build and test with contains_regex feature + run: cargo test --no-default-features --features contains_regex + + - name: Build and test with all features + run: cargo test --all-features build-documentation: name: Build documentation diff --git a/Cargo.toml b/Cargo.toml index 5e4eed1..47c80d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,14 +11,20 @@ categories = ["development-tools", "rust-patterns"] license = "MIT" edition = "2018" +[features] +default = ["markdown_deps_updated", "html_root_url_updated", "contains_regex"] +markdown_deps_updated = ["pulldown-cmark", "semver-parser", "toml"] +html_root_url_updated = ["url", "semver-parser", "syn", "proc-macro2"] +contains_regex = ["regex"] + [dependencies] -pulldown-cmark = { version = "0.8", default-features = false } -semver-parser = "0.9" -syn = { version = "1.0", features = ["full"] } -proc-macro2 = { version = "1.0", features = ["span-locations"] } -toml = "0.5" -url = "2.0" -regex = { version = "1.3", default-features = false, features = ["std", "unicode"] } +pulldown-cmark = { version = "0.8", default-features = false, optional = true } +semver-parser = { version = "0.9", optional = true } +syn = { version = "1.0", default-features = false, features = ["parsing", "printing", "full"], optional = true } +proc-macro2 = { version = "1.0", default-features = false, features = ["span-locations"], optional = true } +toml = { version = "0.5", optional = true } +url = { version = "2.0", optional = true } +regex = { version = "1.3", default-features = false, features = ["std", "unicode"], optional = true } [dev-dependencies] tempfile = "3.1" diff --git a/src/contains_regex.rs b/src/contains_regex.rs index 16e574a..cee0e13 100644 --- a/src/contains_regex.rs +++ b/src/contains_regex.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "contains_regex")] use regex::{escape, Regex}; use crate::helpers::{read_file, Result}; diff --git a/src/helpers.rs b/src/helpers.rs index 92cf699..1bd7b3a 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,18 +1,13 @@ -use std::fmt::Display; -use std::fs::File; -use std::io::{self, Read}; -use std::result; - -use semver_parser::range::{Op, VersionReq}; -use semver_parser::version::Version; +// Imports are inside the function bodies to scope them in a cfg block. /// The common result type, our errors will be simple strings. -pub type Result = result::Result; +pub type Result = std::result::Result; +#[cfg(any(feature = "html_root_url_updated", feature = "markdown_deps_updated"))] fn join(iter: T, sep: &str) -> String where T: IntoIterator, - T::Item: Display, + T::Item: std::fmt::Display, { let mut buf = String::new(); let mut iter = iter.into_iter(); @@ -33,20 +28,26 @@ 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) -> io::Result { - let mut file = File::open(path)?; +pub fn read_file(path: &str) -> std::io::Result { + use std::io::Read; + + let mut file = std::fs::File::open(path)?; let mut buf = String::new(); file.read_to_string(&mut buf)?; Ok(buf.replace("\r\n", "\n")) } /// Indent every line in text by four spaces. +#[cfg(any(feature = "html_root_url_updated", feature = "markdown_deps_updated"))] pub fn indent(text: &str) -> String { join(text.lines().map(|line| String::from(" ") + line), "\n") } /// Verify that the version range request matches the given version. -pub fn version_matches_request(version: &Version, request: &VersionReq) -> Result<()> { +#[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<()> { + use semver_parser::range::Op; if request.predicates.len() != 1 { // Can only handle simple dependencies return Ok(()); @@ -93,11 +94,15 @@ pub fn version_matches_request(version: &Version, request: &VersionReq) -> Resul #[cfg(test)] mod tests { + #[cfg(any(feature = "html_root_url_updated", feature = "markdown_deps_updated"))] use semver_parser::range::parse as parse_request; + #[cfg(any(feature = "html_root_url_updated", feature = "markdown_deps_updated"))] use semver_parser::version::parse as parse_version; + #[cfg(any(feature = "html_root_url_updated", feature = "markdown_deps_updated"))] use super::*; + #[cfg(any(feature = "html_root_url_updated", feature = "markdown_deps_updated"))] mod test_version_matches_request { use super::*; diff --git a/src/html_root_url.rs b/src/html_root_url.rs index d093f86..5369c99 100644 --- a/src/html_root_url.rs +++ b/src/html_root_url.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "html_root_url_updated")] use semver_parser::range::parse as parse_request; use semver_parser::version::parse as parse_version; use semver_parser::version::Version; diff --git a/src/lib.rs b/src/lib.rs index 42a8909..472cc6e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,8 @@ //! `version-sync` provides macros for keeping version numbers in sync //! with your crate version. //! +//! # Library Overview +//! //! 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 some typical cases where @@ -10,10 +12,14 @@ //! 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`]. +//! version, gated behind the "regex_version" feature. +//! See [`assert_contains_regex`]. //! //! * The [`html_root_url`] attribute that tells other crates where to -//! find your documentation. See [`assert_html_root_url_updated`]. +//! 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. //! //! A typical configuration will use an integration test to verify //! that all version numbers are in sync. Create a @@ -22,18 +28,22 @@ //! ```rust //! #[test] //! # fn fake_hidden_test_case_1() {} +//! # #[cfg(feature = "markdown_deps_updated")] //! fn test_readme_deps() { //! version_sync::assert_markdown_deps_updated!("README.md"); //! } //! //! #[test] //! # fn fake_hidden_test_case_2() {} +//! # #[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(); +//! # #[cfg(feature = "html_root_url_updated")] //! # test_html_root_url(); //! # } //! ``` @@ -41,6 +51,16 @@ //! When you run `cargo test`, your version numbers will be //! automatically checked. //! +//! # Cargo Features +//! +//! Each of the macros above are gated behind a feature: +//! +//! * `markdown_deps_updated` enables [`assert_markdown_deps_updated`]. +//! * `html_root_url_updated` enables [`assert_html_root_url_updated`]. +//! * `contains_regex` enables [`assert_contains_regex`]. +//! +//! All of these features are enabled by default. +//! //! [`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 @@ -54,8 +74,16 @@ 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; +#[cfg(feature = "html_root_url_updated")] pub use crate::html_root_url::check_html_root_url; +#[cfg(feature = "markdown_deps_updated")] pub use crate::markdown_deps::check_markdown_deps; /// Assert that dependencies on the current package are up to date. @@ -68,6 +96,8 @@ pub use crate::markdown_deps::check_markdown_deps; /// variables are automatically set by Cargo when compiling your /// crate. /// +/// This macro is enabled by the `markdown_deps_updated` feature. +/// /// # Usage /// /// The typical way to use this macro is from an integration test: @@ -95,6 +125,7 @@ pub use crate::markdown_deps::check_markdown_deps; /// /// [`check_markdown_deps`]: fn.check_markdown_deps.html #[macro_export] +#[cfg(feature = "markdown_deps_updated")] macro_rules! assert_markdown_deps_updated { ($path:expr) => { let pkg_name = env!("CARGO_PKG_NAME"); @@ -119,6 +150,8 @@ macro_rules! assert_markdown_deps_updated { /// `$CARGO_PKG_VERSION`. These environment variables are /// automatically set by Cargo when compiling your crate. /// +/// This macro is enabled by the `html_root_url_updated` feature. +/// /// # Usage /// /// The typical way to use this macro is from an integration test: @@ -147,6 +180,7 @@ macro_rules! assert_markdown_deps_updated { /// [api-guidelines]: https://rust-lang-nursery.github.io/api-guidelines/documentation.html#crate-sets-html_root_url-attribute-c-html-root /// [`check_html_root_url`]: fn.check_html_root_url.html #[macro_export] +#[cfg(feature = "html_root_url_updated")] macro_rules! assert_html_root_url_updated { ($path:expr) => { let pkg_name = env!("CARGO_PKG_NAME"); @@ -170,6 +204,8 @@ macro_rules! assert_html_root_url_updated { /// environment variables. These environment variables are /// automatically set by Cargo when compiling your crate. /// +/// This macro is enabled by the `contains_regex` feature. +/// /// # Usage /// /// The typical way to use this macro is from an integration test: @@ -210,6 +246,7 @@ macro_rules! assert_html_root_url_updated { /// /// [`check_contains_regex`]: fn.check_contains_regex.html #[macro_export] +#[cfg(feature = "contains_regex")] macro_rules! assert_contains_regex { ($path:expr, $format:expr) => { let pkg_name = env!("CARGO_PKG_NAME"); diff --git a/src/markdown_deps.rs b/src/markdown_deps.rs index e2b0acc..c36dc2d 100644 --- a/src/markdown_deps.rs +++ b/src/markdown_deps.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "markdown_deps_updated")] use pulldown_cmark::{CodeBlockKind::Fenced, Event, Parser, Tag}; use semver_parser::range::parse as parse_request; use semver_parser::range::VersionReq; diff --git a/tests/version-numbers.rs b/tests/version-numbers.rs index 5a9064a..277ad64 100644 --- a/tests/version-numbers.rs +++ b/tests/version-numbers.rs @@ -1,9 +1,11 @@ #[test] +#[cfg(feature = "markdown_deps_updated")] fn test_readme_deps() { version_sync::assert_markdown_deps_updated!("README.md"); } #[test] +#[cfg(feature = "contains_regex")] fn test_readme_changelog() { version_sync::assert_contains_regex!( "README.md", @@ -12,6 +14,7 @@ fn test_readme_changelog() { } #[test] +#[cfg(feature = "html_root_url_updated")] fn test_html_root_url() { version_sync::assert_html_root_url_updated!("src/lib.rs"); }