1use std::fmt::Display;
18
19#[derive(Clone, Copy, Debug)]
20#[repr(u8)]
21pub enum TitleWhitespace {
22 Spaces,
23 Underscores,
24}
25
26impl TitleWhitespace {
27 pub(crate) const fn char(self) -> char {
28 match self {
29 TitleWhitespace::Spaces => ' ',
30 TitleWhitespace::Underscores => '_',
31 }
32 }
33
34 pub(crate) const fn other_char(self) -> char {
35 match self {
36 TitleWhitespace::Spaces => '_',
37 TitleWhitespace::Underscores => ' ',
38 }
39 }
40}
41
42struct WhitespaceDisplayer<'a>(pub(crate) &'a str, pub(crate) TitleWhitespace);
45
46impl Display for WhitespaceDisplayer<'_> {
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 let Self(s, whitespace) = self;
49 use std::fmt::Write as _;
50 let mut last_pos = None;
53 for (pos, _) in s.match_indices(whitespace.other_char()) {
54 f.write_str(&s[last_pos.replace(pos + 1).unwrap_or(0)..pos])?;
55 f.write_char(whitespace.char())?;
56 }
57 if last_pos
58 .map(|last| last < s.len())
61 .unwrap_or(true)
63 {
64 f.write_str(&s[last_pos.unwrap_or(0)..s.len()])?;
65 }
66 Ok(())
67 }
68}
69
70#[test]
71fn whitespace_displayer_displays_spaces_as_underscores() {
72 for (input, expected) in [
73 (" ", "_"),
74 (" ", "___"),
75 ("a b", "a_b"),
76 (" a", "_a"),
77 ("a ", "a_"),
78 (" a b ", "_a_b_"),
79 ("a b", "a__b"),
80 (" ab", "__ab"),
81 ("ab ", "ab__"),
82 (" a b", "__a__b"),
83 (" a b ", "__a__b__"),
84 ] {
85 for input in [input, &input.replace(' ', "_")] {
86 assert_eq!(
87 &WhitespaceDisplayer(input, TitleWhitespace::Underscores)
88 .to_string(),
89 expected,
90 "\n{input:?}"
91 );
92 }
93 }
94}
95
96pub(crate) struct TitleDisplay<'a> {
97 pub(crate) interwiki: Option<&'a str>,
98 pub(crate) namespace: Option<&'a str>,
99 pub(crate) dbkey: &'a str,
100 pub(crate) fragment: Option<&'a str>,
101 pub(crate) whitespace: TitleWhitespace,
102}
103
104impl Display for TitleDisplay<'_> {
105 fn fmt(
106 &self,
107 f: &mut std::fmt::Formatter<'_>,
108 ) -> std::result::Result<(), std::fmt::Error> {
109 let whitespace_displayer = |s| WhitespaceDisplayer(s, self.whitespace);
110 if let Some(interwiki) = self.interwiki {
111 whitespace_displayer(interwiki).fmt(f)?;
112 f.write_str(":")?;
113 }
114 if let Some(namespace) = self.namespace {
115 whitespace_displayer(namespace).fmt(f)?;
116 f.write_str(":")?;
117 }
118 whitespace_displayer(self.dbkey).fmt(f)?;
119 if let Some(fragment) = self.fragment {
120 f.write_str("#")?;
121 fragment.fmt(f)?;
122 }
123 Ok(())
124 }
125}