///|
fn make_shell_fast_arguments(script : String, ifs : String) -> Array[String]? {
for char in ifs {
if !(char is (' ' | '\t' | '\n')) {
return None
}
}
let chars = script.iter().collect()
let arguments : Array[String] = []
let word = StringBuilder()
let mut present = false
let mut quoted = false
let mut index = 0
while index < chars.length() {
let char = chars[index]
if quoted {
if char == '\'' {
quoted = false
} else {
word.write_char(char)
}
} else if char == '\\' {
index += 1
if index < chars.length() {
if chars[index] == '\n' {
if word.is_empty() {
while index + 1 < chars.length() && chars[index + 1] is (' ' | '\t') {
index += 1
}
}
} else {
present = true
word.write_char(chars[index])
}
}
} else if char == '\'' {
quoted = true
present = true
} else if char is (' ' | '\t') {
if present {
arguments.push(word.to_string())
word.reset()
present = false
}
} else {
if "#;\"*?[]&|<>(){}$`^~!".contains(char.to_string()) ||
(arguments.is_empty() && char == '=') {
return None
}
present = true
word.write_char(char)
}
index += 1
}
if quoted {
return None
}
if present {
arguments.push(word.to_string())
}
if arguments.get(0) is Some(command) &&
[
".", ":", "alias", "bg", "break", "case", "cd", "command", "continue", "eval",
"exec", "exit", "export", "fc", "fg", "for", "getopts", "hash", "if", "jobs",
"login", "logout", "read", "readonly", "return", "set", "shift", "test", "times",
"trap", "type", "ulimit", "umask", "unalias", "unset", "wait", "while",
].contains(command) {
return None
}
Some(arguments)
}
///|
fn make_shell_source(
script : String,
ifs : String,
force_shell~ : Bool = false,
) -> String? {
let mut start = 0
while start < script.length() && script[start] is (' ' | '\t') {
start += 1
}
let script = script[start:].to_owned()
if script == "" {
return None
}
// GNU's default /bin/sh argv builder treats LF as an ordinary byte on its
// direct-argv path. Quoting those arguments preserves them in our interpreter.
if !force_shell && make_shell_fast_arguments(script, ifs) is Some(arguments) {
if arguments.is_empty() {
return None
}
return Some(
arguments
.map(argument => "'" + argument.replace_all(old="'", new="'\\''") + "'")
.join(" "),
)
}
// The shell path escapes the source then reparses its argv. This removes a
// raw LF but preserves an original backslash-LF pair for the shell itself.
let chars = script.iter().collect()
let result = StringBuilder()
let mut index = 0
while index < chars.length() {
if chars[index] == '\\' && chars.get(index + 1) == Some('\n') {
result.write_string("\\\n")
index += 2
} else {
if chars[index] != '\n' {
result.write_char(chars[index])
}
index += 1
}
}
Some(result.to_string())
}