codas_macros/
lib.rs

1#![doc = include_str!("../README.md")]
2//! > _Note_: This documentation is auto-generated
3//! > from the project's README.md file.
4use std::{path::PathBuf, process::Command};
5
6use proc_macro::{TokenStream, TokenTree};
7
8/// Loads a coda from a file, generating Rust data
9/// structures and codecs for the coda and exporting
10/// them into the module that called this macro.
11///
12/// Refer to the [crate] docs for more info.
13#[proc_macro]
14pub fn export_coda(tokens: TokenStream) -> TokenStream {
15    let coda_path = parse_token_string(tokens);
16
17    // Locate the workspace root path.
18    let workspace_toml = Command::new(env!("CARGO"))
19        .arg("locate-project")
20        .arg("--workspace")
21        .arg("--message-format=plain")
22        .output()
23        .unwrap()
24        .stdout;
25    let workspace_toml = String::from_utf8_lossy(&workspace_toml);
26    let workspace_root = PathBuf::from(workspace_toml.trim());
27    let workspace_root = workspace_root.parent().unwrap();
28
29    // Locate the coda relative to the workspace root.
30    let path = workspace_root.join(coda_path);
31
32    // Load and parse the coda from the file.
33    let coda = std::fs::read_to_string(path.clone()).unwrap();
34    let coda = codas::parse::parse(&coda).unwrap();
35
36    // Generate Rust code.
37    let mut codegen = vec![];
38    #[cfg(feature = "serde")]
39    {
40        codas::langs::rust::generate_types(&coda, &mut codegen, true).unwrap();
41    }
42    #[cfg(not(feature = "serde"))]
43    {
44        codas::langs::rust::generate_types(&coda, &mut codegen, false).unwrap();
45    }
46    let codegen = String::from_utf8_lossy(&codegen);
47
48    // Prepend the generated code with a statement
49    // that will trigger a rebuild of the code whenever
50    // the source file changes.
51    let mut codegen_prefix = format!(
52        r#"
53        const _: &str = include_str!("{}");
54    "#,
55        path.display()
56    );
57    codegen_prefix += &codegen;
58
59    codegen_prefix.parse().unwrap()
60}
61
62/// Parses the first quoted string from `tokens`,
63/// returning the string (without quotes).
64fn parse_token_string(tokens: TokenStream) -> String {
65    // Assume the tokens contain a single string literal.
66    let mut coda = match tokens.into_iter().next() {
67        Some(TokenTree::Literal(string)) => string.to_string(),
68        _ => panic!("rly?"),
69    };
70
71    // Strip leading/trailing literal markers.
72    if coda.starts_with("r#") {
73        coda.remove(0); // r
74        coda.remove(0); // #
75        coda.remove(coda.len() - 1); // #
76    }
77
78    // Strip quotes.
79    coda.remove(0);
80    coda.remove(coda.len() - 1);
81
82    coda
83}