mirror of
https://seed.radicle.dev/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt.git
synced 2026-10-27 07:10:58 +08:00
- Refactored branches(), tags(), namespaces(). - Added filtering for revision_branches(). Signed-off-by: Han Xu <keepsimple@gmail.com>
107 lines
3.3 KiB
Rust
107 lines
3.3 KiB
Rust
// Copyright © 2021 The Radicle Link Contributors
|
|
//
|
|
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
|
|
// Linking Exception. For full terms see the included LICENSE file.
|
|
|
|
use thiserror::Error;
|
|
|
|
pub struct Options {
|
|
/// If `false`, the refname must contain at least one `/`.
|
|
pub allow_onelevel: bool,
|
|
/// If `true`, the refname may contain exactly one `*` character.
|
|
pub allow_pattern: bool,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq, Error)]
|
|
#[non_exhaustive]
|
|
pub enum Error {
|
|
#[error("empty input")]
|
|
Empty,
|
|
#[error("lone '@' character")]
|
|
LoneAt,
|
|
#[error("consecutive or trailing slash")]
|
|
Slash,
|
|
#[error("ends with '.lock'")]
|
|
DotLock,
|
|
#[error("consecutive dots ('..')")]
|
|
DotDot,
|
|
#[error("at-open-brace ('@{{')")]
|
|
AtOpenBrace,
|
|
#[error("invalid character {0:?}")]
|
|
InvalidChar(char),
|
|
#[error("component starts with '.'")]
|
|
StartsDot,
|
|
#[error("component ends with '.'")]
|
|
EndsDot,
|
|
#[error("control character")]
|
|
Control,
|
|
#[error("whitespace")]
|
|
Space,
|
|
#[error("must contain at most one '*'")]
|
|
Pattern,
|
|
#[error("must contain at least one '/'")]
|
|
OneLevel,
|
|
}
|
|
|
|
/// Validate that a string slice is a valid refname.
|
|
pub fn ref_format(opts: Options, s: &str) -> Result<(), Error> {
|
|
match s {
|
|
"" => Err(Error::Empty),
|
|
"@" => Err(Error::LoneAt),
|
|
"." => Err(Error::StartsDot),
|
|
_ => {
|
|
let mut globs = 0usize;
|
|
let mut parts = 0usize;
|
|
|
|
for x in s.split('/') {
|
|
if x.is_empty() {
|
|
return Err(Error::Slash);
|
|
}
|
|
|
|
parts += 1;
|
|
|
|
if x.ends_with(".lock") {
|
|
return Err(Error::DotLock);
|
|
}
|
|
|
|
let last_char = x.chars().count() - 1;
|
|
for (i, y) in x.chars().zip(x.chars().cycle().skip(1)).enumerate() {
|
|
match y {
|
|
('.', '.') => return Err(Error::DotDot),
|
|
('@', '{') => return Err(Error::AtOpenBrace),
|
|
|
|
('\0', _) => return Err(Error::InvalidChar('\0')),
|
|
('\\', _) => return Err(Error::InvalidChar('\\')),
|
|
('~', _) => return Err(Error::InvalidChar('~')),
|
|
('^', _) => return Err(Error::InvalidChar('^')),
|
|
(':', _) => return Err(Error::InvalidChar(':')),
|
|
('?', _) => return Err(Error::InvalidChar('?')),
|
|
('[', _) => return Err(Error::InvalidChar('[')),
|
|
|
|
('*', _) => globs += 1,
|
|
|
|
('.', _) if i == 0 => return Err(Error::StartsDot),
|
|
('.', _) if i == last_char => return Err(Error::EndsDot),
|
|
|
|
(' ', _) => return Err(Error::Space),
|
|
|
|
(z, _) if z.is_ascii_control() => return Err(Error::Control),
|
|
|
|
_ => continue,
|
|
}
|
|
}
|
|
}
|
|
|
|
if parts < 2 && !opts.allow_onelevel {
|
|
Err(Error::OneLevel)
|
|
} else if globs > 1 && opts.allow_pattern {
|
|
Err(Error::Pattern)
|
|
} else if globs > 0 && !opts.allow_pattern {
|
|
Err(Error::InvalidChar('*'))
|
|
} else {
|
|
Ok(())
|
|
}
|
|
},
|
|
}
|
|
}
|