cargo-parcel 0.0.4

Experimental extended cargo installer
Documentation
use std::{
    convert::TryFrom,
    fmt,
    path::{Path, PathBuf},
    str::FromStr,
};

/// Represents a manual page to be installed.
#[derive(Debug, Clone)]
pub struct ManSource {
    section: u8,
    target_file_name: String,
    path: PathBuf,
    format: ManSourceFormat,
}

impl ManSource {
    /// The man page section.
    pub fn section(&self) -> u8 {
        self.section
    }

    /// Returns the target file name of the man page.
    ///
    /// The returned path includes the suffix such as ".1" or ".3pm", and
    /// indicates the file name the man page should be installed as.
    pub fn target_file_name(&self) -> &str {
        &self.target_file_name
    }

    /// Returns the path to the source file of the man page.
    pub fn path(&self) -> &Path {
        &self.path
    }

    /// Returns the file format of the source file.
    pub fn format(&self) -> ManSourceFormat {
        self.format
    }
}

/// The format of the source file for a man page.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ManSourceFormat {
    /// The man page source is written in Markdown, and needs to be rendered
    /// before installation.
    Markdown,
    /// The man page source is to be installed verbatim.
    Verbatim,
}

impl FromStr for ManSource {
    type Err = ParseManSourceError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let path = Path::new(s);
        let (stem, ext) = match (path.file_stem(), path.extension()) {
            // The unwrap calls should never panic as the input is valid UTF-8.
            (Some(stem), Some(ext)) => (stem.to_str().unwrap(), ext.to_str().unwrap()),
            _ => return Err(ParseManSourceError::missing_extension()),
        };
        let (format, target_name, ext) = match ext {
            "md" => {
                if let Some(dot) = stem.rfind('.') {
                    (ManSourceFormat::Markdown, stem, &stem[dot + 1..])
                } else {
                    return Err(ParseManSourceError::missing_section());
                }
            }
            _ => {
                // The unwrap calls should never panic as we obtained the stem
                // and extension before, so there should definitively be a UTF-8
                // file name present.
                let target_name = path.file_name().unwrap().to_str().unwrap();
                (ManSourceFormat::Verbatim, target_name, ext)
            }
        };
        let section = if let Some(pos) = ext.as_bytes().iter().position(|c| !c.is_ascii_digit()) {
            &ext[..pos]
        } else {
            ext
        };
        let section = section
            .parse()
            .map_err(|_| ParseManSourceError::invalid_extension())?;
        Ok(ManSource {
            target_file_name: target_name.into(),
            section,
            path: path.to_path_buf(),
            format,
        })
    }
}

impl TryFrom<&str> for ManSource {
    type Error = ParseManSourceError;

    fn try_from(s: &str) -> Result<Self, Self::Error> {
        s.parse()
    }
}

/// Error stemming from parsing a
#[derive(Debug, Clone)]
pub struct ParseManSourceError(ParseManSourceErrorInner);

impl ParseManSourceError {
    fn missing_extension() -> Self {
        ParseManSourceError(ParseManSourceErrorInner::MissingExtension)
    }
    fn missing_section() -> Self {
        ParseManSourceError(ParseManSourceErrorInner::MissingSection)
    }
    fn invalid_extension() -> Self {
        ParseManSourceError(ParseManSourceErrorInner::InvalidExtension)
    }
}

#[derive(Debug, Clone)]
enum ParseManSourceErrorInner {
    MissingExtension,
    MissingSection,
    InvalidExtension,
}

impl fmt::Display for ParseManSourceError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use ParseManSourceErrorInner::*;
        match self.0 {
            MissingExtension => write!(f, "missing extension"),
            MissingSection => write!(f, "missing section"),
            InvalidExtension => write!(f, "invalid extension"),
        }
    }
}

impl std::error::Error for ParseManSourceError {}