|
1 | 1 | use anyhow::{anyhow, bail};
|
2 | 2 | use std::{
|
| 3 | + borrow::Cow, |
3 | 4 | io::{BufRead, Lines},
|
4 | 5 | iter::Peekable,
|
5 | 6 | };
|
@@ -431,6 +432,141 @@ impl ListMarker {
|
431 | 432 | }
|
432 | 433 | }
|
433 | 434 |
|
| 435 | +fn process_inline_macros(line: &str) -> anyhow::Result<Cow<'_, str>> { |
| 436 | + let mut chars = line.char_indices(); |
| 437 | + loop { |
| 438 | + let (start, end, a_macro) = match get_next_line_component(&mut chars) { |
| 439 | + Component::None => break, |
| 440 | + Component::Text => continue, |
| 441 | + Component::Macro(s, e, m) => (s, e, m), |
| 442 | + }; |
| 443 | + let mut src = line.chars(); |
| 444 | + let mut processed = String::new(); |
| 445 | + for _ in 0..start { |
| 446 | + processed.push(src.next().unwrap()); |
| 447 | + } |
| 448 | + processed.push_str(a_macro.process()?.as_str()); |
| 449 | + for _ in start..end { |
| 450 | + let _ = src.next().unwrap(); |
| 451 | + } |
| 452 | + let mut pos = end; |
| 453 | + |
| 454 | + loop { |
| 455 | + let (start, end, a_macro) = match get_next_line_component(&mut chars) { |
| 456 | + Component::None => break, |
| 457 | + Component::Text => continue, |
| 458 | + Component::Macro(s, e, m) => (s, e, m), |
| 459 | + }; |
| 460 | + for _ in pos..start { |
| 461 | + processed.push(src.next().unwrap()); |
| 462 | + } |
| 463 | + processed.push_str(a_macro.process()?.as_str()); |
| 464 | + for _ in start..end { |
| 465 | + let _ = src.next().unwrap(); |
| 466 | + } |
| 467 | + pos = end; |
| 468 | + } |
| 469 | + for ch in src { |
| 470 | + processed.push(ch); |
| 471 | + } |
| 472 | + return Ok(Cow::Owned(processed)); |
| 473 | + } |
| 474 | + Ok(Cow::Borrowed(line)) |
| 475 | +} |
| 476 | + |
| 477 | +fn get_next_line_component(chars: &mut std::str::CharIndices<'_>) -> Component { |
| 478 | + let (start, mut macro_name) = match chars.next() { |
| 479 | + None => return Component::None, |
| 480 | + Some((_, ch)) if ch == ' ' || !ch.is_ascii() => return Component::Text, |
| 481 | + Some((pos, ch)) => (pos, String::from(ch)), |
| 482 | + }; |
| 483 | + loop { |
| 484 | + match chars.next() { |
| 485 | + None => return Component::None, |
| 486 | + Some((_, ch)) if ch == ' ' || !ch.is_ascii() => return Component::Text, |
| 487 | + Some((_, ':')) => break, |
| 488 | + Some((_, ch)) => macro_name.push(ch), |
| 489 | + } |
| 490 | + } |
| 491 | + |
| 492 | + let mut macro_target = String::new(); |
| 493 | + loop { |
| 494 | + match chars.next() { |
| 495 | + None => return Component::None, |
| 496 | + Some((_, ' ')) => return Component::Text, |
| 497 | + Some((_, '[')) => break, |
| 498 | + Some((_, ch)) => macro_target.push(ch), |
| 499 | + } |
| 500 | + } |
| 501 | + |
| 502 | + let mut attr_value = String::new(); |
| 503 | + let end = loop { |
| 504 | + match chars.next() { |
| 505 | + None => return Component::None, |
| 506 | + Some((pos, ']')) => break pos + 1, |
| 507 | + Some((_, ch)) => attr_value.push(ch), |
| 508 | + } |
| 509 | + }; |
| 510 | + |
| 511 | + Component::Macro(start, end, Macro::new(macro_name, macro_target, attr_value)) |
| 512 | +} |
| 513 | + |
| 514 | +enum Component { |
| 515 | + None, |
| 516 | + Text, |
| 517 | + Macro(usize, usize, Macro), |
| 518 | +} |
| 519 | + |
| 520 | +struct Macro { |
| 521 | + name: String, |
| 522 | + target: String, |
| 523 | + attrs: String, |
| 524 | +} |
| 525 | + |
| 526 | +impl Macro { |
| 527 | + fn new(name: String, target: String, attrs: String) -> Self { |
| 528 | + Self { name, target, attrs } |
| 529 | + } |
| 530 | + |
| 531 | + fn process(&self) -> anyhow::Result<String> { |
| 532 | + let name = &self.name; |
| 533 | + let text = match name.as_str() { |
| 534 | + "https" => { |
| 535 | + let url = &self.target; |
| 536 | + let anchor_text = &self.attrs; |
| 537 | + format!("[{anchor_text}](https:{url})") |
| 538 | + } |
| 539 | + "image" => { |
| 540 | + let url = &self.target; |
| 541 | + let alt = &self.attrs; |
| 542 | + format!("") |
| 543 | + } |
| 544 | + "kbd" => { |
| 545 | + let keys = self.attrs.split('+').map(|k| Cow::Owned(format!("<kbd>{k}</kbd>"))); |
| 546 | + keys.collect::<Vec<_>>().join("+") |
| 547 | + } |
| 548 | + "pr" => { |
| 549 | + let pr = &self.target; |
| 550 | + let url = format!("https://github.com/rust-analyzer/rust-analyzer/pull/{pr}"); |
| 551 | + format!("[`#{pr}`]({url})") |
| 552 | + } |
| 553 | + "commit" => { |
| 554 | + let hash = &self.target; |
| 555 | + let short = &hash[0..7]; |
| 556 | + let url = format!("https://github.com/rust-analyzer/rust-analyzer/commit/{hash}"); |
| 557 | + format!("[`{short}`]({url})") |
| 558 | + } |
| 559 | + "release" => { |
| 560 | + let date = &self.target; |
| 561 | + let url = format!("https://github.com/rust-analyzer/rust-analyzer/releases/{date}"); |
| 562 | + format!("[`{date}`]({url})") |
| 563 | + } |
| 564 | + _ => bail!("macro not supported: {name}"), |
| 565 | + }; |
| 566 | + Ok(text) |
| 567 | + } |
| 568 | +} |
| 569 | + |
434 | 570 | #[cfg(test)]
|
435 | 571 | mod tests {
|
436 | 572 | use super::*;
|
@@ -612,4 +748,50 @@ This is a plain listing.
|
612 | 748 |
|
613 | 749 | assert_eq!(actual, expected);
|
614 | 750 | }
|
| 751 | + |
| 752 | + macro_rules! test_inline_macro_processing { |
| 753 | + ($(( |
| 754 | + $name:ident, |
| 755 | + $input:expr, |
| 756 | + $expected:expr |
| 757 | + ),)*) => ($( |
| 758 | + #[test] |
| 759 | + fn $name() { |
| 760 | + let input = $input; |
| 761 | + let actual = process_inline_macros(&input).unwrap(); |
| 762 | + let expected = $expected; |
| 763 | + assert_eq!(actual, expected) |
| 764 | + } |
| 765 | + )*); |
| 766 | + } |
| 767 | + |
| 768 | + test_inline_macro_processing! { |
| 769 | + (inline_macro_processing_for_empty_line, "", ""), |
| 770 | + (inline_macro_processing_for_line_with_no_macro, "foo bar", "foo bar"), |
| 771 | + ( |
| 772 | + inline_macro_processing_for_macro_in_line_start, |
| 773 | + "kbd::[Ctrl+T] foo", |
| 774 | + "<kbd>Ctrl</kbd>+<kbd>T</kbd> foo" |
| 775 | + ), |
| 776 | + ( |
| 777 | + inline_macro_processing_for_macro_in_line_end, |
| 778 | + "foo kbd::[Ctrl+T]", |
| 779 | + "foo <kbd>Ctrl</kbd>+<kbd>T</kbd>" |
| 780 | + ), |
| 781 | + ( |
| 782 | + inline_macro_processing_for_macro_in_the_middle_of_line, |
| 783 | + "foo kbd::[Ctrl+T] foo", |
| 784 | + "foo <kbd>Ctrl</kbd>+<kbd>T</kbd> foo" |
| 785 | + ), |
| 786 | + ( |
| 787 | + inline_macro_processing_for_several_macros, |
| 788 | + "foo kbd::[Ctrl+T] foo kbd::[Enter] foo", |
| 789 | + "foo <kbd>Ctrl</kbd>+<kbd>T</kbd> foo <kbd>Enter</kbd> foo" |
| 790 | + ), |
| 791 | + ( |
| 792 | + inline_macro_processing_for_several_macros_without_text_in_between, |
| 793 | + "foo kbd::[Ctrl+T]kbd::[Enter] foo", |
| 794 | + "foo <kbd>Ctrl</kbd>+<kbd>T</kbd><kbd>Enter</kbd> foo" |
| 795 | + ), |
| 796 | + } |
615 | 797 | }
|
0 commit comments