repomake_dsl/
lib.rs

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}