Skip to main content

pinto/
template.rs

1//! Plain text template to apply during creation.
2
3use crate::error::{Error, Result};
4use std::fmt;
5use std::str::FromStr;
6
7/// Safe name for the template (file name without extension).
8#[derive(Debug, Clone, PartialEq, Eq, Hash)]
9pub struct TemplateName(String);
10
11impl TemplateName {
12    /// Create a name consisting only of ASCII alphanumeric characters, `-`, and `_`.
13    pub fn new(value: impl Into<String>) -> Result<Self> {
14        let value = value.into();
15        if value.is_empty()
16            || !value
17                .bytes()
18                .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_'))
19        {
20            return Err(Error::InvalidTemplateName(value));
21        }
22        Ok(Self(value))
23    }
24
25    /// Template name without extension.
26    #[must_use]
27    pub fn as_str(&self) -> &str {
28        &self.0
29    }
30}
31
32impl fmt::Display for TemplateName {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        f.write_str(&self.0)
35    }
36}
37
38impl FromStr for TemplateName {
39    type Err = Error;
40
41    fn from_str(value: &str) -> Result<Self> {
42        Self::new(value)
43    }
44}
45
46/// The creation target to which the template is applied.
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum TemplateKind {
49    /// The body of the PBI.
50    Item,
51    /// Sprint goal body.
52    Sprint,
53}
54
55impl TemplateKind {
56    /// Directory name under `.pinto/templates/`.
57    #[must_use]
58    pub fn as_str(self) -> &'static str {
59        match self {
60            Self::Item => "item",
61            Self::Sprint => "sprint",
62        }
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69    use crate::error::Error;
70
71    #[test]
72    fn template_name_accepts_safe_file_stems_and_rejects_paths() {
73        assert_eq!(TemplateName::new("story-1").unwrap().as_str(), "story-1");
74        for invalid in ["", "../secret", "feature/one", "with space", "日本語"] {
75            assert_eq!(
76                TemplateName::new(invalid),
77                Err(Error::InvalidTemplateName(invalid.to_string()))
78            );
79        }
80    }
81}