// Argument parsing for the portable securitytxt-tool CLI.
///|
/// Parsed options: single-value keeps the last occurrence; repeatable accumulate.
struct CliOptions {
mut text : String
mut text_set : Bool
mut limits : String
mut now : String
mut retrieval_uri : String
mut retrieval_uri_set : Bool
mut content_type : String
mut content_type_set : Bool
mut expires : String
mut canonical : String
mut encryption : String
mut policy : String
mut hiring : String
mut preferred_languages : String
contacts : Array[String]
acknowledgments : Array[String]
extensions : Array[String]
comments : Array[String]
mut help : Bool
positionals : Array[String]
mut unknown : String
}
///|
fn empty_options() -> CliOptions {
{
text: "",
text_set: false,
limits: "default",
now: "",
retrieval_uri: "",
retrieval_uri_set: false,
content_type: "",
content_type_set: false,
expires: "",
canonical: "",
encryption: "",
policy: "",
hiring: "",
preferred_languages: "",
contacts: [],
acknowledgments: [],
extensions: [],
comments: [],
help: false,
positionals: [],
unknown: "",
}
}
///|
fn known_command(cmd : String) -> Bool {
cmd == "parse" ||
cmd == "validate" ||
cmd == "fresh" ||
cmd == "generate" ||
cmd == "audit" ||
cmd == "stats" ||
cmd == "help" ||
cmd == "version" ||
cmd == "--help" ||
cmd == "--version"
}
///|
fn split_command(args : Array[String]) -> (String, Array[String]) {
if args.is_empty() {
return ("help", [])
}
let start = if known_command(args[0]) { 0 } else { 1 }
if start >= args.length() {
return ("help", [])
}
let cmd = match args[start] {
"--help" => "help"
"--version" => "version"
name => name
}
(cmd, args[start + 1:].to_owned())
}
///|
/// Options that take a value; repeatable options land in arrays.
fn value_option(name : String) -> Bool {
name == "--text" ||
name == "--limits" ||
name == "--now" ||
name == "--retrieval-uri" ||
name == "--content-type" ||
name == "--expires" ||
name == "--canonical" ||
name == "--encryption" ||
name == "--policy" ||
name == "--hiring" ||
name == "--preferred-languages" ||
name == "--contact" ||
name == "--acknowledgments" ||
name == "--extension" ||
name == "--comment"
}
///|
fn parse_options(args : Array[String]) -> CliOptions {
let o = empty_options()
let mut i = 0
while i < args.length() {
let a = args[i]
if value_option(a) {
let value = if i + 1 < args.length() { args[i + 1] } else { "" }
match a {
"--text" => {
o.text = value
o.text_set = true
}
"--limits" => o.limits = value
"--now" => o.now = value
"--retrieval-uri" => {
o.retrieval_uri = value
o.retrieval_uri_set = true
}
"--content-type" => {
o.content_type = value
o.content_type_set = true
}
"--expires" => o.expires = value
"--canonical" => o.canonical = value
"--encryption" => o.encryption = value
"--policy" => o.policy = value
"--hiring" => o.hiring = value
"--preferred-languages" => o.preferred_languages = value
"--contact" => o.contacts.push(value)
"--acknowledgments" => o.acknowledgments.push(value)
"--extension" => o.extensions.push(value)
"--comment" => o.comments.push(value)
_ => ()
}
i += 2
} else if a == "--help" || a == "-h" {
o.help = true
i += 1
} else if a.has_prefix("-") {
o.unknown = a
i += 1
} else {
o.positionals.push(a)
i += 1
}
}
o
}
///|
fn resolve_limits(name : String) -> @lib.Limits {
match name {
"strict" => @lib.Limits::strict()
"permissive" => @lib.Limits::permissive()
_ => @lib.Limits::default()
}
}
///|
fn input_text(o : CliOptions) -> String {
if o.text_set {
o.text
} else if o.positionals.length() > 0 {
o.positionals[0]
} else {
""
}
}
///|
/// Parse --now; Ok(None) when absent, Err carries error JSON when not RFC 3339.
fn resolve_now(o : CliOptions) -> Result[@lib.DateTime?, String] {
if o.now.length() == 0 {
return Ok(None)
}
match @lib.parse_rfc3339(o.now) {
Ok(dt) => Ok(Some(dt))
Err(err) => {
let message = "--now must be an RFC 3339 date-time: \{err.message()}"
Err(
"{\"ok\":false,\"error\":{\"kind\":\"InvalidNow\",\"message\":\{q(message)}}}",
)
}
}
}
///|
/// Build the retrieval context from --retrieval-uri / --content-type.
fn resolve_context(o : CliOptions) -> @lib.SecurityTxtContext {
let uri = if o.retrieval_uri_set { Some(o.retrieval_uri) } else { None }
let ct = if o.content_type_set { Some(o.content_type) } else { None }
@lib.security_txt_context(uri, ct)
}