Skip to main content

use_pnpm/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7macro_rules! text_newtype {
8    ($name:ident) => {
9        #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
10        pub struct $name(String);
11
12        impl $name {
13            /// Creates non-empty pnpm text metadata.
14            ///
15            /// # Errors
16            ///
17            /// Returns [`PnpmTextError::Empty`] when `input` is empty after trimming.
18            pub fn new(input: &str) -> Result<Self, PnpmTextError> {
19                let trimmed = input.trim();
20                if trimmed.is_empty() {
21                    Err(PnpmTextError::Empty)
22                } else {
23                    Ok(Self(trimmed.to_string()))
24                }
25            }
26
27            /// Returns the stored text.
28            #[must_use]
29            pub fn as_str(&self) -> &str {
30                &self.0
31            }
32        }
33    };
34}
35
36/// Common pnpm command labels.
37#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
38pub enum PnpmCommand {
39    Install,
40    Add,
41    Run,
42    Test,
43    Exec,
44    Publish,
45}
46
47impl FromStr for PnpmCommand {
48    type Err = PnpmTextError;
49
50    fn from_str(input: &str) -> Result<Self, Self::Err> {
51        match normalized(input)?.as_str() {
52            "install" | "i" => Ok(Self::Install),
53            "add" => Ok(Self::Add),
54            "run" => Ok(Self::Run),
55            "test" => Ok(Self::Test),
56            "exec" => Ok(Self::Exec),
57            "publish" => Ok(Self::Publish),
58            _ => Err(PnpmTextError::Unknown),
59        }
60    }
61}
62
63text_newtype!(PnpmWorkspace);
64text_newtype!(PnpmFilter);
65
66/// Common pnpm lockfile labels.
67#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
68pub enum PnpmLockfile {
69    Workspace,
70}
71
72impl PnpmLockfile {
73    /// Returns the lockfile label.
74    #[must_use]
75    pub const fn as_str(self) -> &'static str {
76        "pnpm-lock.yaml"
77    }
78}
79
80/// Error returned when pnpm text metadata is invalid.
81#[derive(Clone, Copy, Debug, Eq, PartialEq)]
82pub enum PnpmTextError {
83    Empty,
84    Unknown,
85}
86
87impl fmt::Display for PnpmTextError {
88    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
89        match self {
90            Self::Empty => formatter.write_str("pnpm metadata text cannot be empty"),
91            Self::Unknown => formatter.write_str("unknown pnpm command"),
92        }
93    }
94}
95
96impl Error for PnpmTextError {}
97
98fn normalized(input: &str) -> Result<String, PnpmTextError> {
99    let trimmed = input.trim();
100    if trimmed.is_empty() {
101        Err(PnpmTextError::Empty)
102    } else {
103        Ok(trimmed.to_ascii_lowercase())
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::{PnpmCommand, PnpmFilter, PnpmLockfile};
110
111    #[test]
112    fn models_pnpm_metadata() -> Result<(), Box<dyn std::error::Error>> {
113        assert_eq!("install".parse::<PnpmCommand>()?, PnpmCommand::Install);
114        assert_eq!(
115            PnpmFilter::new("./packages/app")?.as_str(),
116            "./packages/app"
117        );
118        assert_eq!(PnpmLockfile::Workspace.as_str(), "pnpm-lock.yaml");
119        Ok(())
120    }
121}