Skip to main content

pacaptr_macros/
lib.rs

1mod compat_table;
2#[cfg(feature = "test")]
3mod test_dsl;
4
5use anyhow::Result;
6use proc_macro::TokenStream;
7
8use crate::compat_table::compat_table_impl;
9#[cfg(feature = "test")]
10use crate::test_dsl::test_dsl_impl;
11
12/// A DSL (Domain-Specific Language) embedded in Rust, in order to simplify the
13/// form of smoke tests.
14///
15/// This macro accepts the source of the Test DSL in a **string literal**.
16/// In this DSL, each line is called an `item`. We now support the following
17/// item types:
18/// - `in` item: Run command on `pacaptr`.
19/// - `in !` item: Run command with the system shell (`sh` on Unix,`powershell`
20///   on Windows).
21/// - `ou` item: Check the output of the **last** `in` or `in !` item above
22///   against a **regex** pattern.
23///
24/// A comment in this DSL starts with a `#`.
25///
26/// # Examples
27///
28/// ```no_run
29/// #[test]
30/// #[ignore]
31/// fn apt_r_s() {
32///    test_dsl! { r##"
33///        # Refresh with `pacaptr -Sy`.
34///        in -Sy
35///
36///        # Install `screen`.
37///        in -S screen --yes
38///
39///        # Verify installation.
40///        in ! which screen
41///        ou ^/usr/bin/screen
42///
43///        # Remove `screen` and verify the removal.
44///        in -R screen --yes
45///        in -Qi screen
46///        ou ^Status: deinstall
47///    "## }
48/// }
49/// ```
50#[cfg(feature = "test")]
51#[proc_macro]
52pub fn test_dsl(input: TokenStream) -> TokenStream {
53    use itertools::Itertools;
54    use litrs::StringLit;
55    use quote::quote;
56
57    let input = input.into_iter().collect_vec();
58    if input.len() != 1 {
59        let msg = format!(
60            "argument must be a single string literal, but got {} tokens",
61            input.len()
62        );
63        return quote! { compile_error!(#msg) }.into();
64    }
65
66    let string_lit = match StringLit::try_from(&input[0]) {
67        // Error if the token is not a string literal
68        Err(e) => return e.to_compile_error(),
69        Ok(lit) => lit,
70    };
71
72    res_token_stream(test_dsl_impl(string_lit.value()))
73}
74
75/// Generates the compatibility table as a docstring on the top of given input.
76#[proc_macro]
77pub fn compat_table(input: TokenStream) -> TokenStream {
78    let res =
79        compat_table_impl().map(|docstring| TokenStream::from_iter([docstring.into(), input]));
80    res_token_stream(res)
81}
82
83fn res_token_stream(res: Result<impl Into<TokenStream>, syn::Error>) -> TokenStream {
84    res.map_or_else(|e| e.to_compile_error().into(), Into::into)
85}