kdl/
identifier.rs

1#[cfg(feature = "span")]
2use miette::SourceSpan;
3use std::{fmt::Display, str::FromStr};
4
5use crate::{v2_parser, KdlError, KdlValue};
6
7/// Represents a KDL
8/// [Identifier](https://github.com/kdl-org/kdl/blob/main/SPEC.md#identifier).
9#[derive(Debug, Clone, Eq)]
10pub struct KdlIdentifier {
11    pub(crate) value: String,
12    pub(crate) repr: Option<String>,
13    #[cfg(feature = "span")]
14    pub(crate) span: SourceSpan,
15}
16
17impl PartialEq for KdlIdentifier {
18    fn eq(&self, other: &Self) -> bool {
19        self.value == other.value && self.repr == other.repr
20        // intentionally omitted: self.span == other.span
21    }
22}
23
24impl std::hash::Hash for KdlIdentifier {
25    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
26        self.value.hash(state);
27        self.repr.hash(state);
28        // Intentionally omitted: self.span.hash(state);
29    }
30}
31
32impl KdlIdentifier {
33    /// Gets the string value for this identifier.
34    pub fn value(&self) -> &str {
35        &self.value
36    }
37
38    /// Sets the string value for this identifier.
39    pub fn set_value(&mut self, value: impl Into<String>) {
40        self.value = value.into();
41    }
42
43    /// Gets this identifier's span.
44    ///
45    /// This value will be properly initialized when created via [`crate::KdlDocument::parse`]
46    /// but may become invalidated if the document is mutated. We do not currently
47    /// guarantee this to yield any particularly consistent results at that point.
48    #[cfg(feature = "span")]
49    pub fn span(&self) -> SourceSpan {
50        self.span
51    }
52
53    /// Sets this identifier's span.
54    #[cfg(feature = "span")]
55    pub fn set_span(&mut self, span: impl Into<SourceSpan>) {
56        self.span = span.into();
57    }
58
59    /// Gets the custom string representation for this identifier, if any.
60    pub fn repr(&self) -> Option<&str> {
61        self.repr.as_deref()
62    }
63
64    /// Sets a custom string representation for this identifier.
65    pub fn set_repr(&mut self, repr: impl Into<String>) {
66        self.repr = Some(repr.into());
67    }
68
69    /// Length of this identifier when rendered as a string.
70    pub fn len(&self) -> usize {
71        format!("{self}").len()
72    }
73
74    /// Returns true if this identifier is completely empty.
75    pub fn is_empty(&self) -> bool {
76        self.len() == 0
77    }
78
79    /// Resets this identifier to its default representation. It will attempt
80    /// to make it an unquoted identifier, and fall back to a string
81    /// representation if that would be invalid.
82    pub fn clear_format(&mut self) {
83        self.repr = None;
84    }
85
86    /// Auto-formats this identifier.
87    pub fn autoformat(&mut self) {
88        self.repr = None;
89    }
90
91    /// Parses a string into a entry.
92    ///
93    /// If the `v1-fallback` feature is enabled, this method will first try to
94    /// parse the string as a KDL v2 entry, and, if that fails, it will try
95    /// to parse again as a KDL v1 entry. If both fail, only the v2 parse
96    /// errors will be returned.
97    pub fn parse(s: &str) -> Result<Self, KdlError> {
98        #[cfg(not(feature = "v1-fallback"))]
99        {
100            v2_parser::try_parse(v2_parser::identifier, s)
101        }
102        #[cfg(feature = "v1-fallback")]
103        {
104            v2_parser::try_parse(v2_parser::identifier, s)
105                .or_else(|e| KdlIdentifier::parse_v1(s).map_err(|_| e))
106        }
107    }
108
109    /// Parses a KDL v1 string into an entry.
110    #[cfg(feature = "v1")]
111    pub fn parse_v1(s: &str) -> Result<Self, KdlError> {
112        let ret: Result<kdlv1::KdlIdentifier, kdlv1::KdlError> = s.parse();
113        ret.map(|x| x.into()).map_err(|e| e.into())
114    }
115}
116
117#[cfg(feature = "v1")]
118impl From<kdlv1::KdlIdentifier> for KdlIdentifier {
119    fn from(value: kdlv1::KdlIdentifier) -> Self {
120        Self {
121            value: value.value().into(),
122            repr: value.repr().map(|x| x.into()),
123            #[cfg(feature = "span")]
124            span: (value.span().offset(), value.span().len()).into(),
125        }
126    }
127}
128
129impl Display for KdlIdentifier {
130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131        if let Some(repr) = &self.repr {
132            write!(f, "{repr}")
133        } else {
134            write!(f, "{}", KdlValue::String(self.value().into()))
135        }
136    }
137}
138
139impl From<&str> for KdlIdentifier {
140    fn from(value: &str) -> Self {
141        Self {
142            value: value.to_string(),
143            repr: None,
144            #[cfg(feature = "span")]
145            span: SourceSpan::from(0..0),
146        }
147    }
148}
149
150impl From<String> for KdlIdentifier {
151    fn from(value: String) -> Self {
152        Self {
153            value,
154            repr: None,
155            #[cfg(feature = "span")]
156            span: SourceSpan::from(0..0),
157        }
158    }
159}
160
161impl From<KdlIdentifier> for String {
162    fn from(value: KdlIdentifier) -> Self {
163        value.value
164    }
165}
166
167impl FromStr for KdlIdentifier {
168    type Err = KdlError;
169
170    fn from_str(s: &str) -> Result<Self, Self::Err> {
171        Self::parse(s)
172    }
173}
174
175#[cfg(test)]
176mod test {
177    use super::*;
178
179    #[test]
180    fn parsing() -> miette::Result<()> {
181        let plain = "foo";
182        assert_eq!(
183            plain.parse::<KdlIdentifier>()?,
184            KdlIdentifier {
185                value: plain.to_string(),
186                repr: Some(plain.to_string()),
187                #[cfg(feature = "span")]
188                span: SourceSpan::from(0..3),
189            }
190        );
191
192        let quoted = r#""foo\"bar""#;
193        assert_eq!(
194            quoted.parse::<KdlIdentifier>()?,
195            KdlIdentifier {
196                value: "foo\"bar".to_string(),
197                repr: Some(quoted.to_string()),
198                #[cfg(feature = "span")]
199                span: SourceSpan::from(0..0),
200            }
201        );
202
203        let invalid = "123";
204        assert!(invalid.parse::<KdlIdentifier>().is_err());
205
206        let invalid = "   space   ";
207        assert!(invalid.parse::<KdlIdentifier>().is_err());
208
209        let invalid = "\"x";
210        assert!(invalid.parse::<KdlIdentifier>().is_err());
211
212        Ok(())
213    }
214
215    #[test]
216    fn formatting() {
217        let plain = KdlIdentifier::from("foo");
218        assert_eq!(format!("{plain}"), "foo");
219
220        let quoted = KdlIdentifier::from("foo\"bar");
221        assert_eq!(format!("{quoted}"), r#""foo\"bar""#);
222
223        let mut custom_repr = KdlIdentifier::from("foo");
224        custom_repr.set_repr(r#""foo/bar""#.to_string());
225        assert_eq!(format!("{custom_repr}"), r#""foo/bar""#);
226    }
227}