1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use crate::util::try_unescape;
use crate::{Error, Identifier, Result};
use serde::Deserialize;
use std::fmt;
use std::str::FromStr;

/// A template expression embeds a program written in the template sub-language as an expression.
///
/// This type wraps the raw template string representation. Refer to the documentation of the
/// [`template`][`crate::template`] module if you need to parse and further evaluate the raw
/// template.
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
pub enum TemplateExpr {
    /// A quoted template expression is delimited by quote characters (`"`) and defines a template
    /// as a single-line expression with escape characters. The raw template string may contain
    /// escape sequences.
    QuotedString(String),
    /// A heredoc template expression is introduced by a `<<` sequence and defines a template via a
    /// multi-line sequence terminated by a user-chosen delimiter. The raw template string in the
    /// heredoc may contain escape sequences.
    Heredoc(Heredoc),
}

impl TemplateExpr {
    /// Returns the template as a `&str`.
    pub(crate) fn as_str(&self) -> &str {
        match self {
            TemplateExpr::QuotedString(s) => s,
            TemplateExpr::Heredoc(heredoc) => &heredoc.template,
        }
    }
}

impl From<&str> for TemplateExpr {
    fn from(s: &str) -> Self {
        TemplateExpr::QuotedString(s.to_owned())
    }
}

impl From<String> for TemplateExpr {
    fn from(string: String) -> Self {
        TemplateExpr::QuotedString(string)
    }
}

impl From<Heredoc> for TemplateExpr {
    fn from(heredoc: Heredoc) -> Self {
        TemplateExpr::Heredoc(heredoc)
    }
}

impl fmt::Display for TemplateExpr {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str(&try_unescape(self.as_str()))
    }
}

/// A heredoc template expression is introduced by a `<<` sequence and defines a template via a
/// multi-line sequence terminated by a user-chosen delimiter.
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Heredoc {
    /// The delimiter identifier that denotes the heredoc start and end.
    pub delimiter: Identifier,
    /// The raw template contained in the heredoc.
    pub template: String,
    /// The heredoc strip mode.
    pub strip: HeredocStripMode,
}

impl Heredoc {
    /// Creates a new `Heredoc` with the provided delimiter and template body.
    pub fn new<T>(delimiter: Identifier, template: T) -> Heredoc
    where
        T: Into<String>,
    {
        Heredoc {
            delimiter,
            template: template.into(),
            strip: HeredocStripMode::default(),
        }
    }

    /// Sets the heredoc strip mode to use on the template.
    pub fn with_strip_mode(mut self, strip: HeredocStripMode) -> Heredoc {
        self.strip = strip;
        self
    }
}

/// The strip behaviour for the template contained in the heredoc.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HeredocStripMode {
    /// Do not strip leading whitespace.
    None,
    /// Any literal string at the start of each line is analyzed to find the minimum number
    /// of leading spaces, and then that number of prefix spaces is removed from all line-leading
    /// literal strings. The final closing marker may also have an arbitrary number of spaces
    /// preceding it on its line.
    Indent,
}

impl HeredocStripMode {
    /// Returns the string representation of the heredoc strip mode. This is the part before the
    /// delimiter identifier.
    pub fn as_str(&self) -> &'static str {
        match self {
            HeredocStripMode::None => "<<",
            HeredocStripMode::Indent => "<<-",
        }
    }
}

impl Default for HeredocStripMode {
    fn default() -> Self {
        HeredocStripMode::None
    }
}

impl FromStr for HeredocStripMode {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self> {
        match s {
            "<<" => Ok(HeredocStripMode::None),
            "<<-" => Ok(HeredocStripMode::Indent),
            _ => Err(Error::new(format!("invalid heredoc strip mode: `{s}`"))),
        }
    }
}