///|
// Unicode decimal-digit block starts (Unicode 15.1, CPython 3.13). Nd digits,
// not arbitrary numeric symbols, in float and integer strings.
fn report_digit(ch : Char) -> Int32? {
let code = ch.to_int()
for
start in [
0x0030, 0x0660, 0x06F0, 0x07C0, 0x0966, 0x09E6, 0x0A66, 0x0AE6, 0x0B66, 0x0BE6,
0x0C66, 0x0CE6, 0x0D66, 0x0DE6, 0x0E50, 0x0ED0, 0x0F20, 0x1040, 0x1090, 0x17E0,
0x1810, 0x1946, 0x19D0, 0x1A80, 0x1A90, 0x1B50, 0x1BB0, 0x1C40, 0x1C50, 0xA620,
0xA8D0, 0xA900, 0xA9D0, 0xA9F0, 0xAA50, 0xABF0, 0xFF10, 0x104A0, 0x10D30, 0x11066,
0x110F0, 0x11136, 0x111D0, 0x112F0, 0x11450, 0x114D0, 0x11650, 0x116C0, 0x11730,
0x118E0, 0x11950, 0x11C50, 0x11D50, 0x11DA0, 0x11F50, 0x16A60, 0x16AC0, 0x16B50,
0x1D7CE, 0x1D7D8, 0x1D7E2, 0x1D7EC, 0x1D7F6, 0x1E140, 0x1E2F0, 0x1E4F0, 0x1E950,
0x1FBF0,
] {
if code >= start && code < start + 10 {
return Some(code - start)
}
}
None
}
///|
fn report_option(arg : String, command : String) -> Bool {
if arg.has_prefix("--=") || arg.has_prefix("-=") {
return true
}
if arg.has_prefix("-h") {
return true
}
if arg.has_prefix("--") {
let stem = arg.split("=").next().unwrap().to_owned()
if stem.length() > 2 &&
(
"--help".has_prefix(stem) ||
"--glob".has_prefix(stem) ||
(command == "merge" && "--suite-name".has_prefix(stem))
) {
return true
}
}
if !arg.has_prefix("-") || arg == "-" || arg.contains(" ") {
return false
}
let chars = arg.to_array()
let length = chars.length() - (if chars.last() == Some('\n') { 1 } else { 0 })
let mut digits = 0
let mut dot = false
for i = 1; i < length; i = i + 1 {
if report_digit(chars[i]) is Some(_) {
digits += 1
} else if chars[i] == '.' && !dot {
dot = true
digits = 0
} else {
return true
}
}
digits == 0
}
///|
fn report_usage(command : String) -> String {
if command == "merge" {
"Usage: junit-delta merge [--glob] [--suite-name NAME] INPUT... OUTPUT\nOUTPUT '-' writes UTF-8 XML to stdout.\n"
} else {
"Usage: junit-delta verify [--glob] INPUT...\nExit: 0 passed/skipped, 1 failed/error or runtime error, 2 usage error.\n"
}
}
///|
fn run_report_cli(
args : Array[String],
read : (String) -> String raise,
write~ : ((String, String) -> Unit raise)?,
glob~ : ((String) -> Array[String] raise)?,
) -> CliResult {
let command = args[0]
let paths = []
let mut use_glob = false
let mut suite_name = None
let mut options = true
let mut limit = None
let mut invalid = false
let mut index = 1
while index < args.length() {
let arg = args[index]
if options &&
(
(arg.has_prefix("--=") || arg.has_prefix("-=")) ||
(
arg.has_prefix("-h") &&
({
let chars = arg.to_array()
let mut i = 1
while i < chars.length() && chars[i] == 'h' {
i += 1
}
i < chars.length() && (chars[i] == '=' || chars[i] == '-')
})
)
) {
return { stdout: "", stderr: report_usage(command), status: 2, }
}
if options &&
arg.has_prefix("--") &&
arg.contains("=") &&
arg.find("=").unwrap() > 2 &&
(
"--glob".has_prefix(arg[:arg.find("=").unwrap()].to_owned()) ||
"--help".has_prefix(arg[:arg.find("=").unwrap()].to_owned())
) {
return { stdout: "", stderr: report_usage(command), status: 2, }
}
if options &&
(
arg.has_prefix("-h") ||
(arg.has_prefix("--") && "--help".has_prefix(arg) && arg.length() > 2)
) {
return { stdout: report_usage(command), stderr: "", status: 0, }
} else if options && arg == "--" {
if limit is Some(maximum) && paths.length() >= maximum {
invalid = true
}
options = false
} else if options &&
arg.has_prefix("--") &&
arg.length() > 2 &&
"--glob".has_prefix(arg) {
if !paths.is_empty() && limit is None {
limit = Some(
if command == "merge" && paths.length() == 1 {
2
} else {
paths.length()
},
)
}
use_glob = true
} else if options &&
command == "merge" &&
arg.has_prefix("--") &&
arg.contains("=") &&
arg.find("=").unwrap() > 2 &&
"--suite-name".has_prefix(arg.split("=").next().unwrap().to_owned()) {
if !paths.is_empty() && limit is None {
limit = Some(if paths.length() == 1 { 2 } else { paths.length() })
}
suite_name = Some(arg[arg.find("=").unwrap() + 1:].to_owned())
} else if options &&
command == "merge" &&
arg.has_prefix("--") &&
arg.length() > 2 &&
"--suite-name".has_prefix(arg) {
if !paths.is_empty() && limit is None {
limit = Some(if paths.length() == 1 { 2 } else { paths.length() })
}
index += 1
if index >= args.length() || report_option(args[index], command) {
return {
stdout: "",
stderr: "junit-delta: --suite-name requires a value\n",
status: 2,
}
}
suite_name = Some(args[index])
} else if options && report_option(arg, command) {
invalid = true
} else {
if limit is Some(maximum) && paths.length() >= maximum {
invalid = true
}
paths.push(arg)
}
index += 1
}
if invalid || paths.length() < (if command == "merge" { 2 } else { 1 }) {
return { stdout: "", stderr: report_usage(command), status: 2, }
}
let output = if command == "merge" { paths.pop().unwrap() } else { "" }
try {
let result = @junit.JUnitXml::new()
for pattern in paths {
let sources = if use_glob {
match glob {
Some(glob) => glob(pattern)
None =>
raise InvalidInput("this host has not supplied glob expansion")
}
} else {
[pattern]
}
for path in sources {
let report = @junit.JUnitXml::from_decoded(read(path))
if command == "verify" {
for suite in report.suites() {
for case in suite.cases() {
if !case.is_passed() && !case.is_skipped() {
return { stdout: "", stderr: "", status: 1, }
}
}
}
} else {
result.merge_inplace(report)
}
}
}
if command == "verify" {
return { stdout: "", stderr: "", status: 0, }
}
result.update_statistics()
if suite_name is Some(name) && name != "" {
result.set_name(Some(name))
}
let xml = result.element().write_xml()
if output == "-" {
return { stdout: xml, stderr: "", status: 0, }
}
match write {
Some(write) => write(output, xml)
None => raise InvalidInput("this host has not supplied file writing")
}
{ stdout: "", stderr: "", status: 0, }
} catch {
error =>
{
stdout: "",
stderr: "junit-delta: \{terminal_text(clipped(error.to_string(), 2048))}\n",
status: 1,
}
}
}