Files
SCOPENET-MC/crates/core/src/maven.rs
T
Claude 8cdf616a94 Add launcher engine (scopenet-core) and admin panel server
- scopenet-shared: wire types for branding, auth, instances, manifests
- scopenet-core: Mojang versions/libraries/assets, automatic Java runtimes,
  Fabric/Quilt/Forge/NeoForge installation, file sync, launch command
  builder, Microsoft auth, servers.dat injection, server list ping
- scopenet-panel: Axum + SQLite admin API with JWT auth, users/groups,
  branding, settings, instances, Modrinth/CurseForge/zip import, file
  hosting and launch stats

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ARcGWxLx21FwXJ3yfGriS
2026-09-28 04:22:17 +00:00

75 lines
2.5 KiB
Rust

/// A Maven coordinate: `group:artifact:version[:classifier][@ext]`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Coord {
pub group: String,
pub artifact: String,
pub version: String,
pub classifier: Option<String>,
pub ext: String,
}
impl Coord {
pub fn parse(s: &str) -> Option<Self> {
let s = s.trim().trim_start_matches('[').trim_end_matches(']');
let (main, ext) = match s.split_once('@') {
Some((m, e)) => (m, e.to_string()),
None => (s, "jar".to_string()),
};
let parts: Vec<&str> = main.split(':').collect();
if parts.len() < 3 {
return None;
}
Some(Self {
group: parts[0].into(),
artifact: parts[1].into(),
version: parts[2].into(),
classifier: parts.get(3).map(|c| c.to_string()),
ext,
})
}
/// Relative repository path (`/`-separated).
pub fn path(&self) -> String {
let classifier = self.classifier.as_ref().map(|c| format!("-{c}")).unwrap_or_default();
format!(
"{}/{}/{}/{}-{}{}.{}",
self.group.replace('.', "/"),
self.artifact,
self.version,
self.artifact,
self.version,
classifier,
self.ext
)
}
/// Identity used to de-duplicate libraries (version-less).
pub fn key(&self) -> String {
match &self.classifier {
Some(c) => format!("{}:{}:{}", self.group, self.artifact, c),
None => format!("{}:{}", self.group, self.artifact),
}
}
pub fn with_classifier(&self, classifier: &str) -> Self {
Self { classifier: Some(classifier.to_string()), ..self.clone() }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_coords() {
let c = Coord::parse("net.fabricmc:fabric-loader:0.16.9").unwrap();
assert_eq!(c.path(), "net/fabricmc/fabric-loader/0.16.9/fabric-loader-0.16.9.jar");
let c = Coord::parse("de.oceanlabs.mcp:mcp_config:1.20.1-20230612.114412@zip").unwrap();
assert_eq!(c.path(), "de/oceanlabs/mcp/mcp_config/1.20.1-20230612.114412/mcp_config-1.20.1-20230612.114412.zip");
let c = Coord::parse("[net.minecraft:client:1.20.1-20230612.114412:slim]").unwrap();
assert_eq!(c.path(), "net/minecraft/client/1.20.1-20230612.114412/client-1.20.1-20230612.114412-slim.jar");
assert_eq!(c.key(), "net.minecraft:client:slim");
assert!(Coord::parse("nope").is_none());
}
}