cargo_parcel/
man.rs

1use std::{
2    convert::TryFrom,
3    fmt,
4    path::{Path, PathBuf},
5    str::FromStr,
6};
7
8/// Represents a manual page to be installed.
9#[derive(Debug, Clone)]
10pub struct ManSource {
11    section: u8,
12    target_file_name: String,
13    path: PathBuf,
14    format: ManSourceFormat,
15}
16
17impl ManSource {
18    /// The man page section.
19    pub fn section(&self) -> u8 {
20        self.section
21    }
22
23    /// Returns the target file name of the man page.
24    ///
25    /// The returned path includes the suffix such as ".1" or ".3pm", and
26    /// indicates the file name the man page should be installed as.
27    pub fn target_file_name(&self) -> &str {
28        &self.target_file_name
29    }
30
31    /// Returns the path to the source file of the man page.
32    pub fn path(&self) -> &Path {
33        &self.path
34    }
35
36    /// Returns the file format of the source file.
37    pub fn format(&self) -> ManSourceFormat {
38        self.format
39    }
40}
41
42/// The format of the source file for a man page.
43#[derive(Debug, Copy, Clone, Eq, PartialEq)]
44pub enum ManSourceFormat {
45    /// The man page source is written in Markdown, and needs to be rendered
46    /// before installation.
47    Markdown,
48    /// The man page source is to be installed verbatim.
49    Verbatim,
50}
51
52impl FromStr for ManSource {
53    type Err = ParseManSourceError;
54
55    fn from_str(s: &str) -> Result<Self, Self::Err> {
56        let path = Path::new(s);
57        let (stem, ext) = match (path.file_stem(), path.extension()) {
58            // The unwrap calls should never panic as the input is valid UTF-8.
59            (Some(stem), Some(ext)) => (stem.to_str().unwrap(), ext.to_str().unwrap()),
60            _ => return Err(ParseManSourceError::missing_extension()),
61        };
62        let (format, target_name, ext) = match ext {
63            "md" => {
64                if let Some(dot) = stem.rfind('.') {
65                    (ManSourceFormat::Markdown, stem, &stem[dot + 1..])
66                } else {
67                    return Err(ParseManSourceError::missing_section());
68                }
69            }
70            _ => {
71                // The unwrap calls should never panic as we obtained the stem
72                // and extension before, so there should definitively be a UTF-8
73                // file name present.
74                let target_name = path.file_name().unwrap().to_str().unwrap();
75                (ManSourceFormat::Verbatim, target_name, ext)
76            }
77        };
78        let section = if let Some(pos) = ext.as_bytes().iter().position(|c| !c.is_ascii_digit()) {
79            &ext[..pos]
80        } else {
81            ext
82        };
83        let section = section
84            .parse()
85            .map_err(|_| ParseManSourceError::invalid_extension())?;
86        Ok(ManSource {
87            target_file_name: target_name.into(),
88            section,
89            path: path.to_path_buf(),
90            format,
91        })
92    }
93}
94
95impl TryFrom<&str> for ManSource {
96    type Error = ParseManSourceError;
97
98    fn try_from(s: &str) -> Result<Self, Self::Error> {
99        s.parse()
100    }
101}
102
103/// Error stemming from parsing a
104#[derive(Debug, Clone)]
105pub struct ParseManSourceError(ParseManSourceErrorInner);
106
107impl ParseManSourceError {
108    fn missing_extension() -> Self {
109        ParseManSourceError(ParseManSourceErrorInner::MissingExtension)
110    }
111    fn missing_section() -> Self {
112        ParseManSourceError(ParseManSourceErrorInner::MissingSection)
113    }
114    fn invalid_extension() -> Self {
115        ParseManSourceError(ParseManSourceErrorInner::InvalidExtension)
116    }
117}
118
119#[derive(Debug, Clone)]
120enum ParseManSourceErrorInner {
121    MissingExtension,
122    MissingSection,
123    InvalidExtension,
124}
125
126impl fmt::Display for ParseManSourceError {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        use ParseManSourceErrorInner::*;
129        match self.0 {
130            MissingExtension => write!(f, "missing extension"),
131            MissingSection => write!(f, "missing section"),
132            InvalidExtension => write!(f, "invalid extension"),
133        }
134    }
135}
136
137impl std::error::Error for ParseManSourceError {}