///|
priv struct CoverageCliResponse {
roster : Json
error : Json
} derive(ToJson)
///|
priv struct BalancedCliResponse {
roster : Json
balance : Json
candidates_evaluated : Int
error : Json
} derive(ToJson)
///|
fn response_error(message : String) -> String {
let absent : String? = None
let response : CoverageCliResponse = {
roster: @json.to_json(absent),
error: @json.to_json(message),
}
@json.to_json(response).stringify(indent=2)
}
///|
fn check_coverage(text : String) -> String {
let request = match @moonplan.decode_coverage_request(text) {
Err(error) => return response_error(error.message())
Ok(request) => request
}
match
@moonplan.analyze_coverage_with_policy(
request.workers,
request.requirements,
request.policy,
) {
Err(error) => response_error(error.message())
Ok(analysis) => @moonplan.encode_coverage_analysis(analysis, 2)
}
}
///|
fn solve_coverage(text : String, csv : Bool) -> String {
let request = match @moonplan.decode_coverage_request(text) {
Err(error) => return response_error(error.message())
Ok(request) => request
}
let analysis = match
@moonplan.analyze_coverage_with_policy(
request.workers,
request.requirements,
request.policy,
) {
Err(error) => return response_error(error.message())
Ok(analysis) => analysis
}
if !analysis.is_feasible() {
return response_error(analysis.summary())
}
match request.solve() {
Err(error) => response_error(error.message())
Ok(roster) => {
if csv {
return @moonplan.encode_coverage_roster_csv(roster)
}
let absent : String? = None
let response : CoverageCliResponse = {
roster: @json.to_json(roster),
error: @json.to_json(absent),
}
@json.to_json(response).stringify(indent=2)
}
}
}
///|
fn solve_coverage_bounded(text : String, node_limit : Int) -> String {
let request = match @moonplan.decode_coverage_request(text) {
Err(error) => return response_error(error.message())
Ok(request) => request
}
match request.solve_with_node_limit(node_limit) {
Err(error) => return response_error(error.message())
Ok(outcome) => @moonplan.encode_coverage_solve_outcome(outcome, 2)
}
}
///|
fn solve_coverage_balanced(text : String, candidate_limit : Int) -> String {
let request = match @moonplan.decode_coverage_request(text) {
Err(error) => return response_error(error.message())
Ok(request) => request
}
let analysis = match
@moonplan.analyze_coverage_with_policy(
request.workers,
request.requirements,
request.policy,
) {
Err(error) => return response_error(error.message())
Ok(analysis) => analysis
}
if !analysis.is_feasible() {
return response_error(analysis.summary())
}
match request.solve_balanced(candidate_limit) {
Err(error) => response_error(error.message())
Ok(result) => {
let absent : String? = None
let response : BalancedCliResponse = {
roster: @json.to_json(result.roster),
balance: @json.to_json(result.balance),
candidates_evaluated: result.candidates_evaluated,
error: @json.to_json(absent),
}
@json.to_json(response).stringify(indent=2)
}
}
}
///|
fn solve_coverage_fairest(text : String, node_limit : Int) -> String {
let request = match @moonplan.decode_coverage_request(text) {
Err(error) => return response_error(error.message())
Ok(request) => request
}
match request.solve_fairest(node_limit) {
Err(error) => response_error(error.message())
Ok(outcome) => @moonplan.encode_fair_coverage_outcome(outcome, 2)
}
}
///|
fn output_csv(args : Array[String]) -> Bool? {
let mut found = false
let mut csv = false
for index = 0; index < args.length(); index = index + 1 {
if args[index] == "--format" {
if found || index + 1 >= args.length() {
return None
}
found = true
match args[index + 1] {
"json" => csv = false
"csv" => csv = true
_ => return None
}
}
}
Some(csv)
}
///|
fn json_argument(args : Array[String]) -> String? {
let mut result : String? = None
for index = 0; index + 1 < args.length(); index = index + 1 {
if args[index] == "--json" {
if result is Some(_) {
return None
}
result = Some(args[index + 1])
}
}
result
}
///|
fn positive_limit(args : Array[String], flag : String) -> Result[Int?, String] {
let mut budget : Int? = None
for index = 0; index < args.length(); index = index + 1 {
if args[index] == flag {
if budget is Some(_) || index + 1 >= args.length() {
return Err("\{flag} requires exactly one positive integer")
}
let parsed = @string.parse_int(args[index + 1]) catch {
_ => return Err("\{flag} requires exactly one positive integer")
}
if parsed <= 0 {
return Err("\{flag} requires exactly one positive integer")
}
budget = Some(parsed)
}
}
Ok(budget)
}
///|
fn node_budget(args : Array[String]) -> Result[Int?, String] {
positive_limit(args, "--node-limit")
}
///|
fn candidate_budget(args : Array[String]) -> Result[Int?, String] {
positive_limit(args, "--candidate-limit")
}
///|
fn fair_budget(args : Array[String]) -> Result[Int?, String] {
positive_limit(args, "--fair-node-limit")
}
///|
fn main {
let args = @env.args()
match
(
json_argument(args),
output_csv(args),
node_budget(args),
candidate_budget(args),
fair_budget(args),
) {
(Some(text), Some(csv), Ok(budget), Ok(candidates), Ok(fair_limit)) =>
if (
args.contains("--check") &&
(
csv ||
budget is Some(_) ||
candidates is Some(_) ||
fair_limit is Some(_)
)
) ||
(
csv &&
(budget is Some(_) || candidates is Some(_) || fair_limit is Some(_))
) ||
(budget is Some(_) && (candidates is Some(_) || fair_limit is Some(_))) ||
(candidates is Some(_) && fair_limit is Some(_)) {
println(
response_error(
"--check, --format csv, --node-limit, --candidate-limit, and --fair-node-limit cannot be combined",
),
)
} else if args.contains("--check") {
println(check_coverage(text))
} else {
match budget {
Some(limit) => println(solve_coverage_bounded(text, limit))
None =>
match candidates {
Some(limit) => println(solve_coverage_balanced(text, limit))
None =>
match fair_limit {
Some(limit) => println(solve_coverage_fairest(text, limit))
None => println(solve_coverage(text, csv))
}
}
}
}
(_, _, Err(message), _, _) => println(response_error(message))
(_, _, _, Err(message), _) => println(response_error(message))
(_, _, _, _, Err(message)) => println(response_error(message))
_ =>
println(
response_error(
"usage: moon run cmd/coverage --json '' [--check | --format json|csv | --node-limit N | --candidate-limit N | --fair-node-limit N]",
),
)
}
}