pretty_text_parser/
syntax.rs1use bevy::ecs::reflect::AppTypeRegistry;
8use bevy::reflect::{Reflect, reflect_trait};
9use convert_case::Casing;
10
11#[reflect_trait]
16pub trait GetDynamicEffectSyntax {
17 fn get(&self) -> DynamicEffectSyntax<'static>;
19}
20
21pub fn get_dynamic_effect_syntax<T: Reflect>(
24 registry: &AppTypeRegistry,
25 effect: &T,
26) -> Option<DynamicEffectSyntax<'static>> {
27 let registry = registry.read();
28 let registration = registry.get(std::any::TypeId::of::<T>())?;
29 let dynamic_syntax = registration.data::<ReflectGetDynamicEffectSyntax>()?;
30 dynamic_syntax
31 .get(effect.as_reflect())
32 .map(GetDynamicEffectSyntax::get)
33}
34
35#[derive(Debug, Clone, Copy)]
37pub struct DynamicEffectSyntax<'a> {
38 pub name: &'a str,
40 pub fields: &'a [DynamicEffectFieldSyntax<'a>],
43 pub is_material: bool,
45}
46
47impl<'a> DynamicEffectSyntax<'a> {
48 pub fn help_fmt(&self) -> String {
50 format!(
51 "Usage: pretty!(\"[my text]({}({}))\")\nSyntax for `{}`:\n{}",
52 self.name,
53 self.parsed_field_usage(),
54 self.name,
55 self.fields
56 .iter()
57 .map(DynamicEffectFieldSyntax::help_fmt)
58 .collect::<Vec<_>>()
59 .join("\n"),
60 )
61 }
62
63 pub fn doc_fmt(&self) -> String {
65 format!(
66 "# [Argument Syntax](pretty_text::parser)\n{}\n{}\n{}",
67 self.fields
68 .iter()
69 .map(DynamicEffectFieldSyntax::doc_fmt)
70 .collect::<Vec<_>>()
71 .join("\n\n"),
72 self.usage(),
73 NOTES,
74 )
75 }
76
77 fn usage(&self) -> String {
78 let insert = if self.is_material {
79 format!(
80 "PrettyTextMaterial(materials.add({}::default()))",
81 self.name.to_case(convert_case::Case::Pascal),
82 )
83 } else {
84 format!(
85 "{}::default()",
86 self.name.to_case(convert_case::Case::Pascal),
87 )
88 };
89
90 format!(
91 "{0}\n// Parsed\nworld.spawn(pretty!(\"[my text]({1}({2}))\"));\n\
92 world.spawn(PrettyParser::bundle(\"[my text]({1}({2}))\")?);\n\n\
93 // Literal\nworld.spawn((\n\tText::new(\"my text\"),\n\t{3},\n));\n{4}",
94 USAGE_HEADER_STR,
95 self.name,
96 self.parsed_field_usage(),
97 insert,
98 USAGE_FOOTER_STR,
99 )
100 }
101
102 fn parsed_field_usage(&self) -> String {
103 self.fields
104 .iter()
105 .map(|field| {
106 let dot_stripped = field.default.strip_suffix(".0").unwrap_or(field.default);
107 field.default.strip_suffix("f32").unwrap_or(dot_stripped)
108 })
109 .collect::<Vec<_>>()
110 .join(", ")
111 }
112}
113
114#[derive(Debug, Clone, Copy)]
116pub struct DynamicEffectFieldSyntax<'a> {
117 pub name: &'a str,
119 pub docs: &'a str,
121 pub default: &'a str,
123 pub arguments: &'a [Argument<'a>],
125}
126
127impl DynamicEffectFieldSyntax<'_> {
128 pub fn help_fmt(&self) -> String {
130 format!(
131 " {}: {}\n Valid arguments: [{}]",
132 self.name,
133 self.docs,
134 self.arguments
135 .iter()
136 .map(Argument::help_fmt)
137 .collect::<Vec<_>>()
138 .join(", ")
139 )
140 }
141
142 pub fn doc_fmt(&self) -> String {
144 format!(
145 "`{}`: {}\n{}",
146 self.name,
147 self.docs,
148 self.arguments
149 .iter()
150 .map(Argument::doc_fmt)
151 .collect::<Vec<_>>()
152 .join("")
153 )
154 }
155}
156
157#[derive(Debug, Clone, Copy)]
159pub struct Argument<'a> {
160 pub base: &'a str,
162 pub shorthand: Option<&'a str>,
164}
165
166impl Argument<'_> {
167 pub fn help_fmt(&self) -> String {
169 match self.shorthand {
170 Some(shorthand) => format!("`\"{}\"` or `\"{}\"`", self.base, shorthand),
171 None => format!("`\"{}\"`", self.base),
172 }
173 }
174
175 pub fn doc_fmt(&self) -> String {
177 match self.shorthand {
178 Some(shorthand) => format!("- `\"{}\"`, `\"{}\"`\n", self.base, shorthand),
179 None => format!("- `\"{}\"`\n", self.base),
180 }
181 }
182}
183
184const USAGE_HEADER_STR: &str = r"
186# Usage
187
188```
189# use bevy_pretty_text::prelude::*;
190# use bevy::prelude::*;
191#
192# struct DummyMaterials;
193# impl DummyMaterials {
194# fn add<T: bevy::prelude::Asset>(&self, _: T) -> bevy::prelude::Handle<T> {
195# bevy::prelude::Handle::default()
196# }
197# }
198#
199# fn __test() -> bevy::prelude::Result {
200# let mut world = bevy::prelude::World::new();
201# #[allow(unused)]
202# let materials = DummyMaterials;";
203
204const USAGE_FOOTER_STR: &str = r"
205# Ok(())
206# }
207# assert!(__test().is_ok());
208```";
209
210const NOTES: &str = r#"
211### Notes
212- See [`ArgParser`](crate::parser::ArgParser) for documentation on argument syntax.\n
213- Positional arguments must appear in order.\n
214- Named arguments can appear after positional arguments but not before.\n
215"#;
216
217#[cfg(feature = "proc-macro")]
218impl quote::ToTokens for DynamicEffectSyntax<'_> {
219 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
220 use quote::TokenStreamExt;
221
222 let name = self.name;
223 let fields = self.fields;
224 let is_material = self.is_material;
225
226 tokens.append_all(quote::quote! {
227 ::bevy_pretty_text::effects::dynamic::DynamicEffectSyntax {
228 name: #name,
229 fields: &[#(#fields,)*],
230 is_material: #is_material,
231 }
232 });
233 }
234}
235
236#[cfg(feature = "proc-macro")]
237impl quote::ToTokens for DynamicEffectFieldSyntax<'_> {
238 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
239 use quote::TokenStreamExt;
240
241 let name = self.name;
242 let docs = self.docs;
243 let default = self.default;
244 let arguments = self.arguments;
245
246 tokens.append_all(quote::quote! {
247 ::bevy_pretty_text::effects::dynamic::DynamicEffectFieldSyntax {
248 name: #name,
249 docs: #docs,
250 default: #default,
251 arguments: &[#(#arguments,)*],
252 }
253 });
254 }
255}
256
257#[cfg(feature = "proc-macro")]
258impl quote::ToTokens for Argument<'_> {
259 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
260 use quote::{TokenStreamExt, quote};
261
262 let base = self.base;
263 let shorthand = match self.shorthand {
264 Some(shorthand) => quote! { Some(#shorthand) },
265 None => quote! { None },
266 };
267
268 tokens.append_all(quote! {
269 ::bevy_pretty_text::effects::dynamic::Argument {
270 base: #base,
271 shorthand: #shorthand,
272 }
273 });
274 }
275}