bluejay_typegen_codegen/
input.rs1use quote::{ToTokens, TokenStreamExt};
2use syn::parse::Parse;
3
4mod kw {
5 syn::custom_keyword!(borrow);
6 syn::custom_keyword!(enums_as_str);
7}
8
9pub enum DocumentInput {
10 Path(syn::LitStr),
11 Dsl {
12 bracket: syn::token::Bracket,
13 contents: proc_macro2::TokenStream,
14 },
15}
16
17impl Parse for DocumentInput {
18 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
19 if input.peek(syn::token::Bracket) {
20 let contents;
21 Ok(Self::Dsl {
22 bracket: syn::bracketed!(contents in input),
23 contents: contents.parse()?,
24 })
25 } else {
26 input.parse().map(Self::Path)
27 }
28 }
29}
30
31impl ToTokens for DocumentInput {
32 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
33 match self {
34 Self::Path(path) => tokens.append(path.token()),
35 DocumentInput::Dsl { bracket, contents } => {
36 bracket.surround(tokens, |tokens| tokens.extend(contents.clone()))
37 }
38 }
39 }
40}
41
42impl DocumentInput {
43 pub(crate) fn read_to_string_and_path(&self) -> syn::Result<(String, Option<String>)> {
44 match self {
45 Self::Path(path) => {
46 Self::read_file(path).map(|contents| (contents, Some(path.value())))
47 }
48 Self::Dsl { contents, .. } => Ok((contents.to_string(), None)),
49 }
50 }
51
52 fn read_file(filename: &syn::LitStr) -> syn::Result<String> {
53 let cargo_manifest_dir =
54 std::env::var("CARGO_MANIFEST_DIR").map_err(|_| syn::Error::new(filename.span(), "Environment variable CARGO_MANIFEST_DIR was not set but is needed to resolve relative paths"))?;
55 let base_path = std::path::PathBuf::from(cargo_manifest_dir);
56
57 let file_path = base_path.join(filename.value());
58
59 std::fs::read_to_string(file_path)
60 .map_err(|err| syn::Error::new(filename.span(), format!("{}", err)))
61 }
62}
63
64pub struct Input {
65 pub(crate) schema: DocumentInput,
66 pub borrow: Option<syn::LitBool>,
67 pub enums_as_str: syn::punctuated::Punctuated<syn::LitStr, syn::Token![,]>,
68}
69
70impl Parse for Input {
71 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
72 let schema: DocumentInput = input.parse()?;
73
74 let mut borrow: Option<syn::LitBool> = None;
75 let mut enums_as_str = None;
76
77 while !input.is_empty() {
78 input.parse::<syn::Token![,]>()?;
79 let lookahead = input.lookahead1();
80 if lookahead.peek(kw::borrow) {
81 parse_key_value(input, &mut borrow)?;
82 } else if lookahead.peek(kw::enums_as_str) {
83 parse_key_value_with(input, &mut enums_as_str, |input| {
84 let content;
85 syn::bracketed!(content in input);
86 syn::punctuated::Punctuated::parse_separated_nonempty(&content)
87 })?;
88 } else {
89 return Err(lookahead.error());
90 }
91 }
92
93 let enums_as_str = enums_as_str.unwrap_or_default();
94
95 Ok(Self {
96 schema,
97 borrow,
98 enums_as_str,
99 })
100 }
101}
102
103fn parse_key_value<V: syn::parse::Parse>(
104 input: syn::parse::ParseStream,
105 value: &mut Option<V>,
106) -> syn::Result<()> {
107 parse_key_value_with(input, value, syn::parse::Parse::parse)
108}
109
110pub(crate) fn parse_key_value_with<V>(
111 input: syn::parse::ParseStream,
112 value: &mut Option<V>,
113 parser: fn(syn::parse::ParseStream<'_>) -> syn::Result<V>,
114) -> syn::Result<()> {
115 let key: syn::Ident = input.parse()?;
116
117 if value.is_some() {
118 return Err(syn::Error::new(
119 key.span(),
120 format!("Duplicate entry for `{}`", key),
121 ));
122 }
123
124 input.parse::<syn::Token![=]>()?;
125 *value = Some(parser(input)?);
126 Ok(())
127}