use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Scope {
Repository,
Organization,
}
impl Scope {
#[must_use]
pub const fn word(self) -> &'static str {
match self {
Scope::Repository => "repo",
Scope::Organization => "org",
}
}
#[must_use]
pub const fn token(self) -> &'static str {
match self {
Scope::Repository => "repository",
Scope::Organization => "organization",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum RepoName {
Widgets,
Gadgets,
Portal,
WidgetsCase,
Fleet(u8),
Outside,
Malformed,
IllegalChar,
}
impl RepoName {
#[must_use]
pub fn token(self) -> String {
match self {
RepoName::Widgets => "acme/widgets".to_string(),
RepoName::Gadgets => "acme/gadgets".to_string(),
RepoName::Portal => "globex/portal".to_string(),
RepoName::WidgetsCase => "Acme/Widgets".to_string(),
RepoName::Fleet(n) => format!("acme/fleet-{n:02}"),
RepoName::Outside => "outside/tools".to_string(),
RepoName::Malformed => "acme".to_string(),
RepoName::IllegalChar => "acme/wid gets".to_string(),
}
}
#[must_use]
pub fn canonical(self) -> Option<String> {
match self {
RepoName::Malformed | RepoName::IllegalChar => None,
other => Some(other.token().to_ascii_lowercase()),
}
}
#[must_use]
pub const fn class(self) -> &'static str {
match self {
RepoName::Widgets | RepoName::Gadgets => "ordinary",
RepoName::Portal => "second-account",
RepoName::WidgetsCase => "case-variant",
RepoName::Fleet(_) => "budget-fleet",
RepoName::Outside => "not-installed",
RepoName::Malformed => "malformed",
RepoName::IllegalChar => "illegal-character",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum OrgName {
Acme,
Globex,
AcmeCase,
Outside,
TrailingDash,
}
impl OrgName {
#[must_use]
pub fn token(self) -> String {
match self {
OrgName::Acme => "acme",
OrgName::Globex => "globex",
OrgName::AcmeCase => "ACME",
OrgName::Outside => "outside",
OrgName::TrailingDash => "acme-",
}
.to_string()
}
#[must_use]
pub fn canonical(self) -> Option<String> {
match self {
OrgName::TrailingDash => None,
other => Some(other.token().to_ascii_lowercase()),
}
}
#[must_use]
pub const fn class(self) -> &'static str {
match self {
OrgName::Acme => "ordinary",
OrgName::Globex => "second-account",
OrgName::AcmeCase => "case-variant",
OrgName::Outside => "not-installed",
OrgName::TrailingDash => "illegal-edge",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum HostLabelValue {
Home,
Office,
HomeCase,
Max64,
TooLong65,
Space,
TrailingDash,
}
impl HostLabelValue {
#[must_use]
pub fn token(self) -> String {
match self {
HostLabelValue::Home => "home".to_string(),
HostLabelValue::Office => "office".to_string(),
HostLabelValue::HomeCase => "Home".to_string(),
HostLabelValue::Max64 => format!("host-{}", "x".repeat(59)),
HostLabelValue::TooLong65 => format!("host-{}", "x".repeat(60)),
HostLabelValue::Space => "home pc".to_string(),
HostLabelValue::TrailingDash => "home-".to_string(),
}
}
#[must_use]
pub fn canonical(self) -> Option<String> {
match self {
HostLabelValue::TooLong65 | HostLabelValue::Space | HostLabelValue::TrailingDash => {
None
}
other => Some(other.token().to_ascii_lowercase()),
}
}
#[must_use]
pub const fn class(self) -> &'static str {
match self {
HostLabelValue::Home | HostLabelValue::Office => "ordinary",
HostLabelValue::HomeCase => "case-variant",
HostLabelValue::Max64 => "boundary-64",
HostLabelValue::TooLong65 => "too-long-65",
HostLabelValue::Space => "illegal-character",
HostLabelValue::TrailingDash => "illegal-edge",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum LabelValue {
Gpu,
GpuCase,
LargeDisk,
SelfHosted,
Max256,
TooLong257,
Comma,
Blank,
Derived(HostLabelValue),
DerivedCase(HostLabelValue),
}
impl LabelValue {
#[must_use]
pub fn token(self, resolver: &dyn Resolver) -> String {
match self {
LabelValue::Gpu => "gpu".to_string(),
LabelValue::GpuCase => "GPU".to_string(),
LabelValue::LargeDisk => "large-disk".to_string(),
LabelValue::SelfHosted => "self-hosted".to_string(),
LabelValue::Max256 => "l".repeat(256),
LabelValue::TooLong257 => "l".repeat(257),
LabelValue::Comma => "gpu,fast".to_string(),
LabelValue::Blank => " ".to_string(),
LabelValue::Derived(host) => resolver.derived_label(&host_segment(host)),
LabelValue::DerivedCase(host) => resolver
.derived_label(&host_segment(host))
.to_ascii_uppercase(),
}
}
#[must_use]
pub fn canonical(self) -> Option<String> {
match self {
LabelValue::Gpu | LabelValue::GpuCase => Some("gpu".to_string()),
LabelValue::LargeDisk => Some("large-disk".to_string()),
LabelValue::SelfHosted => Some("self-hosted".to_string()),
LabelValue::Max256 => Some("l".repeat(256)),
LabelValue::TooLong257 | LabelValue::Comma | LabelValue::Blank => None,
LabelValue::Derived(host) | LabelValue::DerivedCase(host) => {
Some(derived_symbol(&host_segment(host)))
}
}
}
#[must_use]
pub const fn class(self) -> &'static str {
match self {
LabelValue::Gpu | LabelValue::LargeDisk => "ordinary",
LabelValue::GpuCase => "case-variant",
LabelValue::SelfHosted => "github-default-name",
LabelValue::Max256 => "boundary-256",
LabelValue::TooLong257 => "too-long-257",
LabelValue::Comma => "separator",
LabelValue::Blank => "blank",
LabelValue::Derived(_) => "derived-host-label",
LabelValue::DerivedCase(_) => "derived-host-label-case-variant",
}
}
}
fn host_segment(host: HostLabelValue) -> String {
host.canonical()
.unwrap_or_else(|| host.token().to_ascii_lowercase())
}
#[must_use]
pub fn derived_symbol(host_label: &str) -> String {
format!("rm-{host_label}-<os>-<arch>")
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Capacity {
Zero,
One,
Two,
Five,
Max,
}
impl Capacity {
pub const ALL: [Capacity; 5] = [
Capacity::Zero,
Capacity::One,
Capacity::Two,
Capacity::Five,
Capacity::Max,
];
#[must_use]
pub const fn value(self) -> u16 {
match self {
Capacity::Zero => 0,
Capacity::One => 1,
Capacity::Two => 2,
Capacity::Five => 5,
Capacity::Max => u16::MAX,
}
}
#[must_use]
pub const fn class(self) -> &'static str {
match self {
Capacity::Zero => "zero",
Capacity::One => "one-product-default",
Capacity::Two => "normal",
Capacity::Five => "normal-other",
Capacity::Max => "u16-max",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PathValue {
Alpha,
Beta,
AlphaInner,
RootsDir,
DeepMissing,
OccupiedFile,
Relative,
InsideAppState,
}
impl PathValue {
pub const ALL: [PathValue; 8] = [
PathValue::Alpha,
PathValue::Beta,
PathValue::AlphaInner,
PathValue::RootsDir,
PathValue::DeepMissing,
PathValue::OccupiedFile,
PathValue::Relative,
PathValue::InsideAppState,
];
#[must_use]
pub const fn location(self) -> Option<(Anchor, &'static [&'static str])> {
match self {
PathValue::Alpha => Some((Anchor::Roots, &["alpha"])),
PathValue::Beta => Some((Anchor::Roots, &["beta"])),
PathValue::AlphaInner => Some((Anchor::Roots, &["alpha", "inner"])),
PathValue::RootsDir => Some((Anchor::Roots, &[])),
PathValue::DeepMissing => Some((Anchor::Roots, &["missing", "deep"])),
PathValue::OccupiedFile => Some((Anchor::Roots, &["occupied.txt"])),
PathValue::InsideAppState => Some((Anchor::Data, &["state", "rman"])),
PathValue::Relative => None,
}
}
#[must_use]
pub fn symbolic(self) -> String {
match self.location() {
None => "relative/root".to_string(),
Some((anchor, parts)) => {
let mut text = anchor.symbol().to_string();
for part in parts {
text.push('/');
text.push_str(part);
}
text
}
}
}
#[must_use]
pub const fn class(self) -> &'static str {
match self {
PathValue::Alpha | PathValue::Beta => "fresh-leaf",
PathValue::AlphaInner => "nested-leaf",
PathValue::RootsDir => "existing-parent",
PathValue::DeepMissing => "missing-parents",
PathValue::OccupiedFile => "existing-file",
PathValue::Relative => "relative",
PathValue::InsideAppState => "inside-app-data",
}
}
#[must_use]
pub fn relation(self, other: PathValue) -> Relation {
let (Some((a_anchor, a)), Some((b_anchor, b))) = (self.location(), other.location()) else {
return Relation::Disjoint;
};
if a_anchor != b_anchor {
return Relation::Disjoint;
}
if a == b {
Relation::Same
} else if a.len() > b.len() && a.starts_with(b) {
Relation::Inside
} else if b.len() > a.len() && b.starts_with(a) {
Relation::Contains
} else {
Relation::Disjoint
}
}
#[must_use]
pub const fn is_creatable_leaf(self) -> bool {
matches!(
self,
PathValue::Alpha | PathValue::Beta | PathValue::AlphaInner
)
}
#[must_use]
pub const fn creatable_parent(self) -> Option<PathValue> {
match self {
PathValue::AlphaInner => Some(PathValue::Alpha),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Anchor {
Roots,
Data,
}
impl Anchor {
#[must_use]
pub const fn symbol(self) -> &'static str {
match self {
Anchor::Roots => "<roots>",
Anchor::Data => "<data>",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Relation {
Same,
Inside,
Contains,
Disjoint,
}
pub trait Resolver {
fn path(&self, value: PathValue) -> String;
fn derived_label(&self, host_label: &str) -> String;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Symbolic;
impl Resolver for Symbolic {
fn path(&self, value: PathValue) -> String {
value.symbolic()
}
fn derived_label(&self, host_label: &str) -> String {
derived_symbol(host_label)
}
}
impl fmt::Display for Scope {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.word())
}
}