1use proc_macro::TokenStream;
2use proc_macro2::{Span, TokenStream as TokenStream2};
3use quote::quote;
4use syn::{
5 Expr, ExprLit, Ident, Lit, LitStr, Result, Token, braced,
6 parse::{Parse, ParseStream},
7 parse_macro_input,
8};
9
10#[proc_macro]
11pub fn view(input: TokenStream) -> TokenStream {
12 let root = parse_macro_input!(input as ViewRoot);
13 root.expand().into()
14}
15
16struct ViewRoot {
17 node: Node,
18}
19
20impl ViewRoot {
21 fn expand(self) -> TokenStream2 {
22 self.node.expand()
23 }
24}
25
26impl Parse for ViewRoot {
27 fn parse(input: ParseStream<'_>) -> Result<Self> {
28 Ok(Self {
29 node: input.parse()?,
30 })
31 }
32}
33
34struct Node {
35 name: Ident,
36 attrs: Vec<Attribute>,
37 children: Vec<Child>,
38}
39
40struct Attribute {
41 name: Ident,
42 value: Expr,
43}
44
45enum Child {
46 Node(Node),
47 Expr(Expr),
48 Text(LitStr),
49}
50
51impl Parse for Node {
52 fn parse(input: ParseStream<'_>) -> Result<Self> {
53 input.parse::<Token![<]>()?;
54 let name: Ident = input.parse()?;
55 let mut attrs = Vec::new();
56
57 while !(input.peek(Token![>]) || (input.peek(Token![/]) && input.peek2(Token![>]))) {
60 let attr_name: Ident = input.parse()?;
61 input.parse::<Token![=]>()?;
62 let value = if input.peek(syn::token::Brace) {
63 let content;
64 braced!(content in input);
65 content.parse()?
66 } else {
67 Expr::Lit(ExprLit {
68 attrs: Vec::new(),
69 lit: input.parse::<Lit>()?,
70 })
71 };
72 attrs.push(Attribute {
73 name: attr_name,
74 value,
75 });
76 }
77
78 if input.peek(Token![/]) {
79 input.parse::<Token![/]>()?;
80 input.parse::<Token![>]>()?;
81 return Ok(Self {
82 name,
83 attrs,
84 children: Vec::new(),
85 });
86 }
87
88 input.parse::<Token![>]>()?;
89 let mut children = Vec::new();
90
91 while !(input.peek(Token![<]) && input.peek2(Token![/])) {
92 children.push(input.parse()?);
93 }
94
95 input.parse::<Token![<]>()?;
96 input.parse::<Token![/]>()?;
97 let close_name: Ident = input.parse()?;
98 if close_name != name {
99 return Err(syn::Error::new(
100 close_name.span(),
101 format!("expected closing tag </{}>", name),
102 ));
103 }
104 input.parse::<Token![>]>()?;
105
106 Ok(Self {
107 name,
108 attrs,
109 children,
110 })
111 }
112}
113
114impl Parse for Child {
115 fn parse(input: ParseStream<'_>) -> Result<Self> {
116 if input.peek(Token![<]) {
117 Ok(Self::Node(input.parse()?))
118 } else if input.peek(LitStr) {
119 Ok(Self::Text(input.parse()?))
120 } else if input.peek(syn::token::Brace) {
121 let content;
122 braced!(content in input);
123 Ok(Self::Expr(content.parse()?))
124 } else {
125 Err(input.error("expected a child element, string literal, or {expr}"))
126 }
127 }
128}
129
130impl Child {
131 fn expand(self) -> TokenStream2 {
132 match self {
133 Self::Node(node) => node.expand(),
134 Self::Expr(expr) => quote! { ::ansiq_core::IntoElement::into_element(#expr) },
135 Self::Text(text) => quote! { ::ansiq_core::Element::new_text(#text) },
136 }
137 }
138}
139
140impl Node {
141 fn expand(self) -> TokenStream2 {
142 match self.name.to_string().as_str() {
143 "Box" => self.expand_box(),
144 "Text" => self.expand_text(),
145 "Paragraph" => self.expand_paragraph(),
146 "RichText" => self.expand_rich_text(),
147 "Pane" => self.expand_pane(),
148 "Block" => self.expand_block(),
149 "List" => self.expand_list(),
150 "Tabs" => self.expand_tabs(),
151 "Gauge" => self.expand_gauge(),
152 "Clear" => self.expand_clear(),
153 "LineGauge" => self.expand_line_gauge(),
154 "Table" => self.expand_table(),
155 "Sparkline" => self.expand_sparkline(),
156 "BarChart" => self.expand_bar_chart(),
157 "Chart" => self.expand_chart(),
158 "Canvas" => self.expand_canvas(),
159 "Monthly" => self.expand_monthly(),
160 "ScrollView" => self.expand_scroll_view(),
161 "Scrollbar" => self.expand_scrollbar(),
162 "StreamingText" => self.expand_streaming_text(),
163 "Input" => self.expand_input(),
164 "StatusBar" => self.expand_status_bar(),
165 _ => self.expand_custom_component(),
166 }
167 }
168
169 fn expand_box(self) -> TokenStream2 {
170 let attrs = self.attrs;
171 let children = self.children;
172 let direction = attr_expr(&attrs, "direction")
173 .and_then(expr_as_string)
174 .unwrap_or_else(|| "column".to_string());
175 let mut builder = match direction.as_str() {
176 "row" => quote! { ::ansiq_widgets::Box::row() },
177 _ => quote! { ::ansiq_widgets::Box::column() },
178 };
179
180 if let Some(gap) = attr_expr(&attrs, "gap") {
181 builder = quote! { #builder .gap(#gap) };
182 }
183
184 for child in children {
185 let child = child.expand();
186 builder = quote! { #builder .child(#child) };
187 }
188
189 finish_element(builder, &attrs)
190 }
191
192 fn expand_text(self) -> TokenStream2 {
193 let attrs = self.attrs;
194 let children = self.children;
195 let content = content_expr(&attrs, &children).unwrap_or_else(|| quote! { "" });
196 finish_element(quote! { ::ansiq_widgets::Text::new(#content) }, &attrs)
197 }
198
199 fn expand_paragraph(self) -> TokenStream2 {
200 let attrs = self.attrs;
201 let children = self.children;
202 let content = content_expr(&attrs, &children).unwrap_or_else(|| quote! { "" });
203 let mut builder = quote! { ::ansiq_widgets::Paragraph::new(#content) };
204 if let Some(alignment) = attr_expr(&attrs, "alignment") {
205 builder = quote! { #builder .alignment(#alignment) };
206 }
207 if let Some(wrap) = attr_expr(&attrs, "wrap") {
208 builder = quote! { #builder .wrap(#wrap) };
209 }
210 if let Some(block) = attr_expr(&attrs, "block") {
211 builder = quote! { #builder .block(#block) };
212 }
213 if let Some(scroll) = attr_expr(&attrs, "scroll") {
214 builder = quote! { #builder .scroll(#scroll) };
215 } else if attr_expr(&attrs, "scroll_y").is_some() || attr_expr(&attrs, "scroll_x").is_some()
216 {
217 let scroll_y = attr_expr(&attrs, "scroll_y")
218 .map(|expr| quote! { #expr })
219 .unwrap_or_else(|| quote! { 0u16 });
220 let scroll_x = attr_expr(&attrs, "scroll_x")
221 .map(|expr| quote! { #expr })
222 .unwrap_or_else(|| quote! { 0u16 });
223 builder = quote! { #builder .scroll((#scroll_y, #scroll_x)) };
224 }
225 finish_element(builder, &attrs)
226 }
227
228 fn expand_rich_text(self) -> TokenStream2 {
229 let attrs = self.attrs;
230 let block = attr_expr(&attrs, "block")
231 .map(|expr| quote! { #expr })
232 .unwrap_or_else(|| {
233 quote! { ::ansiq_core::HistoryBlock { lines: ::std::vec::Vec::new() } }
234 });
235 finish_element(quote! { ::ansiq_widgets::RichText::new(#block) }, &attrs)
236 }
237
238 fn expand_pane(self) -> TokenStream2 {
239 let attrs = self.attrs;
240 let children = self.children;
241 let mut builder = quote! { ::ansiq_widgets::Pane::new() };
242 if let Some(title) = attr_expr(&attrs, "title") {
243 builder = quote! { #builder .title(#title) };
244 }
245 for child in children {
246 let child = child.expand();
247 builder = quote! { #builder .child(#child) };
248 }
249 finish_element(builder, &attrs)
250 }
251
252 fn expand_block(self) -> TokenStream2 {
253 let attrs = self.attrs;
254 let children = self.children;
255 let mut builder = quote! { ::ansiq_widgets::Block::new() };
256 if let Some(title) = attr_expr(&attrs, "title") {
257 builder = quote! { #builder .title(#title) };
258 }
259 if let Some(title_top) = attr_expr(&attrs, "title_top") {
260 builder = quote! { #builder .title_top(#title_top) };
261 }
262 if let Some(title_bottom) = attr_expr(&attrs, "title_bottom") {
263 builder = quote! { #builder .title_bottom(#title_bottom) };
264 }
265 if let Some(title_alignment) = attr_expr(&attrs, "title_alignment") {
266 builder = quote! { #builder .title_alignment(#title_alignment) };
267 }
268 if let Some(title_position) = attr_expr(&attrs, "title_position") {
269 builder = quote! { #builder .title_position(#title_position) };
270 }
271 if let Some(bordered) = attr_expr(&attrs, "bordered") {
272 builder = quote! { #builder .bordered_flag(#bordered) };
273 }
274 if let Some(borders) = attr_expr(&attrs, "borders") {
275 builder = quote! { #builder .borders(#borders) };
276 }
277 if let Some(border_type) = attr_expr(&attrs, "border_type") {
278 builder = quote! { #builder .border_type(#border_type) };
279 }
280 if let Some(border_set) = attr_expr(&attrs, "border_set") {
281 builder = quote! { #builder .border_set(#border_set) };
282 }
283 if let Some(padding) = attr_expr(&attrs, "padding") {
284 builder = quote! { #builder .padding(#padding) };
285 }
286 if let Some(border_style) = attr_expr(&attrs, "border_style") {
287 builder = quote! { #builder .border_style(#border_style) };
288 }
289 if let Some(title_style) = attr_expr(&attrs, "title_style") {
290 builder = quote! { #builder .title_style(#title_style) };
291 }
292 for child in children {
293 let child = child.expand();
294 builder = quote! { #builder .child(#child) };
295 }
296 finish_element(builder, &attrs)
297 }
298
299 fn expand_list(self) -> TokenStream2 {
300 let attrs = self.attrs;
301 let items = attr_expr(&attrs, "items")
302 .map(|expr| quote! { #expr })
303 .unwrap_or_else(|| quote! { ::std::vec::Vec::<::std::string::String>::new() });
304 let mut builder = quote! { ::ansiq_widgets::List::new(#items) };
305 if let Some(items) = attr_expr(&attrs, "items") {
306 builder = quote! { #builder .items(#items) };
307 }
308 if let Some(block) = attr_expr(&attrs, "block") {
309 builder = quote! { #builder .block(#block) };
310 }
311 if let Some(selected) = attr_expr(&attrs, "selected") {
312 builder = quote! { #builder .selected(#selected) };
313 }
314 if let Some(on_select) = attr_expr(&attrs, "on_select") {
315 builder = quote! { #builder .on_select(#on_select) };
316 }
317 finish_element(builder, &attrs)
318 }
319
320 fn expand_tabs(self) -> TokenStream2 {
321 let attrs = self.attrs;
322 let titles = attr_expr(&attrs, "titles")
323 .map(|expr| quote! { #expr })
324 .unwrap_or_else(|| quote! { ::std::vec::Vec::<::std::string::String>::new() });
325 let mut builder = quote! { ::ansiq_widgets::Tabs::new(#titles) };
326 if let Some(block) = attr_expr(&attrs, "block") {
327 builder = quote! { #builder .block(#block) };
328 }
329 if let Some(titles) = attr_expr(&attrs, "titles") {
330 builder = quote! { #builder .titles(#titles) };
331 }
332 if let Some(selected) = attr_expr(&attrs, "selected") {
333 builder = quote! { #builder .selected(#selected) };
334 }
335 if let Some(highlight_style) = attr_expr(&attrs, "highlight_style") {
336 builder = quote! { #builder .highlight_style(#highlight_style) };
337 }
338 if let Some(divider) = attr_expr(&attrs, "divider") {
339 builder = quote! { #builder .divider(#divider) };
340 }
341 if let Some(padding_left) = attr_expr(&attrs, "padding_left") {
342 builder = quote! { #builder .padding_left(#padding_left) };
343 }
344 if let Some(padding_right) = attr_expr(&attrs, "padding_right") {
345 builder = quote! { #builder .padding_right(#padding_right) };
346 }
347 if let Some(on_select) = attr_expr(&attrs, "on_select") {
348 builder = quote! { #builder .on_select(#on_select) };
349 }
350 finish_element(builder, &attrs)
351 }
352
353 fn expand_gauge(self) -> TokenStream2 {
354 let attrs = self.attrs;
355 let mut builder = quote! { ::ansiq_widgets::Gauge::new() };
356 if let Some(block) = attr_expr(&attrs, "block") {
357 builder = quote! { #builder .block(#block) };
358 }
359 if let Some(ratio) = attr_expr(&attrs, "ratio") {
360 builder = quote! { #builder .ratio(#ratio) };
361 }
362 if let Some(percent) = attr_expr(&attrs, "percent") {
363 builder = quote! { #builder .percent(#percent) };
364 }
365 if let Some(label) = attr_expr(&attrs, "label") {
366 builder = quote! { #builder .label(#label) };
367 }
368 if let Some(use_unicode) = attr_expr(&attrs, "use_unicode") {
369 builder = quote! { #builder .use_unicode(#use_unicode) };
370 }
371 if let Some(gauge_style) = attr_expr(&attrs, "gauge_style") {
372 builder = quote! { #builder .gauge_style(#gauge_style) };
373 }
374 finish_element(builder, &attrs)
375 }
376
377 fn expand_clear(self) -> TokenStream2 {
378 let attrs = self.attrs;
379 finish_element(quote! { ::ansiq_widgets::Clear::new() }, &attrs)
380 }
381
382 fn expand_line_gauge(self) -> TokenStream2 {
383 let attrs = self.attrs;
384 let mut builder = quote! { ::ansiq_widgets::LineGauge::new() };
385 if let Some(block) = attr_expr(&attrs, "block") {
386 builder = quote! { #builder .block(#block) };
387 }
388 if let Some(ratio) = attr_expr(&attrs, "ratio") {
389 builder = quote! { #builder .ratio(#ratio) };
390 }
391 if let Some(percent) = attr_expr(&attrs, "percent") {
392 builder = quote! { #builder .percent(#percent) };
393 }
394 if let Some(label) = attr_expr(&attrs, "label") {
395 builder = quote! { #builder .label(#label) };
396 }
397 if let Some(line_set) = attr_expr(&attrs, "line_set") {
398 builder = quote! { #builder .line_set(#line_set) };
399 }
400 if let Some(filled_symbol) = attr_expr(&attrs, "filled_symbol") {
401 builder = quote! { #builder .filled_symbol(#filled_symbol) };
402 }
403 if let Some(unfilled_symbol) = attr_expr(&attrs, "unfilled_symbol") {
404 builder = quote! { #builder .unfilled_symbol(#unfilled_symbol) };
405 }
406 if let Some(filled_style) = attr_expr(&attrs, "filled_style") {
407 builder = quote! { #builder .filled_style(#filled_style) };
408 }
409 if let Some(unfilled_style) = attr_expr(&attrs, "unfilled_style") {
410 builder = quote! { #builder .unfilled_style(#unfilled_style) };
411 }
412 finish_element(builder, &attrs)
413 }
414
415 fn expand_table(self) -> TokenStream2 {
416 let attrs = self.attrs;
417 let rows = attr_expr(&attrs, "rows")
418 .map(|expr| quote! { #expr })
419 .unwrap_or_else(
420 || quote! { ::std::vec::Vec::<::std::vec::Vec<::std::string::String>>::new() },
421 );
422 let widths = attr_expr(&attrs, "widths")
423 .map(|expr| quote! { #expr })
424 .unwrap_or_else(|| quote! { ::std::vec::Vec::<::ansiq_core::Constraint>::new() });
425 let mut builder = quote! { ::ansiq_widgets::Table::new(#rows, #widths) };
426 if let Some(block) = attr_expr(&attrs, "block") {
427 builder = quote! { #builder .block(#block) };
428 }
429 if let Some(header) = attr_expr(&attrs, "header") {
430 builder = quote! { #builder .header(#header) };
431 } else if let Some(headers) = attr_expr(&attrs, "headers") {
432 builder = quote! { #builder .headers(#headers) };
433 }
434 if let Some(footer) = attr_expr(&attrs, "footer") {
435 builder = quote! { #builder .footer(#footer) };
436 }
437 if let Some(widths) = attr_expr(&attrs, "widths") {
438 builder = quote! { #builder .widths(#widths) };
439 }
440 if let Some(column_spacing) = attr_expr(&attrs, "column_spacing") {
441 builder = quote! { #builder .column_spacing(#column_spacing) };
442 }
443 if let Some(flex) = attr_expr(&attrs, "flex") {
444 builder = quote! { #builder .flex(#flex) };
445 }
446 if let Some(alignments) = attr_expr(&attrs, "alignments") {
447 builder = quote! { #builder .alignments(#alignments) };
448 }
449 if let Some(selected) = attr_expr(&attrs, "selected") {
450 builder = quote! { #builder .selected(#selected) };
451 }
452 if let Some(highlight_symbol) = attr_expr(&attrs, "highlight_symbol") {
453 builder = quote! { #builder .highlight_symbol(#highlight_symbol) };
454 }
455 if let Some(on_select) = attr_expr(&attrs, "on_select") {
456 builder = quote! { #builder .on_select(#on_select) };
457 }
458 finish_element(builder, &attrs)
459 }
460
461 fn expand_sparkline(self) -> TokenStream2 {
462 let attrs = self.attrs;
463 let mut builder = quote! { ::ansiq_widgets::Sparkline::new() };
464 if let Some(values) = attr_expr(&attrs, "values") {
465 builder = quote! { #builder .values(#values) };
466 }
467 if let Some(max) = attr_expr(&attrs, "max") {
468 builder = quote! { #builder .max(#max) };
469 }
470 finish_element(builder, &attrs)
471 }
472
473 fn expand_bar_chart(self) -> TokenStream2 {
474 let attrs = self.attrs;
475 let mut builder = quote! { ::ansiq_widgets::BarChart::new() };
476 if let Some(bars) = attr_expr(&attrs, "bars") {
477 builder = quote! { #builder .bars(#bars) };
478 }
479 if let Some(max) = attr_expr(&attrs, "max") {
480 builder = quote! { #builder .max(#max) };
481 }
482 if let Some(bar_width) = attr_expr(&attrs, "bar_width") {
483 builder = quote! { #builder .bar_width(#bar_width) };
484 }
485 finish_element(builder, &attrs)
486 }
487
488 fn expand_chart(self) -> TokenStream2 {
489 let attrs = self.attrs;
490 let datasets = attr_expr(&attrs, "datasets");
491 let mut builder = quote! { ::ansiq_widgets::Chart::new() };
492 if let Some(min_y) = attr_expr(&attrs, "min_y") {
493 builder = quote! { #builder .min_y(#min_y) };
494 }
495 if let Some(max_y) = attr_expr(&attrs, "max_y") {
496 builder = quote! { #builder .max_y(#max_y) };
497 }
498 let builder = if let Some(datasets) = datasets {
499 quote! {{
500 let mut chart = #builder;
501 for dataset in #datasets {
502 chart = if let Some(label) = dataset.label {
503 chart.named_dataset(label, dataset.points)
504 } else {
505 chart.dataset(dataset.points)
506 };
507 }
508 chart
509 }}
510 } else {
511 builder
512 };
513 finish_element(builder, &attrs)
514 }
515
516 fn expand_canvas(self) -> TokenStream2 {
517 let attrs = self.attrs;
518 let cells = attr_expr(&attrs, "cells");
519 let mut builder = quote! { ::ansiq_widgets::Canvas::new() };
520 if let Some(width) = attr_expr(&attrs, "width") {
521 let height = attr_expr(&attrs, "height")
522 .map(|expr| quote! { #expr })
523 .unwrap_or_else(|| quote! { 8u16 });
524 builder = quote! { #builder .size(#width, #height) };
525 }
526 let builder = if let Some(cells) = cells {
527 quote! {{
528 let mut canvas = #builder;
529 for cell in #cells {
530 canvas = canvas.point(cell.x, cell.y, cell.symbol);
531 }
532 canvas
533 }}
534 } else {
535 builder
536 };
537 finish_element(builder, &attrs)
538 }
539
540 fn expand_monthly(self) -> TokenStream2 {
541 let attrs = self.attrs;
542 let mut builder = quote! { ::ansiq_widgets::Monthly::new() };
543 if let Some(year) = attr_expr(&attrs, "year") {
544 builder = quote! { #builder .year(#year) };
545 }
546 if let Some(month) = attr_expr(&attrs, "month") {
547 builder = quote! { #builder .month(#month) };
548 }
549 if let Some(selected_day) = attr_expr(&attrs, "selected_day") {
550 builder = quote! { #builder .selected_day(#selected_day) };
551 }
552 finish_element(builder, &attrs)
553 }
554
555 fn expand_scrollbar(self) -> TokenStream2 {
556 let attrs = self.attrs;
557 let orientation = attr_expr(&attrs, "orientation")
558 .map(|expr| quote! { #expr })
559 .unwrap_or_else(|| quote! { ::ansiq_core::ScrollbarOrientation::VerticalRight });
560 let mut builder = quote! { ::ansiq_widgets::Scrollbar::new(#orientation) };
561 if let Some(position) = attr_expr(&attrs, "position") {
562 builder = quote! { #builder .position(#position) };
563 }
564 if let Some(content_length) = attr_expr(&attrs, "content_length") {
565 builder = quote! { #builder .content_length(#content_length) };
566 }
567 if let Some(viewport_length) = attr_expr(&attrs, "viewport_length") {
568 builder = quote! { #builder .viewport_length(#viewport_length) };
569 }
570 if let Some(viewport_content_length) = attr_expr(&attrs, "viewport_content_length") {
571 builder = quote! { #builder .viewport_content_length(#viewport_content_length) };
572 }
573 if let Some(symbols) = attr_expr(&attrs, "symbols") {
574 builder = quote! { #builder .symbols(#symbols) };
575 }
576 if let Some(thumb_symbol) = attr_expr(&attrs, "thumb_symbol") {
577 builder = quote! { #builder .thumb_symbol(#thumb_symbol) };
578 }
579 if let Some(track_symbol) = attr_expr(&attrs, "track_symbol") {
580 builder = quote! { #builder .track_symbol(#track_symbol) };
581 }
582 if let Some(begin_symbol) = attr_expr(&attrs, "begin_symbol") {
583 builder = quote! { #builder .begin_symbol(#begin_symbol) };
584 }
585 if let Some(end_symbol) = attr_expr(&attrs, "end_symbol") {
586 builder = quote! { #builder .end_symbol(#end_symbol) };
587 }
588 if let Some(thumb_style) = attr_expr(&attrs, "thumb_style") {
589 builder = quote! { #builder .thumb_style(#thumb_style) };
590 }
591 if let Some(track_style) = attr_expr(&attrs, "track_style") {
592 builder = quote! { #builder .track_style(#track_style) };
593 }
594 if let Some(begin_style) = attr_expr(&attrs, "begin_style") {
595 builder = quote! { #builder .begin_style(#begin_style) };
596 }
597 if let Some(end_style) = attr_expr(&attrs, "end_style") {
598 builder = quote! { #builder .end_style(#end_style) };
599 }
600 if let Some(on_scroll) = attr_expr(&attrs, "on_scroll") {
601 builder = quote! { #builder .on_scroll(#on_scroll) };
602 }
603 finish_element(builder, &attrs)
604 }
605
606 fn expand_scroll_view(self) -> TokenStream2 {
607 let attrs = self.attrs;
608 let children = self.children;
609 let mut builder = quote! { ::ansiq_widgets::ScrollView::new() };
610 if let Some(follow_bottom) = attr_expr(&attrs, "follow_bottom") {
611 builder = quote! { #builder .follow_bottom(#follow_bottom) };
612 }
613 if let Some(offset) = attr_expr(&attrs, "offset") {
614 builder = quote! { #builder .offset(#offset) };
615 }
616 if let Some(on_scroll) = attr_expr(&attrs, "on_scroll") {
617 builder = quote! { #builder .on_scroll(#on_scroll) };
618 }
619 for child in children {
620 let child = child.expand();
621 builder = quote! { #builder .child(#child) };
622 }
623 finish_element(builder, &attrs)
624 }
625
626 fn expand_streaming_text(self) -> TokenStream2 {
627 let attrs = self.attrs;
628 let children = self.children;
629 let content = content_expr(&attrs, &children).unwrap_or_else(|| quote! { "" });
630 finish_element(
631 quote! { ::ansiq_widgets::StreamingText::new(#content) },
632 &attrs,
633 )
634 }
635
636 fn expand_input(self) -> TokenStream2 {
637 let attrs = self.attrs;
638 let mut builder = quote! { ::ansiq_widgets::Input::new() };
639 if let Some(value) = attr_expr(&attrs, "value") {
640 builder = quote! { #builder .value(#value) };
641 }
642 if let Some(placeholder) = attr_expr(&attrs, "placeholder") {
643 builder = quote! { #builder .placeholder(#placeholder) };
644 }
645 if let Some(on_change) = attr_expr(&attrs, "on_change") {
646 builder = quote! { #builder .on_change(#on_change) };
647 }
648 if let Some(on_submit) = attr_expr(&attrs, "on_submit") {
649 builder = quote! { #builder .on_submit(#on_submit) };
650 }
651 finish_element(builder, &attrs)
652 }
653
654 fn expand_status_bar(self) -> TokenStream2 {
655 let attrs = self.attrs;
656 let content = attr_expr(&attrs, "text")
657 .or_else(|| attr_expr(&attrs, "content"))
658 .map(|expr| quote! { #expr })
659 .unwrap_or_else(|| quote! { "" });
660 finish_element(quote! { ::ansiq_widgets::StatusBar::new(#content) }, &attrs)
661 }
662
663 fn expand_custom_component(self) -> TokenStream2 {
664 if !self.attrs.is_empty() || !self.children.is_empty() {
665 return compile_error(
666 self.name.span(),
667 "custom components with props or children are not supported yet",
668 );
669 }
670
671 let name = self.name;
672 quote! { ::ansiq_core::component_with_cx(stringify!(#name), #name) }
673 }
674}
675
676fn finish_element(builder: TokenStream2, attrs: &[Attribute]) -> TokenStream2 {
677 let mut element = quote! { #builder .build() };
678
679 if let Some(layout) = attr_expr(attrs, "layout") {
680 element = quote! { #element .with_layout(#layout) };
681 }
682 if let Some(style) = attr_expr(attrs, "style") {
683 element = quote! { #element .with_style(#style) };
684 }
685 if let Some(focusable) = attr_expr(attrs, "focusable") {
686 element = quote! { #element .with_focusable(#focusable) };
687 }
688
689 element
690}
691
692fn attr_expr<'a>(attrs: &'a [Attribute], name: &str) -> Option<&'a Expr> {
693 attrs
694 .iter()
695 .find(|attr| attr.name == Ident::new(name, Span::call_site()))
696 .map(|attr| &attr.value)
697}
698
699fn content_expr(attrs: &[Attribute], children: &[Child]) -> Option<TokenStream2> {
700 attr_expr(attrs, "content")
701 .or_else(|| attr_expr(attrs, "text"))
702 .map(|expr| quote! { #expr })
703 .or_else(|| {
704 if children.len() == 1 {
705 match &children[0] {
706 Child::Text(text) => Some(quote! { #text }),
707 Child::Expr(expr) => Some(quote! { #expr }),
708 Child::Node(_) => None,
709 }
710 } else {
711 None
712 }
713 })
714}
715
716fn expr_as_string(expr: &Expr) -> Option<String> {
717 match expr {
718 Expr::Lit(lit) => match &lit.lit {
719 syn::Lit::Str(value) => Some(value.value()),
720 _ => None,
721 },
722 _ => None,
723 }
724}
725
726fn compile_error(span: Span, message: &str) -> TokenStream2 {
727 syn::Error::new(span, message).to_compile_error()
728}