1*d4726bddSHONG Yifan //! Bazel label parsing library.
2*d4726bddSHONG Yifan //!
3*d4726bddSHONG Yifan //! USAGE: `label::analyze("//foo/bar:baz")
4*d4726bddSHONG Yifan mod label_error;
5*d4726bddSHONG Yifan use label_error::LabelError;
6*d4726bddSHONG Yifan
7*d4726bddSHONG Yifan /// Parse and analyze given str.
8*d4726bddSHONG Yifan ///
9*d4726bddSHONG Yifan /// TODO: validate . and .. in target name
10*d4726bddSHONG Yifan /// TODO: validate used characters in target name
analyze(input: &'_ str) -> Result<Label<'_>>11*d4726bddSHONG Yifan pub fn analyze(input: &'_ str) -> Result<Label<'_>> {
12*d4726bddSHONG Yifan Label::analyze(input)
13*d4726bddSHONG Yifan }
14*d4726bddSHONG Yifan
15*d4726bddSHONG Yifan #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
16*d4726bddSHONG Yifan pub enum Repository<'s> {
17*d4726bddSHONG Yifan /// A `@@` prefixed name that is unique within a workspace. E.g. `@@rules_rust~0.1.2~toolchains~local_rustc`
18*d4726bddSHONG Yifan Canonical(&'s str), // stringifies to `@@self.0` where `self.0` may be empty
19*d4726bddSHONG Yifan /// A `@` (single) prefixed name. E.g. `@rules_rust`.
20*d4726bddSHONG Yifan Apparent(&'s str),
21*d4726bddSHONG Yifan }
22*d4726bddSHONG Yifan
23*d4726bddSHONG Yifan impl<'s> Repository<'s> {
repo_name(&self) -> &'s str24*d4726bddSHONG Yifan pub fn repo_name(&self) -> &'s str {
25*d4726bddSHONG Yifan match self {
26*d4726bddSHONG Yifan Repository::Canonical(name) => &name[2..],
27*d4726bddSHONG Yifan Repository::Apparent(name) => &name[1..],
28*d4726bddSHONG Yifan }
29*d4726bddSHONG Yifan }
30*d4726bddSHONG Yifan }
31*d4726bddSHONG Yifan
32*d4726bddSHONG Yifan #[derive(Debug, PartialEq, Eq)]
33*d4726bddSHONG Yifan pub enum Label<'s> {
34*d4726bddSHONG Yifan Relative {
35*d4726bddSHONG Yifan target_name: &'s str,
36*d4726bddSHONG Yifan },
37*d4726bddSHONG Yifan Absolute {
38*d4726bddSHONG Yifan repository: Option<Repository<'s>>,
39*d4726bddSHONG Yifan package_name: &'s str,
40*d4726bddSHONG Yifan target_name: &'s str,
41*d4726bddSHONG Yifan },
42*d4726bddSHONG Yifan }
43*d4726bddSHONG Yifan
44*d4726bddSHONG Yifan type Result<T, E = LabelError> = core::result::Result<T, E>;
45*d4726bddSHONG Yifan
46*d4726bddSHONG Yifan impl<'s> Label<'s> {
47*d4726bddSHONG Yifan /// Parse and analyze given str.
analyze(input: &'s str) -> Result<Label<'s>>48*d4726bddSHONG Yifan pub fn analyze(input: &'s str) -> Result<Label<'s>> {
49*d4726bddSHONG Yifan let label = input;
50*d4726bddSHONG Yifan
51*d4726bddSHONG Yifan if label.is_empty() {
52*d4726bddSHONG Yifan return Err(LabelError(err(
53*d4726bddSHONG Yifan label,
54*d4726bddSHONG Yifan "Empty string cannot be parsed into a label.",
55*d4726bddSHONG Yifan )));
56*d4726bddSHONG Yifan }
57*d4726bddSHONG Yifan
58*d4726bddSHONG Yifan if label.starts_with(':') {
59*d4726bddSHONG Yifan return match consume_name(input, label)? {
60*d4726bddSHONG Yifan None => Err(LabelError(err(
61*d4726bddSHONG Yifan label,
62*d4726bddSHONG Yifan "Relative packages must have a name.",
63*d4726bddSHONG Yifan ))),
64*d4726bddSHONG Yifan Some(name) => Ok(Label::Relative { target_name: name }),
65*d4726bddSHONG Yifan };
66*d4726bddSHONG Yifan }
67*d4726bddSHONG Yifan
68*d4726bddSHONG Yifan let (input, repository) = consume_repository_name(input, label)?;
69*d4726bddSHONG Yifan
70*d4726bddSHONG Yifan // Shorthand labels such as `@repo` are expanded to `@repo//:repo`.
71*d4726bddSHONG Yifan if input.is_empty() {
72*d4726bddSHONG Yifan if let Some(ref repo) = repository {
73*d4726bddSHONG Yifan let target_name = repo.repo_name();
74*d4726bddSHONG Yifan if target_name.is_empty() {
75*d4726bddSHONG Yifan return Err(LabelError(err(
76*d4726bddSHONG Yifan label,
77*d4726bddSHONG Yifan "invalid target name: empty target name",
78*d4726bddSHONG Yifan )));
79*d4726bddSHONG Yifan } else {
80*d4726bddSHONG Yifan return Ok(Label::Absolute {
81*d4726bddSHONG Yifan repository,
82*d4726bddSHONG Yifan package_name: "",
83*d4726bddSHONG Yifan target_name,
84*d4726bddSHONG Yifan });
85*d4726bddSHONG Yifan };
86*d4726bddSHONG Yifan }
87*d4726bddSHONG Yifan }
88*d4726bddSHONG Yifan let (input, package_name) = consume_package_name(input, label)?;
89*d4726bddSHONG Yifan let name = consume_name(input, label)?;
90*d4726bddSHONG Yifan let name = match (package_name, name) {
91*d4726bddSHONG Yifan (None, None) => {
92*d4726bddSHONG Yifan return Err(LabelError(err(
93*d4726bddSHONG Yifan label,
94*d4726bddSHONG Yifan "labels must have a package and/or a name.",
95*d4726bddSHONG Yifan )))
96*d4726bddSHONG Yifan }
97*d4726bddSHONG Yifan (Some(package_name), None) => name_from_package(package_name),
98*d4726bddSHONG Yifan (_, Some(name)) => name,
99*d4726bddSHONG Yifan };
100*d4726bddSHONG Yifan
101*d4726bddSHONG Yifan Ok(Label::Absolute {
102*d4726bddSHONG Yifan repository,
103*d4726bddSHONG Yifan package_name: package_name.unwrap_or_default(),
104*d4726bddSHONG Yifan target_name: name,
105*d4726bddSHONG Yifan })
106*d4726bddSHONG Yifan }
107*d4726bddSHONG Yifan
is_relative(&self) -> bool108*d4726bddSHONG Yifan pub fn is_relative(&self) -> bool {
109*d4726bddSHONG Yifan match self {
110*d4726bddSHONG Yifan Label::Absolute { .. } => false,
111*d4726bddSHONG Yifan Label::Relative { .. } => true,
112*d4726bddSHONG Yifan }
113*d4726bddSHONG Yifan }
114*d4726bddSHONG Yifan
repo(&self) -> Option<&Repository<'s>>115*d4726bddSHONG Yifan pub fn repo(&self) -> Option<&Repository<'s>> {
116*d4726bddSHONG Yifan match self {
117*d4726bddSHONG Yifan Label::Absolute { repository, .. } => repository.as_ref(),
118*d4726bddSHONG Yifan Label::Relative { .. } => None,
119*d4726bddSHONG Yifan }
120*d4726bddSHONG Yifan }
121*d4726bddSHONG Yifan
repo_name(&self) -> Option<&'s str>122*d4726bddSHONG Yifan pub fn repo_name(&self) -> Option<&'s str> {
123*d4726bddSHONG Yifan match self {
124*d4726bddSHONG Yifan Label::Absolute { repository, .. } => repository.as_ref().map(|repo| repo.repo_name()),
125*d4726bddSHONG Yifan Label::Relative { .. } => None,
126*d4726bddSHONG Yifan }
127*d4726bddSHONG Yifan }
128*d4726bddSHONG Yifan
package(&self) -> Option<&'s str>129*d4726bddSHONG Yifan pub fn package(&self) -> Option<&'s str> {
130*d4726bddSHONG Yifan match self {
131*d4726bddSHONG Yifan Label::Relative { .. } => None,
132*d4726bddSHONG Yifan Label::Absolute { package_name, .. } => Some(*package_name),
133*d4726bddSHONG Yifan }
134*d4726bddSHONG Yifan }
135*d4726bddSHONG Yifan
name(&self) -> &'s str136*d4726bddSHONG Yifan pub fn name(&self) -> &'s str {
137*d4726bddSHONG Yifan match self {
138*d4726bddSHONG Yifan Label::Relative { target_name } => target_name,
139*d4726bddSHONG Yifan Label::Absolute { target_name, .. } => target_name,
140*d4726bddSHONG Yifan }
141*d4726bddSHONG Yifan }
142*d4726bddSHONG Yifan }
143*d4726bddSHONG Yifan
err<'s>(label: &'s str, msg: &'s str) -> String144*d4726bddSHONG Yifan fn err<'s>(label: &'s str, msg: &'s str) -> String {
145*d4726bddSHONG Yifan let mut err_msg = label.to_string();
146*d4726bddSHONG Yifan err_msg.push_str(" must be a legal label; ");
147*d4726bddSHONG Yifan err_msg.push_str(msg);
148*d4726bddSHONG Yifan err_msg
149*d4726bddSHONG Yifan }
150*d4726bddSHONG Yifan
consume_repository_name<'s>( input: &'s str, label: &'s str, ) -> Result<(&'s str, Option<Repository<'s>>)>151*d4726bddSHONG Yifan fn consume_repository_name<'s>(
152*d4726bddSHONG Yifan input: &'s str,
153*d4726bddSHONG Yifan label: &'s str,
154*d4726bddSHONG Yifan ) -> Result<(&'s str, Option<Repository<'s>>)> {
155*d4726bddSHONG Yifan let at_signs = {
156*d4726bddSHONG Yifan let mut count = 0;
157*d4726bddSHONG Yifan for char in input.chars() {
158*d4726bddSHONG Yifan if char == '@' {
159*d4726bddSHONG Yifan count += 1;
160*d4726bddSHONG Yifan } else {
161*d4726bddSHONG Yifan break;
162*d4726bddSHONG Yifan }
163*d4726bddSHONG Yifan }
164*d4726bddSHONG Yifan count
165*d4726bddSHONG Yifan };
166*d4726bddSHONG Yifan if at_signs == 0 {
167*d4726bddSHONG Yifan return Ok((input, None));
168*d4726bddSHONG Yifan }
169*d4726bddSHONG Yifan if at_signs > 2 {
170*d4726bddSHONG Yifan return Err(LabelError(err(label, "Unexpected number of leading `@`.")));
171*d4726bddSHONG Yifan }
172*d4726bddSHONG Yifan
173*d4726bddSHONG Yifan let slash_pos = input.find("//").unwrap_or(input.len());
174*d4726bddSHONG Yifan let repository_name = &input[at_signs..slash_pos];
175*d4726bddSHONG Yifan
176*d4726bddSHONG Yifan if !repository_name.is_empty() {
177*d4726bddSHONG Yifan if !repository_name
178*d4726bddSHONG Yifan .chars()
179*d4726bddSHONG Yifan .next()
180*d4726bddSHONG Yifan .unwrap()
181*d4726bddSHONG Yifan .is_ascii_alphabetic()
182*d4726bddSHONG Yifan {
183*d4726bddSHONG Yifan return Err(LabelError(err(
184*d4726bddSHONG Yifan label,
185*d4726bddSHONG Yifan "workspace names must start with a letter.",
186*d4726bddSHONG Yifan )));
187*d4726bddSHONG Yifan }
188*d4726bddSHONG Yifan if !repository_name
189*d4726bddSHONG Yifan .chars()
190*d4726bddSHONG Yifan .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.' || c == '~')
191*d4726bddSHONG Yifan {
192*d4726bddSHONG Yifan return Err(LabelError(err(
193*d4726bddSHONG Yifan label,
194*d4726bddSHONG Yifan "workspace names \
195*d4726bddSHONG Yifan may contain only A-Z, a-z, 0-9, '-', '_', '.', and '~'.",
196*d4726bddSHONG Yifan )));
197*d4726bddSHONG Yifan }
198*d4726bddSHONG Yifan }
199*d4726bddSHONG Yifan
200*d4726bddSHONG Yifan let repository = if at_signs == 1 {
201*d4726bddSHONG Yifan Repository::Apparent(&input[0..slash_pos])
202*d4726bddSHONG Yifan } else if at_signs == 2 {
203*d4726bddSHONG Yifan if repository_name.is_empty() {
204*d4726bddSHONG Yifan return Err(LabelError(err(
205*d4726bddSHONG Yifan label,
206*d4726bddSHONG Yifan "main repository labels are only represented by a single `@`.",
207*d4726bddSHONG Yifan )));
208*d4726bddSHONG Yifan }
209*d4726bddSHONG Yifan Repository::Canonical(&input[0..slash_pos])
210*d4726bddSHONG Yifan } else {
211*d4726bddSHONG Yifan return Err(LabelError(err(label, "Unexpected number of leading `@`.")));
212*d4726bddSHONG Yifan };
213*d4726bddSHONG Yifan
214*d4726bddSHONG Yifan Ok((&input[slash_pos..], Some(repository)))
215*d4726bddSHONG Yifan }
216*d4726bddSHONG Yifan
consume_package_name<'s>(input: &'s str, label: &'s str) -> Result<(&'s str, Option<&'s str>)>217*d4726bddSHONG Yifan fn consume_package_name<'s>(input: &'s str, label: &'s str) -> Result<(&'s str, Option<&'s str>)> {
218*d4726bddSHONG Yifan let is_absolute = match input.rfind("//") {
219*d4726bddSHONG Yifan None => false,
220*d4726bddSHONG Yifan Some(0) => true,
221*d4726bddSHONG Yifan Some(_) => {
222*d4726bddSHONG Yifan return Err(LabelError(err(
223*d4726bddSHONG Yifan label,
224*d4726bddSHONG Yifan "'//' cannot appear in the middle of the label.",
225*d4726bddSHONG Yifan )));
226*d4726bddSHONG Yifan }
227*d4726bddSHONG Yifan };
228*d4726bddSHONG Yifan
229*d4726bddSHONG Yifan let (package_name, rest) = match (is_absolute, input.find(':')) {
230*d4726bddSHONG Yifan (false, colon_pos) if colon_pos.map_or(true, |pos| pos != 0) => {
231*d4726bddSHONG Yifan return Err(LabelError(err(
232*d4726bddSHONG Yifan label,
233*d4726bddSHONG Yifan "relative packages are not permitted.",
234*d4726bddSHONG Yifan )));
235*d4726bddSHONG Yifan }
236*d4726bddSHONG Yifan (_, colon_pos) => {
237*d4726bddSHONG Yifan let (input, colon_pos) = if is_absolute {
238*d4726bddSHONG Yifan (&input[2..], colon_pos.map(|cp| cp - 2))
239*d4726bddSHONG Yifan } else {
240*d4726bddSHONG Yifan (input, colon_pos)
241*d4726bddSHONG Yifan };
242*d4726bddSHONG Yifan match colon_pos {
243*d4726bddSHONG Yifan Some(colon_pos) => (&input[0..colon_pos], &input[colon_pos..]),
244*d4726bddSHONG Yifan None => (input, ""),
245*d4726bddSHONG Yifan }
246*d4726bddSHONG Yifan }
247*d4726bddSHONG Yifan };
248*d4726bddSHONG Yifan
249*d4726bddSHONG Yifan if package_name.is_empty() {
250*d4726bddSHONG Yifan return Ok((rest, None));
251*d4726bddSHONG Yifan }
252*d4726bddSHONG Yifan
253*d4726bddSHONG Yifan if !package_name.chars().all(|c| {
254*d4726bddSHONG Yifan c.is_ascii_alphanumeric()
255*d4726bddSHONG Yifan || c == '/'
256*d4726bddSHONG Yifan || c == '-'
257*d4726bddSHONG Yifan || c == '.'
258*d4726bddSHONG Yifan || c == ' '
259*d4726bddSHONG Yifan || c == '$'
260*d4726bddSHONG Yifan || c == '('
261*d4726bddSHONG Yifan || c == ')'
262*d4726bddSHONG Yifan || c == '_'
263*d4726bddSHONG Yifan || c == '+'
264*d4726bddSHONG Yifan }) {
265*d4726bddSHONG Yifan return Err(LabelError(err(
266*d4726bddSHONG Yifan label,
267*d4726bddSHONG Yifan "package names may contain only A-Z, \
268*d4726bddSHONG Yifan a-z, 0-9, '/', '-', '.', ' ', '$', '(', ')', '_', and '+'.",
269*d4726bddSHONG Yifan )));
270*d4726bddSHONG Yifan }
271*d4726bddSHONG Yifan if package_name.ends_with('/') {
272*d4726bddSHONG Yifan return Err(LabelError(err(
273*d4726bddSHONG Yifan label,
274*d4726bddSHONG Yifan "package names may not end with '/'.",
275*d4726bddSHONG Yifan )));
276*d4726bddSHONG Yifan }
277*d4726bddSHONG Yifan
278*d4726bddSHONG Yifan if rest.is_empty() && is_absolute {
279*d4726bddSHONG Yifan // This label doesn't contain the target name, we have to use
280*d4726bddSHONG Yifan // last segment of the package name as target name.
281*d4726bddSHONG Yifan return Ok((
282*d4726bddSHONG Yifan match package_name.rfind('/') {
283*d4726bddSHONG Yifan Some(pos) => &package_name[pos..],
284*d4726bddSHONG Yifan None => package_name,
285*d4726bddSHONG Yifan },
286*d4726bddSHONG Yifan Some(package_name),
287*d4726bddSHONG Yifan ));
288*d4726bddSHONG Yifan }
289*d4726bddSHONG Yifan
290*d4726bddSHONG Yifan Ok((rest, Some(package_name)))
291*d4726bddSHONG Yifan }
292*d4726bddSHONG Yifan
consume_name<'s>(input: &'s str, label: &'s str) -> Result<Option<&'s str>>293*d4726bddSHONG Yifan fn consume_name<'s>(input: &'s str, label: &'s str) -> Result<Option<&'s str>> {
294*d4726bddSHONG Yifan if input.is_empty() {
295*d4726bddSHONG Yifan return Ok(None);
296*d4726bddSHONG Yifan }
297*d4726bddSHONG Yifan if input == ":" {
298*d4726bddSHONG Yifan return Err(LabelError(err(label, "empty target name.")));
299*d4726bddSHONG Yifan }
300*d4726bddSHONG Yifan let name = if let Some(stripped) = input.strip_prefix(':') {
301*d4726bddSHONG Yifan stripped
302*d4726bddSHONG Yifan } else if let Some(stripped) = input.strip_prefix("//") {
303*d4726bddSHONG Yifan stripped
304*d4726bddSHONG Yifan } else {
305*d4726bddSHONG Yifan input.strip_prefix('/').unwrap_or(input)
306*d4726bddSHONG Yifan };
307*d4726bddSHONG Yifan
308*d4726bddSHONG Yifan if name.starts_with('/') {
309*d4726bddSHONG Yifan return Err(LabelError(err(
310*d4726bddSHONG Yifan label,
311*d4726bddSHONG Yifan "target names may not start with '/'.",
312*d4726bddSHONG Yifan )));
313*d4726bddSHONG Yifan }
314*d4726bddSHONG Yifan if name.starts_with(':') {
315*d4726bddSHONG Yifan return Err(LabelError(err(
316*d4726bddSHONG Yifan label,
317*d4726bddSHONG Yifan "target names may not contain with ':'.",
318*d4726bddSHONG Yifan )));
319*d4726bddSHONG Yifan }
320*d4726bddSHONG Yifan Ok(Some(name))
321*d4726bddSHONG Yifan }
322*d4726bddSHONG Yifan
name_from_package(package_name: &str) -> &str323*d4726bddSHONG Yifan fn name_from_package(package_name: &str) -> &str {
324*d4726bddSHONG Yifan package_name
325*d4726bddSHONG Yifan .rsplit_once('/')
326*d4726bddSHONG Yifan .map(|tup| tup.1)
327*d4726bddSHONG Yifan .unwrap_or(package_name)
328*d4726bddSHONG Yifan }
329*d4726bddSHONG Yifan
330*d4726bddSHONG Yifan #[cfg(test)]
331*d4726bddSHONG Yifan mod tests {
332*d4726bddSHONG Yifan use super::*;
333*d4726bddSHONG Yifan
334*d4726bddSHONG Yifan #[test]
test_repository_name_parsing() -> Result<()>335*d4726bddSHONG Yifan fn test_repository_name_parsing() -> Result<()> {
336*d4726bddSHONG Yifan assert_eq!(analyze("@repo//:foo")?.repo_name(), Some("repo"));
337*d4726bddSHONG Yifan assert_eq!(analyze("@@repo//:foo")?.repo_name(), Some("repo"));
338*d4726bddSHONG Yifan assert_eq!(analyze("@//:foo")?.repo_name(), Some(""));
339*d4726bddSHONG Yifan assert_eq!(analyze("//:foo")?.repo_name(), None);
340*d4726bddSHONG Yifan assert_eq!(analyze(":foo")?.repo_name(), None);
341*d4726bddSHONG Yifan
342*d4726bddSHONG Yifan assert_eq!(analyze("@repo//foo/bar")?.repo_name(), Some("repo"));
343*d4726bddSHONG Yifan assert_eq!(analyze("@@repo//foo/bar")?.repo_name(), Some("repo"));
344*d4726bddSHONG Yifan assert_eq!(analyze("@//foo/bar")?.repo_name(), Some(""));
345*d4726bddSHONG Yifan assert_eq!(analyze("//foo/bar")?.repo_name(), None);
346*d4726bddSHONG Yifan assert_eq!(
347*d4726bddSHONG Yifan analyze("foo/bar"),
348*d4726bddSHONG Yifan Err(LabelError(
349*d4726bddSHONG Yifan "foo/bar must be a legal label; relative packages are not permitted.".to_string()
350*d4726bddSHONG Yifan ))
351*d4726bddSHONG Yifan );
352*d4726bddSHONG Yifan
353*d4726bddSHONG Yifan assert_eq!(analyze("@repo//foo")?.repo_name(), Some("repo"));
354*d4726bddSHONG Yifan assert_eq!(analyze("@@repo//foo")?.repo_name(), Some("repo"));
355*d4726bddSHONG Yifan assert_eq!(analyze("@//foo")?.repo_name(), Some(""));
356*d4726bddSHONG Yifan assert_eq!(analyze("//foo")?.repo_name(), None);
357*d4726bddSHONG Yifan assert_eq!(
358*d4726bddSHONG Yifan analyze("foo"),
359*d4726bddSHONG Yifan Err(LabelError(
360*d4726bddSHONG Yifan "foo must be a legal label; relative packages are not permitted.".to_string()
361*d4726bddSHONG Yifan ))
362*d4726bddSHONG Yifan );
363*d4726bddSHONG Yifan
364*d4726bddSHONG Yifan assert_eq!(
365*d4726bddSHONG Yifan analyze("@@@repo//foo"),
366*d4726bddSHONG Yifan Err(LabelError(
367*d4726bddSHONG Yifan "@@@repo//foo must be a legal label; Unexpected number of leading `@`.".to_owned()
368*d4726bddSHONG Yifan ))
369*d4726bddSHONG Yifan );
370*d4726bddSHONG Yifan
371*d4726bddSHONG Yifan assert_eq!(
372*d4726bddSHONG Yifan analyze("@@@//foo:bar"),
373*d4726bddSHONG Yifan Err(LabelError(
374*d4726bddSHONG Yifan "@@@//foo:bar must be a legal label; Unexpected number of leading `@`.".to_owned()
375*d4726bddSHONG Yifan ))
376*d4726bddSHONG Yifan );
377*d4726bddSHONG Yifan
378*d4726bddSHONG Yifan assert_eq!(
379*d4726bddSHONG Yifan analyze("@foo:bar"),
380*d4726bddSHONG Yifan Err(LabelError(
381*d4726bddSHONG Yifan "@foo:bar must be a legal label; workspace names may contain only A-Z, a-z, 0-9, '-', '_', '.', and '~'.".to_string()
382*d4726bddSHONG Yifan ))
383*d4726bddSHONG Yifan );
384*d4726bddSHONG Yifan
385*d4726bddSHONG Yifan assert_eq!(
386*d4726bddSHONG Yifan analyze("@AZab0123456789_-.//:foo")?.repo_name(),
387*d4726bddSHONG Yifan Some("AZab0123456789_-.")
388*d4726bddSHONG Yifan );
389*d4726bddSHONG Yifan assert_eq!(
390*d4726bddSHONG Yifan analyze("@42//:baz"),
391*d4726bddSHONG Yifan Err(LabelError(
392*d4726bddSHONG Yifan "@42//:baz must be a legal label; workspace names must \
393*d4726bddSHONG Yifan start with a letter."
394*d4726bddSHONG Yifan .to_string()
395*d4726bddSHONG Yifan ))
396*d4726bddSHONG Yifan );
397*d4726bddSHONG Yifan assert_eq!(
398*d4726bddSHONG Yifan analyze("@foo#//:baz"),
399*d4726bddSHONG Yifan Err(LabelError(
400*d4726bddSHONG Yifan "@foo#//:baz must be a legal label; workspace names \
401*d4726bddSHONG Yifan may contain only A-Z, a-z, 0-9, '-', '_', '.', and '~'."
402*d4726bddSHONG Yifan .to_string()
403*d4726bddSHONG Yifan ))
404*d4726bddSHONG Yifan );
405*d4726bddSHONG Yifan assert_eq!(
406*d4726bddSHONG Yifan analyze("@@//foo/bar"),
407*d4726bddSHONG Yifan Err(LabelError(
408*d4726bddSHONG Yifan "@@//foo/bar must be a legal label; main repository labels are only represented by a single `@`."
409*d4726bddSHONG Yifan .to_string()
410*d4726bddSHONG Yifan ))
411*d4726bddSHONG Yifan );
412*d4726bddSHONG Yifan assert_eq!(
413*d4726bddSHONG Yifan analyze("@@//:foo"),
414*d4726bddSHONG Yifan Err(LabelError(
415*d4726bddSHONG Yifan "@@//:foo must be a legal label; main repository labels are only represented by a single `@`."
416*d4726bddSHONG Yifan .to_string()
417*d4726bddSHONG Yifan ))
418*d4726bddSHONG Yifan );
419*d4726bddSHONG Yifan assert_eq!(
420*d4726bddSHONG Yifan analyze("@@//foo"),
421*d4726bddSHONG Yifan Err(LabelError(
422*d4726bddSHONG Yifan "@@//foo must be a legal label; main repository labels are only represented by a single `@`."
423*d4726bddSHONG Yifan .to_string()
424*d4726bddSHONG Yifan ))
425*d4726bddSHONG Yifan );
426*d4726bddSHONG Yifan
427*d4726bddSHONG Yifan assert_eq!(
428*d4726bddSHONG Yifan analyze("@@"),
429*d4726bddSHONG Yifan Err(LabelError(
430*d4726bddSHONG Yifan "@@ must be a legal label; main repository labels are only represented by a single `@`.".to_string()
431*d4726bddSHONG Yifan )),
432*d4726bddSHONG Yifan );
433*d4726bddSHONG Yifan
434*d4726bddSHONG Yifan Ok(())
435*d4726bddSHONG Yifan }
436*d4726bddSHONG Yifan
437*d4726bddSHONG Yifan #[test]
test_package_name_parsing() -> Result<()>438*d4726bddSHONG Yifan fn test_package_name_parsing() -> Result<()> {
439*d4726bddSHONG Yifan assert_eq!(analyze("//:baz/qux")?.package(), Some(""));
440*d4726bddSHONG Yifan assert_eq!(analyze(":baz/qux")?.package(), None);
441*d4726bddSHONG Yifan
442*d4726bddSHONG Yifan assert_eq!(analyze("//foo:baz/qux")?.package(), Some("foo"));
443*d4726bddSHONG Yifan assert_eq!(analyze("//foo/bar:baz/qux")?.package(), Some("foo/bar"));
444*d4726bddSHONG Yifan assert_eq!(
445*d4726bddSHONG Yifan analyze("foo:baz/qux"),
446*d4726bddSHONG Yifan Err(LabelError(
447*d4726bddSHONG Yifan "foo:baz/qux must be a legal label; relative packages are not permitted."
448*d4726bddSHONG Yifan .to_string()
449*d4726bddSHONG Yifan ))
450*d4726bddSHONG Yifan );
451*d4726bddSHONG Yifan assert_eq!(
452*d4726bddSHONG Yifan analyze("foo/bar:baz/qux"),
453*d4726bddSHONG Yifan Err(LabelError(
454*d4726bddSHONG Yifan "foo/bar:baz/qux must be a legal label; relative packages are not permitted."
455*d4726bddSHONG Yifan .to_string()
456*d4726bddSHONG Yifan ))
457*d4726bddSHONG Yifan );
458*d4726bddSHONG Yifan
459*d4726bddSHONG Yifan assert_eq!(analyze("//foo")?.package(), Some("foo"));
460*d4726bddSHONG Yifan
461*d4726bddSHONG Yifan assert_eq!(
462*d4726bddSHONG Yifan analyze("foo//bar"),
463*d4726bddSHONG Yifan Err(LabelError(
464*d4726bddSHONG Yifan "foo//bar must be a legal label; '//' cannot appear in the middle of the label."
465*d4726bddSHONG Yifan .to_string()
466*d4726bddSHONG Yifan ))
467*d4726bddSHONG Yifan );
468*d4726bddSHONG Yifan assert_eq!(
469*d4726bddSHONG Yifan analyze("//foo//bar"),
470*d4726bddSHONG Yifan Err(LabelError(
471*d4726bddSHONG Yifan "//foo//bar must be a legal label; '//' cannot appear in the middle of the label."
472*d4726bddSHONG Yifan .to_string()
473*d4726bddSHONG Yifan ))
474*d4726bddSHONG Yifan );
475*d4726bddSHONG Yifan assert_eq!(
476*d4726bddSHONG Yifan analyze("foo//bar:baz"),
477*d4726bddSHONG Yifan Err(LabelError(
478*d4726bddSHONG Yifan "foo//bar:baz must be a legal label; '//' cannot appear in the middle of the label."
479*d4726bddSHONG Yifan .to_string()
480*d4726bddSHONG Yifan ))
481*d4726bddSHONG Yifan );
482*d4726bddSHONG Yifan assert_eq!(
483*d4726bddSHONG Yifan analyze("//foo//bar:baz"),
484*d4726bddSHONG Yifan Err(LabelError(
485*d4726bddSHONG Yifan "//foo//bar:baz must be a legal label; '//' cannot appear in the middle of the label."
486*d4726bddSHONG Yifan .to_string()
487*d4726bddSHONG Yifan ))
488*d4726bddSHONG Yifan );
489*d4726bddSHONG Yifan
490*d4726bddSHONG Yifan assert_eq!(
491*d4726bddSHONG Yifan analyze("//azAZ09/-. $()_:baz")?.package(),
492*d4726bddSHONG Yifan Some("azAZ09/-. $()_")
493*d4726bddSHONG Yifan );
494*d4726bddSHONG Yifan assert_eq!(
495*d4726bddSHONG Yifan analyze("//bar#:baz"),
496*d4726bddSHONG Yifan Err(LabelError(
497*d4726bddSHONG Yifan "//bar#:baz must be a legal label; package names may contain only A-Z, \
498*d4726bddSHONG Yifan a-z, 0-9, '/', '-', '.', ' ', '$', '(', ')', '_', and '+'."
499*d4726bddSHONG Yifan .to_string()
500*d4726bddSHONG Yifan ))
501*d4726bddSHONG Yifan );
502*d4726bddSHONG Yifan assert_eq!(
503*d4726bddSHONG Yifan analyze("//bar/:baz"),
504*d4726bddSHONG Yifan Err(LabelError(
505*d4726bddSHONG Yifan "//bar/:baz must be a legal label; package names may not end with '/'.".to_string()
506*d4726bddSHONG Yifan ))
507*d4726bddSHONG Yifan );
508*d4726bddSHONG Yifan
509*d4726bddSHONG Yifan assert_eq!(analyze("@repo//foo/bar")?.package(), Some("foo/bar"));
510*d4726bddSHONG Yifan assert_eq!(analyze("//foo/bar")?.package(), Some("foo/bar"));
511*d4726bddSHONG Yifan assert_eq!(
512*d4726bddSHONG Yifan analyze("foo/bar"),
513*d4726bddSHONG Yifan Err(LabelError(
514*d4726bddSHONG Yifan "foo/bar must be a legal label; relative packages are not permitted.".to_string()
515*d4726bddSHONG Yifan ))
516*d4726bddSHONG Yifan );
517*d4726bddSHONG Yifan
518*d4726bddSHONG Yifan assert_eq!(analyze("@repo//foo")?.package(), Some("foo"));
519*d4726bddSHONG Yifan assert_eq!(analyze("//foo")?.package(), Some("foo"));
520*d4726bddSHONG Yifan assert_eq!(
521*d4726bddSHONG Yifan analyze("foo"),
522*d4726bddSHONG Yifan Err(LabelError(
523*d4726bddSHONG Yifan "foo must be a legal label; relative packages are not permitted.".to_string()
524*d4726bddSHONG Yifan ))
525*d4726bddSHONG Yifan );
526*d4726bddSHONG Yifan
527*d4726bddSHONG Yifan Ok(())
528*d4726bddSHONG Yifan }
529*d4726bddSHONG Yifan
530*d4726bddSHONG Yifan #[test]
test_name_parsing() -> Result<()>531*d4726bddSHONG Yifan fn test_name_parsing() -> Result<()> {
532*d4726bddSHONG Yifan assert_eq!(analyze("//foo:baz")?.name(), "baz");
533*d4726bddSHONG Yifan assert_eq!(analyze("//foo:baz/qux")?.name(), "baz/qux");
534*d4726bddSHONG Yifan assert_eq!(analyze(":baz/qux")?.name(), "baz/qux");
535*d4726bddSHONG Yifan
536*d4726bddSHONG Yifan assert_eq!(
537*d4726bddSHONG Yifan analyze("::baz/qux"),
538*d4726bddSHONG Yifan Err(LabelError(
539*d4726bddSHONG Yifan "::baz/qux must be a legal label; target names may not contain with ':'."
540*d4726bddSHONG Yifan .to_string()
541*d4726bddSHONG Yifan ))
542*d4726bddSHONG Yifan );
543*d4726bddSHONG Yifan
544*d4726bddSHONG Yifan assert_eq!(
545*d4726bddSHONG Yifan analyze("//bar:"),
546*d4726bddSHONG Yifan Err(LabelError(
547*d4726bddSHONG Yifan "//bar: must be a legal label; empty target name.".to_string()
548*d4726bddSHONG Yifan ))
549*d4726bddSHONG Yifan );
550*d4726bddSHONG Yifan assert_eq!(analyze("//foo")?.name(), "foo");
551*d4726bddSHONG Yifan
552*d4726bddSHONG Yifan assert_eq!(
553*d4726bddSHONG Yifan analyze("//bar:/foo"),
554*d4726bddSHONG Yifan Err(LabelError(
555*d4726bddSHONG Yifan "//bar:/foo must be a legal label; target names may not start with '/'."
556*d4726bddSHONG Yifan .to_string()
557*d4726bddSHONG Yifan ))
558*d4726bddSHONG Yifan );
559*d4726bddSHONG Yifan
560*d4726bddSHONG Yifan assert_eq!(analyze("@repo//foo/bar")?.name(), "bar");
561*d4726bddSHONG Yifan assert_eq!(analyze("//foo/bar")?.name(), "bar");
562*d4726bddSHONG Yifan assert_eq!(
563*d4726bddSHONG Yifan analyze("foo/bar"),
564*d4726bddSHONG Yifan Err(LabelError(
565*d4726bddSHONG Yifan "foo/bar must be a legal label; relative packages are not permitted.".to_string()
566*d4726bddSHONG Yifan ))
567*d4726bddSHONG Yifan );
568*d4726bddSHONG Yifan
569*d4726bddSHONG Yifan assert_eq!(analyze("@repo//foo")?.name(), "foo");
570*d4726bddSHONG Yifan assert_eq!(analyze("//foo")?.name(), "foo");
571*d4726bddSHONG Yifan assert_eq!(
572*d4726bddSHONG Yifan analyze("foo"),
573*d4726bddSHONG Yifan Err(LabelError(
574*d4726bddSHONG Yifan "foo must be a legal label; relative packages are not permitted.".to_string()
575*d4726bddSHONG Yifan ))
576*d4726bddSHONG Yifan );
577*d4726bddSHONG Yifan
578*d4726bddSHONG Yifan assert_eq!(
579*d4726bddSHONG Yifan analyze("@repo")?,
580*d4726bddSHONG Yifan Label::Absolute {
581*d4726bddSHONG Yifan repository: Some(Repository::Apparent("@repo")),
582*d4726bddSHONG Yifan package_name: "",
583*d4726bddSHONG Yifan target_name: "repo",
584*d4726bddSHONG Yifan },
585*d4726bddSHONG Yifan );
586*d4726bddSHONG Yifan
587*d4726bddSHONG Yifan assert_eq!(
588*d4726bddSHONG Yifan analyze("@"),
589*d4726bddSHONG Yifan Err(LabelError(
590*d4726bddSHONG Yifan "@ must be a legal label; invalid target name: empty target name".to_string()
591*d4726bddSHONG Yifan )),
592*d4726bddSHONG Yifan );
593*d4726bddSHONG Yifan
594*d4726bddSHONG Yifan Ok(())
595*d4726bddSHONG Yifan }
596*d4726bddSHONG Yifan }
597