Rust library to generate static websites
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

perf(markdown): Avoid clone when using components

Princesseuh 30e43e26 d285962c

+52 -131
+52 -131
crates/maudit/src/content/markdown.rs
··· 421 421 } 422 422 423 423 // Second pass: transform events with custom components only if needed 424 - let final_events = match options { 425 - Some(options) if options.components.has_any_components() => { 426 - transform_events_with_components(&events, &options.components, &mut slugger) 427 - } 428 - _ => { 429 - // No options, no components, or empty components - use events as-is 430 - events 424 + if let Some(options) = options { 425 + if options.components.has_any_components() { 426 + transform_events_with_components(&mut events, &options.components, &mut slugger); 431 427 } 432 - }; 428 + } 433 429 434 - pulldown_cmark::html::push_html(&mut html_output, final_events.into_iter()); 430 + pulldown_cmark::html::push_html(&mut html_output, events.into_iter()); 435 431 html_output 436 432 } 437 433 438 - fn transform_events_with_components<'a>( 439 - events: &'a [Event], 434 + fn transform_events_with_components( 435 + events: &mut Vec<Event>, 440 436 components: &MarkdownComponents, 441 437 slugger: &mut slugger::Slugger, 442 - ) -> Vec<Event<'a>> { 443 - let mut transformed = Vec::new(); 438 + ) { 444 439 let mut i = 0; 445 440 446 441 while i < events.len() { 447 - let event = &events[i]; 448 - 449 - match event { 442 + match &events[i] { 450 443 // Headings 451 444 Event::Start(Tag::Heading { 452 445 level, id, classes, .. ··· 464 457 465 458 let custom_html = 466 459 component.render_start(*level as u8, Some(heading_id), &classes_vec); 467 - transformed.push(Event::Html(custom_html.into())); 468 - } else { 469 - transformed.push(event.clone()); 460 + events[i] = Event::Html(custom_html.into()); 470 461 } 471 462 } 472 463 Event::End(TagEnd::Heading(level)) => { 473 464 if let Some(component) = &components.heading { 474 465 let custom_html = component.render_end(*level as u8); 475 - transformed.push(Event::Html(custom_html.into())); 476 - } else { 477 - transformed.push(event.clone()); 466 + events[i] = Event::Html(custom_html.into()); 478 467 } 479 468 } 480 469 ··· 482 471 Event::Start(Tag::Paragraph) => { 483 472 if let Some(component) = &components.paragraph { 484 473 let custom_html = component.render_start(); 485 - transformed.push(Event::Html(custom_html.into())); 486 - } else { 487 - transformed.push(event.clone()); 474 + events[i] = Event::Html(custom_html.into()); 488 475 } 489 476 } 490 477 Event::End(TagEnd::Paragraph) => { 491 478 if let Some(component) = &components.paragraph { 492 479 let custom_html = component.render_end(); 493 - transformed.push(Event::Html(custom_html.into())); 494 - } else { 495 - transformed.push(event.clone()); 480 + events[i] = Event::Html(custom_html.into()); 496 481 } 497 482 } 498 483 ··· 512 497 }; 513 498 let custom_html = 514 499 component.render_start(dest_url.as_ref(), title_str, link_type_converted); 515 - transformed.push(Event::Html(custom_html.into())); 516 - } else { 517 - transformed.push(event.clone()); 500 + events[i] = Event::Html(custom_html.into()); 518 501 } 519 502 } 520 503 Event::End(TagEnd::Link) => { 521 504 if let Some(component) = &components.link { 522 505 let custom_html = component.render_end(); 523 - transformed.push(Event::Html(custom_html.into())); 524 - } else { 525 - transformed.push(event.clone()); 506 + events[i] = Event::Html(custom_html.into()); 526 507 } 527 508 } 528 509 529 - // Images 510 + // Images - special case that consumes multiple events 530 511 Event::Start(Tag::Image { 531 512 dest_url, title, .. 532 513 }) => { ··· 543 524 Some(title.as_ref()) 544 525 }; 545 526 let custom_html = component.render(dest_url.as_ref(), &alt_text, title_str); 546 - transformed.push(Event::Html(custom_html.into())); 547 - // Skip to the end tag 527 + events[i] = Event::Html(custom_html.into()); 528 + 529 + // Skip to the end tag and remove intermediate events 548 530 if let Some(end_index) = find_matching_image_end(events, i) { 549 - i = end_index; 531 + // Remove all events between start and end (inclusive of end) 532 + events.drain(i + 1..=end_index); 550 533 } 551 - } else { 552 - transformed.push(event.clone()); 553 - } 554 - } 555 - Event::End(TagEnd::Image) => { 556 - // Only add this if we didn't handle it above with custom component 557 - if components.image.is_none() { 558 - transformed.push(event.clone()); 559 534 } 560 535 } 561 536 ··· 563 538 Event::Start(Tag::Strong) => { 564 539 if let Some(component) = &components.strong { 565 540 let custom_html = component.render_start(); 566 - transformed.push(Event::Html(custom_html.into())); 567 - } else { 568 - transformed.push(event.clone()); 541 + events[i] = Event::Html(custom_html.into()); 569 542 } 570 543 } 571 544 Event::End(TagEnd::Strong) => { 572 545 if let Some(component) = &components.strong { 573 546 let custom_html = component.render_end(); 574 - transformed.push(Event::Html(custom_html.into())); 575 - } else { 576 - transformed.push(event.clone()); 547 + events[i] = Event::Html(custom_html.into()); 577 548 } 578 549 } 579 550 ··· 581 552 Event::Start(Tag::Emphasis) => { 582 553 if let Some(component) = &components.emphasis { 583 554 let custom_html = component.render_start(); 584 - transformed.push(Event::Html(custom_html.into())); 585 - } else { 586 - transformed.push(event.clone()); 555 + events[i] = Event::Html(custom_html.into()); 587 556 } 588 557 } 589 558 Event::End(TagEnd::Emphasis) => { 590 559 if let Some(component) = &components.emphasis { 591 560 let custom_html = component.render_end(); 592 - transformed.push(Event::Html(custom_html.into())); 593 - } else { 594 - transformed.push(event.clone()); 561 + events[i] = Event::Html(custom_html.into()); 595 562 } 596 563 } 597 564 598 - // Inline Code, i.e `something` 565 + // Inline Code 599 566 Event::Code(code) => { 600 567 if let Some(component) = &components.code { 601 568 let custom_html = component.render(code.as_ref()); 602 - transformed.push(Event::Html(custom_html.into())); 603 - } else { 604 - transformed.push(event.clone()); 569 + events[i] = Event::Html(custom_html.into()); 605 570 } 606 571 } 607 572 608 - // Blockquotes, i.e. > quote 573 + // Blockquotes 609 574 Event::Start(Tag::BlockQuote(kind)) => { 610 575 if let Some(component) = &components.blockquote { 611 576 let kind_converted = kind.as_ref().map(|k| k.into()); 612 577 let custom_html = component.render_start(kind_converted); 613 - transformed.push(Event::Html(custom_html.into())); 614 - } else { 615 - transformed.push(event.clone()); 578 + events[i] = Event::Html(custom_html.into()); 616 579 } 617 580 } 618 581 Event::End(TagEnd::BlockQuote(kind)) => { 619 582 if let Some(component) = &components.blockquote { 620 583 let kind_converted = kind.as_ref().map(|k| k.into()); 621 584 let custom_html = component.render_end(kind_converted); 622 - transformed.push(Event::Html(custom_html.into())); 623 - } else { 624 - transformed.push(event.clone()); 585 + events[i] = Event::Html(custom_html.into()); 625 586 } 626 587 } 627 588 628 - // Hard Breaks, i.e. double spaces at the end of a line 589 + // Hard Breaks 629 590 Event::HardBreak => { 630 591 if let Some(component) = &components.hard_break { 631 592 let custom_html = component.render(); 632 - transformed.push(Event::Html(custom_html.into())); 633 - } else { 634 - transformed.push(event.clone()); 593 + events[i] = Event::Html(custom_html.into()); 635 594 } 636 595 } 637 596 638 - // Horizontal Rules, i.e. --- -> <hr /> 597 + // Horizontal Rules 639 598 Event::Rule => { 640 599 if let Some(component) = &components.horizontal_rule { 641 600 let custom_html = component.render(); 642 - transformed.push(Event::Html(custom_html.into())); 643 - } else { 644 - transformed.push(event.clone()); 601 + events[i] = Event::Html(custom_html.into()); 645 602 } 646 603 } 647 604 648 - // Lists, i.e. - item 605 + // Lists 649 606 Event::Start(Tag::List(first_number)) => { 650 607 if let Some(component) = &components.list { 651 608 let list_type = if first_number.is_some() { ··· 654 611 ListType::Unordered 655 612 }; 656 613 let custom_html = component.render_start(list_type, *first_number); 657 - transformed.push(Event::Html(custom_html.into())); 658 - } else { 659 - transformed.push(event.clone()); 614 + events[i] = Event::Html(custom_html.into()); 660 615 } 661 616 } 662 617 Event::End(TagEnd::List(ordered)) => { ··· 667 622 ListType::Unordered 668 623 }; 669 624 let custom_html = component.render_end(list_type); 670 - transformed.push(Event::Html(custom_html.into())); 671 - } else { 672 - transformed.push(event.clone()); 625 + events[i] = Event::Html(custom_html.into()); 673 626 } 674 627 } 675 628 ··· 677 630 Event::Start(Tag::Item) => { 678 631 if let Some(component) = &components.list_item { 679 632 let custom_html = component.render_start(); 680 - transformed.push(Event::Html(custom_html.into())); 681 - } else { 682 - transformed.push(event.clone()); 633 + events[i] = Event::Html(custom_html.into()); 683 634 } 684 635 } 685 636 Event::End(TagEnd::Item) => { 686 637 if let Some(component) = &components.list_item { 687 638 let custom_html = component.render_end(); 688 - transformed.push(Event::Html(custom_html.into())); 689 - } else { 690 - transformed.push(event.clone()); 639 + events[i] = Event::Html(custom_html.into()); 691 640 } 692 641 } 693 642 ··· 695 644 Event::Start(Tag::Strikethrough) => { 696 645 if let Some(component) = &components.strikethrough { 697 646 let custom_html = component.render_start(); 698 - transformed.push(Event::Html(custom_html.into())); 699 - } else { 700 - transformed.push(event.clone()); 647 + events[i] = Event::Html(custom_html.into()); 701 648 } 702 649 } 703 650 Event::End(TagEnd::Strikethrough) => { 704 651 if let Some(component) = &components.strikethrough { 705 652 let custom_html = component.render_end(); 706 - transformed.push(Event::Html(custom_html.into())); 707 - } else { 708 - transformed.push(event.clone()); 653 + events[i] = Event::Html(custom_html.into()); 709 654 } 710 655 } 711 656 ··· 713 658 Event::TaskListMarker(checked) => { 714 659 if let Some(component) = &components.task_list_marker { 715 660 let custom_html = component.render(*checked); 716 - transformed.push(Event::Html(custom_html.into())); 717 - } else { 718 - transformed.push(event.clone()); 661 + events[i] = Event::Html(custom_html.into()); 719 662 } 720 663 } 721 664 ··· 735 678 }) 736 679 .collect(); 737 680 let custom_html = component.render_start(&alignment_vec); 738 - transformed.push(Event::Html(custom_html.into())); 739 - } else { 740 - transformed.push(event.clone()); 681 + events[i] = Event::Html(custom_html.into()); 741 682 } 742 683 } 743 684 Event::End(TagEnd::Table) => { 744 685 if let Some(component) = &components.table { 745 686 let custom_html = component.render_end(); 746 - transformed.push(Event::Html(custom_html.into())); 747 - } else { 748 - transformed.push(event.clone()); 687 + events[i] = Event::Html(custom_html.into()); 749 688 } 750 689 } 751 690 ··· 753 692 Event::Start(Tag::TableHead) => { 754 693 if let Some(component) = &components.table_head { 755 694 let custom_html = component.render_start(); 756 - transformed.push(Event::Html(custom_html.into())); 757 - } else { 758 - transformed.push(event.clone()); 695 + events[i] = Event::Html(custom_html.into()); 759 696 } 760 697 } 761 698 Event::End(TagEnd::TableHead) => { 762 699 if let Some(component) = &components.table_head { 763 700 let custom_html = component.render_end(); 764 - transformed.push(Event::Html(custom_html.into())); 765 - } else { 766 - transformed.push(event.clone()); 701 + events[i] = Event::Html(custom_html.into()); 767 702 } 768 703 } 769 704 ··· 771 706 Event::Start(Tag::TableRow) => { 772 707 if let Some(component) = &components.table_row { 773 708 let custom_html = component.render_start(); 774 - transformed.push(Event::Html(custom_html.into())); 775 - } else { 776 - transformed.push(event.clone()); 709 + events[i] = Event::Html(custom_html.into()); 777 710 } 778 711 } 779 712 Event::End(TagEnd::TableRow) => { 780 713 if let Some(component) = &components.table_row { 781 714 let custom_html = component.render_end(); 782 - transformed.push(Event::Html(custom_html.into())); 783 - } else { 784 - transformed.push(event.clone()); 715 + events[i] = Event::Html(custom_html.into()); 785 716 } 786 717 } 787 718 ··· 791 722 // For now, assume it's not a header and no specific alignment 792 723 // TODO: Track context to determine if we're in a table head and column alignment 793 724 let custom_html = component.render_start(false, None); 794 - transformed.push(Event::Html(custom_html.into())); 795 - } else { 796 - transformed.push(event.clone()); 725 + events[i] = Event::Html(custom_html.into()); 797 726 } 798 727 } 799 728 Event::End(TagEnd::TableCell) => { 800 729 if let Some(component) = &components.table_cell { 801 730 // TODO: Track context to determine if we're in a table head 802 731 let custom_html = component.render_end(false); 803 - transformed.push(Event::Html(custom_html.into())); 804 - } else { 805 - transformed.push(event.clone()); 732 + events[i] = Event::Html(custom_html.into()); 806 733 } 807 734 } 808 735 809 736 // All other events pass through unchanged 810 - _ => { 811 - transformed.push(event.clone()); 812 - } 737 + _ => {} 813 738 } 814 739 i += 1; 815 740 } 816 - 817 - transformed 818 741 } 819 742 820 743 fn find_matching_heading_end(events: &[Event], start_index: usize) -> Option<usize> { ··· 894 817 895 818 // Both should produce identical output 896 819 assert_eq!(html_no_options, html_with_empty_options); 897 - 820 + 898 821 // Both should have default heading behavior (id attributes and proper HTML structure) 899 822 assert!(html_no_options.contains("id=\"")); 900 823 assert!(html_no_options.contains("<h1")); ··· 902 825 assert!(html_no_options.contains("<h3")); 903 826 assert!(html_with_empty_options.contains("id=\"")); 904 827 } 905 - 906 - 907 828 }