codas_macros/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#![doc = include_str!("../README.md")]
//! > _Note_: This documentation is auto-generated
//! > from the project's README.md file.
use std::{path::PathBuf, process::Command};

use proc_macro::{TokenStream, TokenTree};

/// Loads a coda from a file, generating Rust data
/// structures and codecs for the coda and exporting
/// them into the module that called this macro.
///
/// Refer to the [crate] docs for more info.
#[proc_macro]
pub fn export_coda(tokens: TokenStream) -> TokenStream {
    let coda_path = parse_token_string(tokens);

    // Locate the workspace root path.
    let workspace_toml = Command::new(env!("CARGO"))
        .arg("locate-project")
        .arg("--workspace")
        .arg("--message-format=plain")
        .output()
        .unwrap()
        .stdout;
    let workspace_toml = String::from_utf8_lossy(&workspace_toml);
    let workspace_root = PathBuf::from(workspace_toml.trim());
    let workspace_root = workspace_root.parent().unwrap();

    // Locate the coda relative to the workspace root.
    let path = workspace_root.join(coda_path);

    // Load and parse the coda from the file.
    let coda = std::fs::read_to_string(path.clone()).unwrap();
    let coda = codas::parse::parse(&coda).unwrap();

    // Generate Rust code.
    let mut codegen = vec![];
    codas::langs::rust::generate_types(&coda, &mut codegen).unwrap();
    let codegen = String::from_utf8_lossy(&codegen);

    // Prepend the generated code with a statement
    // that will trigger a rebuild of the code whenever
    // the source file changes.
    let mut codegen_prefix = format!(
        r#"
        const _: &str = include_str!("{}");
    "#,
        path.display()
    );
    codegen_prefix += &codegen;

    codegen_prefix.parse().unwrap()
}

/// Parses the first quoted string from `tokens`,
/// returning the string (without quotes).
fn parse_token_string(tokens: TokenStream) -> String {
    // Assume the tokens contain a single string literal.
    let mut coda = match tokens.into_iter().next() {
        Some(TokenTree::Literal(string)) => string.to_string(),
        _ => panic!("rly?"),
    };

    // Strip leading/trailing literal markers.
    if coda.starts_with("r#") {
        coda.remove(0); // r
        coda.remove(0); // #
        coda.remove(coda.as_bytes().len() - 1); // #
    }

    // Strip quotes.
    coda.remove(0);
    coda.remove(coda.as_bytes().len() - 1);

    coda
}