1use std::borrow::Borrow;
2
3use derive_more::Display;
4use lazy_static::lazy_static;
5use regex::Regex;
6use smol_str::SmolStr;
7use thiserror::Error;
8
9pub static PATH_COMPONENT_REGEX_STR: &str = r"[\w--\d]\w*";
10lazy_static! {
11 pub static ref PATH_REGEX: Regex =
12 Regex::new(&format!(r"^{0}(\.{0})*$", PATH_COMPONENT_REGEX_STR)).unwrap();
13}
14
15#[derive(
16 Clone,
17 Debug,
18 Display,
19 PartialEq,
20 Eq,
21 Hash,
22 PartialOrd,
23 Ord,
24 serde::Serialize,
25 serde::Deserialize,
26)]
27
28pub struct IdentList(SmolStr);
30
31impl IdentList {
32 pub fn new(n: impl Into<SmolStr>) -> Result<Self, InvalidIdentifier> {
34 let n = n.into();
35 if PATH_REGEX.is_match(n.as_str()) {
36 Ok(IdentList(n))
37 } else {
38 Err(InvalidIdentifier(n))
39 }
40 }
41
42 pub fn split_last(&self) -> Option<(IdentList, SmolStr)> {
58 let (prefix, suffix) = self.0.rsplit_once('.')?;
59 let prefix = Self::new_unchecked(prefix);
60 let suffix = suffix.into();
61 Some((prefix, suffix))
62 }
63
64 pub const fn new_unchecked(n: &str) -> Self {
72 IdentList(SmolStr::new_inline(n))
73 }
74
75 pub const fn new_static_unchecked(n: &'static str) -> Self {
79 IdentList(SmolStr::new_static(n))
80 }
81}
82
83impl Borrow<str> for IdentList {
84 fn borrow(&self) -> &str {
85 self.0.borrow()
86 }
87}
88
89impl std::ops::Deref for IdentList {
90 type Target = str;
91
92 fn deref(&self) -> &str {
93 self.0.deref()
94 }
95}
96
97impl TryInto<IdentList> for &str {
98 type Error = InvalidIdentifier;
99
100 fn try_into(self) -> Result<IdentList, InvalidIdentifier> {
101 IdentList::new(SmolStr::new(self))
102 }
103}
104
105#[derive(Clone, Debug, PartialEq, Eq, Error)]
106#[error("Invalid identifier {0}")]
107pub struct InvalidIdentifier(SmolStr);
109
110#[cfg(test)]
111mod test {
112
113 mod proptest {
114 use crate::hugr::ident::IdentList;
115 use ::proptest::prelude::*;
116 impl Arbitrary for super::IdentList {
117 type Parameters = ();
118 type Strategy = BoxedStrategy<Self>;
119 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
120 use crate::proptest::any_ident_string;
121 use proptest::collection::vec;
122 vec(any_ident_string(), 1..2)
123 .prop_map(|vs| {
124 IdentList::new(itertools::intersperse(vs, ".".into()).collect::<String>())
125 .unwrap()
126 })
127 .boxed()
128 }
129 }
130 proptest! {
131 #[test]
132 fn arbitrary_identlist_valid((IdentList(ident_list)): IdentList) {
133 assert!(IdentList::new(ident_list).is_ok())
134 }
135 }
136 }
137
138 use super::IdentList;
139
140 #[test]
141 fn test_idents() {
142 IdentList::new("foo").unwrap();
143 IdentList::new("_foo").unwrap();
144 IdentList::new("Bar_xyz67").unwrap();
145 IdentList::new("foo.bar").unwrap();
146 IdentList::new("foo.bar.baz").unwrap();
147
148 IdentList::new("42").unwrap_err();
149 IdentList::new("foo.42").unwrap_err();
150 IdentList::new("xyz-5").unwrap_err();
151 IdentList::new("foo..bar").unwrap_err();
152 IdentList::new(".foo").unwrap_err();
153 }
154}