///|
/// A structured command line that can be rendered for a shell or response
/// file without losing argument boundaries.
pub(all) struct CommandLine {
executable : String
arguments : Array[String]
} derive(Debug, Eq)
///|
pub fn CommandLine::new(
executable~ : String,
arguments~ : Array[String],
) -> CommandLine {
{ executable, arguments }
}
///|
fn quote_argument(argument : String) -> String {
if argument == "" {
return "\"\""
}
let mut safe = true
for ch in argument {
if ch == ' ' || ch == '\t' || ch == '\n' || ch == '"' || ch == '\\' {
safe = false
}
}
if safe {
argument
} else {
let mut quoted = "\""
for ch in argument {
if ch == '"' || ch == '\\' {
quoted += "\\"
}
quoted += "\{ch}"
}
quoted + "\""
}
}
///|
pub fn CommandLine::to_shell_text(self : CommandLine) -> String {
let rendered : Array[String] = [quote_argument(self.executable)]
for argument in self.arguments {
rendered.push(quote_argument(argument))
}
join_strings(rendered, " ")
}
///|
pub fn CommandLine::to_response_file(self : CommandLine) -> String {
let rendered : Array[String] = []
for argument in self.arguments {
rendered.push(quote_argument(argument))
}
join_strings(rendered, " ") + "\n"
}
///|
pub fn CommandLine::append(
self : CommandLine,
argument : String,
) -> CommandLine {
let arguments = self.arguments.copy()
arguments.push(argument)
{ ..self, arguments, }
}
///|
pub fn CommandLine::has_unsafe_argument(self : CommandLine) -> Bool {
for argument in self.arguments {
for ch in argument {
if ch == ' ' || ch == '\t' || ch == '\n' || ch == '"' || ch == '\\' {
return true
}
}
}
false
}
///|
/// Build a structured command from a response-file-compatible token stream.
pub fn command_line_from_text(text : String) -> Result[CommandLine, String] {
match parse_response_file(text) {
Err(error) => Err(error)
Ok(tokens) => {
if tokens.is_empty() {
return Err("command line is empty")
}
let arguments : Array[String] = []
for index, token in tokens {
if index > 0 {
arguments.push(token)
}
}
Ok({ executable: tokens[0], arguments })
}
}
}