///|
priv struct Dependency {
name : String
version : String
}
///|
priv struct MoonMod {
content : String
version : String?
dependencies : Array[Dependency]
}
///|
fn MoonMod::parse(content : String) -> MoonMod {
{
content,
version: parse_version(content),
dependencies: parse_import_dependencies(content),
}
}
///|
async fn read_moon_mod(moon_mod_path : String) -> MoonMod raise @core.CliError {
guard path_exists(moon_mod_path) else {
raise @core.cli_error("moon.mod not found: \{moon_mod_path}")
}
let content = @fs.read_file(moon_mod_path) catch {
err => raise @core.cli_error("failed to read \{moon_mod_path}: \{err}")
}
let text = content.text() catch {
err =>
raise @core.cli_error("failed to decode \{moon_mod_path}: \{Repr(err)}")
}
MoonMod::parse(text)
}
///|
fn parse_version(content : String) -> String? {
let prefix = "version = \""
for raw_line in content.split("\n") {
let line = raw_line.trim()
guard line.has_prefix(prefix) else { continue }
guard line[prefix.length():].split_once("\"") is Some((version, _)) else {
return None
}
return Some(version.to_owned())
}
None
}
///|
fn MoonMod::dependency_version(self : MoonMod, name : String) -> String? {
for dependency in self.dependencies {
guard dependency.name == name else { continue }
return Some(dependency.version)
}
None
}
///|
fn parse_import_dependencies(content : String) -> Array[Dependency] {
let dependencies : Array[Dependency] = []
let mut in_import = false
for raw_line in content.split("\n") {
let line = raw_line.trim()
if line.has_prefix("import") {
in_import = true
} else if line.has_prefix("}") {
in_import = false
} else if in_import {
if dependency_from_import(line) is Some(dependency) {
dependencies.push(dependency)
}
}
}
dependencies
}
///|
fn dependency_from_import(line : StringView) -> Dependency? {
guard line.split_once("\"") is Some((_, rest)) else { return None }
guard rest.split_once("\"") is Some((entry, _)) else { return None }
guard !entry.is_empty() &&
!entry.has_prefix(".") &&
!entry.has_prefix("/") &&
!entry.contains("://") else {
None
}
match entry.split_once("@") {
Some((name, version)) if !name.is_empty() && !version.is_empty() =>
Some({ name: name.to_owned(), version: version.to_owned(), })
None => Some({ name: entry.to_owned(), version: "unknown", })
_ => None
}
}