local_fmt_macros_internal/def_local_fmt/
arg.rs1use std::path::PathBuf;
2
3use syn::parse::ParseStream;
4use syn::{Ident, LitStr};
5
6#[derive(Debug)]
7pub struct MessageField {
8 pub ty: Ident,
9 pub fields: Option<Vec<(Ident, MessageField)>>,
10}
11
12impl syn::parse::Parse for MessageField {
13 fn parse(input: ParseStream) -> syn::Result<Self> {
14 let ty: Ident = input.parse()?;
15 if !input.peek(syn::token::Brace) {
16 if !input.is_empty() {
17 let _: syn::Token![,] = input.parse()?;
18 }
19 return Ok(Self { ty, fields: None });
20 };
21 let content;
22 syn::braced!(content in input);
23
24 let mut fields = Vec::new();
25 while !content.is_empty() {
26 let ty: Ident = content.parse()?;
27
28 let _: syn::Token![:] = content.parse()?;
29
30 let field: MessageField = content.parse()?;
31
32 fields.push((ty, field));
33
34 if content.is_empty() {
35 break;
36 }
37
38 let _: syn::Token![,] = content.parse()?;
39 }
40
41 Ok(Self {
42 ty,
43 fields: Some(fields),
44 })
45 }
46}
47
48pub struct Args {
49 pub name: Ident,
50 pub lang: Ident,
51 pub message: MessageField,
52 pub supplier: syn::Expr,
53 pub file_type: ArgFileType,
54 pub path: ArgPath,
55}
56
57pub enum ArgFileType {
58 Toml,
59 Json,
60 Yaml,
61}
62
63impl syn::parse::Parse for ArgFileType {
64 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
65 let lit: LitStr = input.parse()?;
66 match lit.value().as_str() {
67 "toml" => Ok(Self::Toml),
68 "json" => Ok(Self::Json),
69 "yaml" => Ok(Self::Yaml),
70 _ => Err(syn::Error::new(lit.span(), "expected toml or json")),
71 }
72 }
73}
74
75#[derive(Debug)]
76pub enum ArgPath {
77 File(PathBuf),
78 Folder(PathBuf),
79}
80
81impl syn::parse::Parse for Args {
82 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
83 mod kw {
84 syn::custom_keyword!(name);
85 syn::custom_keyword!(lang);
86 syn::custom_keyword!(message);
87 syn::custom_keyword!(supplier);
88 syn::custom_keyword!(file_type);
89 syn::custom_keyword!(lang_file);
90 syn::custom_keyword!(lang_folder);
91 }
92
93 macro_rules! parse {
94 ($ident:ident) => {
95 parse!($ident, Ident);
96 };
97 ($ident:ident, $ty:ty) => {
98 parse!($ident, $ty, without_comma);
99 let _: syn::Token![,] = input.parse()?;
100 };
101 ($ident:ident, $ty:ty, without_comma) => {
102 let _: kw::$ident = input.parse()?;
103 let _: syn::Token![=] = input.parse()?;
104 let $ident: $ty = input.parse()?;
105 };
106 }
107
108 parse!(name);
109 parse!(lang);
110 parse!(message, MessageField, without_comma);
111
112 if message.fields.is_some() {
113 let _: syn::Token![,] = input.parse()?;
114 }
115
116 parse!(supplier, syn::Expr);
117
118 parse!(file_type, ArgFileType);
119
120 let crate_root = {
121 #[allow(clippy::panic)]
122 let crate_root = std::env::var("CARGO_MANIFEST_DIR")
123 .unwrap_or_else(|_| panic!("failed to get CARGO_MANIFEST_DIR"));
124 PathBuf::from(crate_root)
125 };
126
127 let path = if input.peek(kw::lang_file) {
128 parse!(lang_file, syn::LitStr, without_comma);
129
130 ArgPath::File(crate_root.join(lang_file.value()))
131 } else if input.peek(kw::lang_folder) {
132 parse!(lang_folder, syn::LitStr, without_comma);
133
134 ArgPath::Folder(crate_root.join(lang_folder.value()))
135 } else {
136 return Err(input.error("expected lang_file or lang_folder"));
137 };
138
139 if input.peek(syn::Token![,]) {
140 let _ = input.parse::<syn::Token![,]>()?;
141 }
142
143 Ok(Self {
144 name,
145 lang,
146 message,
147 supplier,
148 file_type,
149 path,
150 })
151 }
152}