semver_macro/
lib.rs

1//! A proc-macro for [`semver::Version`]
2//!
3//! This crate contains macros to parse [`semver::Version`]s at compile-time.
4//! Currently only versions without pre-release and build metadata identifier
5//! are supported.
6//!
7//! # Example
8//!
9//! ```
10//! use semver::Version;
11//! use semver_macro::{version, env_version};
12//!
13//! // parse from string literal
14//! const FROM_LITERAL: Version = version!("0.1.0");
15//!
16//! // parse from environment variable
17//! const FROM_ENV: Version = env_version!("CARGO_PKG_VERSION");
18//! ```
19
20use proc_macro::TokenStream;
21use proc_macro2::Span;
22use proc_macro_error::{abort, proc_macro_error};
23use quote::quote_spanned;
24use semver::Version;
25use syn::{parse_macro_input, LitStr};
26
27/// Macro that parses a [`semver::Version`] from string and outputs it as a
28/// constant expression.
29///
30/// # Example
31///
32/// ```
33/// use semver::Version;
34/// use semver_macro::version;
35///
36/// const MY_VERSION: Version = version!("0.1.0");
37/// ```
38#[proc_macro_error]
39#[proc_macro]
40pub fn version(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
41    let input = parse_macro_input!(input as LitStr);
42    let span = input.span();
43
44    parse_and_emit(&input.value(), span)
45}
46
47/// Macro that parses a [`semver::Version`] from an environment variable and
48/// outputs it as a constant expression.
49///
50/// # Example
51///
52/// ```
53/// use semver::Version;
54/// use semver_macro::env_version;
55///
56/// const MY_VERSION: Version = env_version!("CARGO_PKG_VERSION");
57/// ```
58#[proc_macro_error]
59#[proc_macro]
60pub fn env_version(input: proc_macro::TokenStream) -> TokenStream {
61    let input = parse_macro_input!(input as LitStr);
62    let span = input.span();
63
64    let input = std::env::var(input.value()).unwrap_or_else(|e| abort!(span, "{}", e));
65
66    parse_and_emit(&input, span)
67}
68
69fn parse_and_emit(input: &str, span: Span) -> TokenStream {
70    let version: Version = input.parse().unwrap_or_else(|e| abort!(span, "{}", e));
71
72    if !version.pre.is_empty() {
73        abort!(span, "Version can't have a pre-release identifier.");
74    }
75    if !version.build.is_empty() {
76        abort!(span, "Version can't have a build metadata identifier.");
77    }
78
79    emit(&version, span)
80}
81
82fn emit(version: &Version, span: Span) -> TokenStream {
83    let major = version.major;
84    let minor = version.minor;
85    let patch = version.patch;
86
87    quote_spanned! {
88        span =>
89        ::semver::Version {
90            major: #major,
91            minor: #minor,
92            patch: #patch,
93            pre: ::semver::Prerelease::EMPTY,
94            build: ::semver::BuildMetadata::EMPTY,
95        }
96    }
97    .into()
98}