Skip to main content

mant_loader/
manual.rs

1//! Shared native-manual selector semantics.
2
3use mant_ir::is_manual_section;
4
5/// Return whether a manual section belongs to a command-page family.
6///
7/// Sections `1` and `8`, including conventional extensions such as `1p`, are
8/// eligible for a tldr command quick reference. Other manual categories
9/// describe APIs, formats, devices, games, or miscellaneous concepts.
10#[must_use]
11pub fn is_command_manual_section(value: &str) -> bool {
12    if !is_manual_section(value) || !matches!(value.as_bytes().first(), Some(b'1' | b'8')) {
13        return false;
14    }
15    let suffix = &value[1..];
16    suffix.is_empty()
17        || suffix
18            .chars()
19            .next()
20            .is_some_and(|character| character.is_ascii_alphabetic())
21}
22
23/// Split the `name(section)` spelling accepted by manual readers.
24#[must_use]
25pub fn parenthesized_manual_reference(selector: &str) -> Option<(&str, &str)> {
26    if selector.contains(['/', '\\']) || !selector.ends_with(')') {
27        return None;
28    }
29    let opening = selector.rfind('(')?;
30    let name = &selector[..opening];
31    let section = &selector[opening + 1..selector.len() - 1];
32    let valid_name = !name.is_empty()
33        && name.chars().all(|character| {
34            !character.is_whitespace() && !character.is_control() && !matches!(character, '(' | ')')
35        });
36    (valid_name && is_manual_section(section)).then_some((name, section))
37}
38
39#[cfg(test)]
40mod tests {
41    use super::{is_command_manual_section, is_manual_section, parenthesized_manual_reference};
42
43    #[test]
44    fn recognizes_conventional_sections_and_command_families() {
45        for section in ["0", "1", "1p", "3type", "8x", "l", "n"] {
46            assert!(is_manual_section(section), "{section}");
47        }
48        for section in ["1", "1p", "8", "8x"] {
49            assert!(is_command_manual_section(section), "{section}");
50        }
51        for section in ["", "qgroup", "17!", "3-type", "ll"] {
52            assert!(!is_manual_section(section), "{section}");
53        }
54        for section in ["0", "10", "17", "3", "5", "l", "n"] {
55            assert!(!is_command_manual_section(section), "{section}");
56        }
57    }
58
59    #[test]
60    fn splits_parenthesized_manual_references_without_paths() {
61        assert_eq!(
62            parenthesized_manual_reference("systemd.slice(5)"),
63            Some(("systemd.slice", "5"))
64        );
65        assert_eq!(parenthesized_manual_reference("manual/1/git"), None);
66        assert_eq!(parenthesized_manual_reference("two words(1)"), None);
67        assert_eq!(parenthesized_manual_reference("function(arg)(3)"), None);
68    }
69}