1#![allow(dead_code)]
2
3use anyhow::{Context, Error, Result, anyhow, bail};
4use fakemap::FakeMap as Map;
5use serde::{Deserialize, Serialize};
6
7#[cfg(test)]
8mod tests;
9
10#[derive(Clone, Debug, Deserialize, Serialize)]
11pub struct Package {
12 pub public: Map<String, Label>,
13 pub private: Map<String, Label>,
14}
15
16#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
17#[serde(try_from = "&str")]
18pub struct Label {
19 segments: Vec<Segment>,
20 root: Root,
21}
22
23impl<'a> TryFrom<&'a str> for Label {
24 type Error = Error;
25
26 fn try_from(mut s: &'a str) -> Result<Self> {
27 let orig = s;
28
29 let root = if s.starts_with("@") {
30 if let Some(pos) = s.find("//") {
31 let name = s[1..pos].to_owned();
32 s = &s[pos..];
33
34 Root::Other(name)
35 } else {
36 bail!("could not find `//` in label {}", orig);
37 }
38 } else if s.starts_with("//") {
39 s = &s[2..];
40
41 Root::Local
42 } else {
43 Root::Relative
44 };
45
46 let mut segments = vec![];
47 loop {
48 dbg!(s);
49 let segment = Segment::try_from(s)?;
50 s = &s[segment.needed_len()..];
51 segments.push(segment);
52 if s.is_empty() {
53 break;
54 }
55 }
56
57 Ok(Label {
58 root,
59 segments,
60 })
61 }
62}
63
64#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
65pub enum Segment {
66 Empty,
67 Dir(String),
68 File(String),
69 Rule(String),
70}
71
72impl Segment {
73 fn dir(s: &str) -> Self {
74 Segment::Dir(s.to_owned())
75 }
76
77 fn file(s: &str) -> Self {
78 Segment::File(s.to_owned())
79 }
80
81 fn rule(s: &str) -> Self {
82 Segment::Rule(s.to_owned())
83 }
84
85 fn needed_len(&self) -> usize {
86 match self {
87 Segment::Empty => 0,
88 Segment::Dir(s) | Segment::Rule(s) => s.len() + 1,
89 Segment::File(s) => s.len(),
90 }
91 }
92
93 fn take_until_sep(s: &str) -> Result<String> {
94 let mut end = 0;
95
96 for (byte_pos, c) in s.char_indices() {
97 if c.is_control() {
98 bail!("control characters not allowed in labels");
99 } else if c == '\\' {
100 bail!("backslashes not allowed in labels; use forward slashes (/) instead");
101 } else if c == '/' || c == ':' {
102 end = byte_pos;
103 break;
104 } else {
105 end = byte_pos + 1;
106 }
107 };
108
109 let s = &s[..end];
110
111 if s.chars().next().map(|c| c.is_whitespace()).unwrap_or(false) {
112 bail!("backslashes not allowed in labels; use forward slashes (/) instead");
113 }
114
115 Ok(s.to_owned())
116 }
117}
118
119impl TryFrom<&'_ str> for Segment {
120 type Error = Error;
121
122 fn try_from(s: &str) -> Result<Self> {
123 let segment = if s.is_empty() || s.starts_with('/') {
124 Segment::Empty
125 } else if s.starts_with(":") {
126 let rule = Self::take_until_sep(&s[1..]).with_context(|| anyhow!("could not parse label segment {}", s))?;
127
128 if rule.is_empty() {
129 bail!("empty rule not allowed (got {})", s);
130 }
131
132 Segment::Rule(rule)
133 } else {
134 let path = Self::take_until_sep(s).with_context(|| anyhow!("could not parse label segment {}", s))?;
135
136 if s.bytes().nth(path.len()).map(|c| c == b'/').unwrap_or(false) {
137 Segment::Dir(path)
138 } else {
139 Segment::File(path)
140 }
141 };
142
143 Ok(segment)
144 }
145}
146
147#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
148pub enum Root {
149 Relative,
150 Local,
151 Other(String),
152}