///|
fn portable_project_path(name : String) -> String raise {
workspace_path(name) |> ignore
if @utf8.encode(name[:]).length() > 4096 {
raise InvalidReference("Export path exceeds 4096 bytes")
}
if name.split("/").count() > 64 {
raise InvalidReference("Export path exceeds 64 components")
}
for component in name.split("/") {
let part = component.to_owned()
if @utf8.encode(part[:]).length() > 255 {
raise InvalidReference("Export filename exceeds 255 UTF-8 bytes")
}
let base = part.split(".").next().unwrap_or("").to_owned().to_lower()
if part.has_suffix(".") ||
part.has_suffix(" ") ||
part.any(c => {
c.to_int() < 32 ||
c.to_int() == 127 ||
"<>:\"\\|?*".contains(c.to_string())
}) ||
[
"con", "prn", "aux", "nul", "clock$", "com¹", "com²", "com³", "lpt¹",
"lpt²", "lpt³", "conin$", "conout$", "com1", "com2", "com3", "com4", "com5",
"com6", "com7", "com8", "com9", "lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6",
"lpt7", "lpt8", "lpt9",
].contains(base) {
raise InvalidReference("Export requires portable relative filenames")
}
}
name
}
///|
/// Validate all contents and portable paths before hosts create any output.
/// Only the files explicitly contained in the snapshot are exported.
pub fn project_export_files(
snapshot : Json,
options~ : ParseOptions = ParseOptions::default(),
) -> Map[String, String] raise {
guard snapshot is Object(fields) &&
fields.get("format") == Some(Json::string("moonvalve-project-v1")) &&
fields.get("source") is Some(String(source)) &&
fields.get("files") is Some(Object(rows)) &&
rows.length() > 0 &&
rows.length() <= 64 else {
raise InvalidReference("Expected a project snapshot containing 1..64 files")
}
let output : Map[String, String] = Map([])
let names : Map[String, Bool] = Map([])
let mut total = 0
for name, value in rows {
portable_project_path(name) |> ignore
let lower = name.to_lower()
if names.contains(lower) {
raise InvalidReference("Case-insensitive export filename collision")
}
names[lower] = true
guard value is String(text) else {
raise InvalidReference("Expected text file contents")
}
total += @utf8.encode(text[:]).length()
if total > 67108864 {
raise InvalidReference("Project export exceeds 64 MiB")
}
if name.to_lower().has_suffix(".kv3") ||
text.trim_start().to_owned().has_prefix("