mentions.rs 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. /// This provides a list of usernames or teams that were pinged in the text
  2. /// provided.
  3. ///
  4. /// It will appropriately skip mentions just like GitHub, i.e., mentions inside
  5. /// code blocks will be ignored.
  6. ///
  7. /// Note that the `@` is skipped in the final output.
  8. pub fn get_mentions(input: &str) -> Vec<&str> {
  9. let ignore_regions = crate::ignore_block::IgnoreBlocks::new(input);
  10. let mut mentions = Vec::new();
  11. for (idx, _) in input.match_indices('@') {
  12. if let Some(previous) = input[..idx].chars().next_back() {
  13. // A github username must stand apart from other text.
  14. //
  15. // Oddly enough, english letters do not work, but letters outside
  16. // ASCII do work as separators; for now just go with this limited
  17. // list.
  18. if let 'a'..='z' | 'A'..='Z' = previous {
  19. continue;
  20. }
  21. }
  22. let mut saw_slash = false;
  23. let username_end = input
  24. .get(idx + 1..)
  25. .unwrap_or_default()
  26. .char_indices()
  27. .find(|(_, terminator)| match terminator {
  28. // These are the valid characters inside of a GitHub
  29. // username
  30. 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' => false,
  31. '/' if !saw_slash => {
  32. saw_slash = true;
  33. false
  34. }
  35. _ => true,
  36. })
  37. .map(|(end, _)| idx + 1 + end)
  38. .unwrap_or(input.len());
  39. let username = input.get(idx + 1..username_end).unwrap_or_default();
  40. if username.is_empty() {
  41. continue;
  42. }
  43. if ignore_regions
  44. .overlaps_ignore(idx..idx + username.len())
  45. .is_some()
  46. {
  47. continue;
  48. }
  49. mentions.push(username);
  50. }
  51. mentions
  52. }
  53. #[test]
  54. fn mentions_in_code_ignored() {
  55. assert_eq!(
  56. get_mentions("@rust-lang/libs `@user`"),
  57. vec!["rust-lang/libs"]
  58. );
  59. assert_eq!(get_mentions("@user `@user`"), vec!["user"]);
  60. assert_eq!(get_mentions("`@user`"), Vec::<&str>::new());
  61. }
  62. #[test]
  63. fn italics() {
  64. assert_eq!(get_mentions("*@rust-lang/libs*"), vec!["rust-lang/libs"]);
  65. }
  66. #[test]
  67. fn slash() {
  68. assert_eq!(
  69. get_mentions("@rust-lang/libs/@rust-lang/release"),
  70. vec!["rust-lang/libs", "rust-lang/release"]
  71. );
  72. }
  73. #[test]
  74. fn no_panic_lone() {
  75. assert_eq!(get_mentions("@ `@`"), Vec::<&str>::new());
  76. }
  77. #[test]
  78. fn no_email() {
  79. assert_eq!(get_mentions("user@example.com"), Vec::<&str>::new());
  80. }