///|
priv struct Expansion {
original : Map[String, String]
active : Set[String]
mut environment_depth : Int32
level : Int32
serial_children : Bool
}
///|
fn Expansion::new(
original~ : Map[String, String] = @process.default_environment(),
level~ : Int32 = 0,
serial_children~ : Bool = false,
) -> Expansion {
{ original, active: Set([]), environment_depth: 0, level, serial_children, }
}
///|
fn Database::expansion(self : Database) -> Expansion {
Expansion::new(
level=self.invocation_level,
serial_children=self.serial_children,
)
}
///|
async fn expansion_environment(
variables : Map[String, Variable],
automatic : Map[String, String],
depth : Int32,
cwd : String,
exported : ExportPolicy,
state : Expansion,
) -> Map[String, String] {
state.environment_depth += 1
defer {
state.environment_depth -= 1
}
let result : Map[String, String] = Map([])
if state.original.get("SHELL") is Some(shell) {
result["SHELL"] = shell
}
for name in exported.names(variables) {
if variables.contains(name) {
result[name] = lookup_variable(
name,
variables,
automatic,
depth + 1,
cwd~,
exported~,
state~,
)
}
}
result["MAKELEVEL"] = (state.level + 1).to_string()
if state.serial_children && result.get("MAKEFLAGS") is Some(flags) {
result["MAKEFLAGS"] = serial_child_flags(flags)
}
if result.get("MAKEFLAGS") is Some(flags) {
result["MAKEFLAGS"] = flags.replace_all(old="$", new="$$")
}
result
}
///|
fn makelevel(variables : Map[String, Variable]) -> Int32 {
let raw = variables
.get("MAKELEVEL")
.map(variable => variable.value)
.unwrap_or("0")
if raw.is_empty() || raw.iter().any(char => char < '0' || char > '9') {
return 0
}
let value = @string.parse_int(raw, base=10) catch { _ => return 0 }
if value == 2147483647 {
0
} else {
value
}
}
///|
async fn shell_result(
script : String,
variables : Map[String, Variable],
automatic : Map[String, String],
depth : Int32,
cwd : String,
exported : ExportPolicy,
state : Expansion,
trim_newlines~ : Bool,
) -> String {
// GNU expands the selectors even when argv construction finds no command.
let shell = lookup_variable(
"SHELL",
variables,
automatic,
depth,
cwd~,
exported~,
state~,
)
let flags = lookup_variable(
".SHELLFLAGS",
variables,
automatic,
depth,
cwd~,
exported~,
state~,
)
let ifs = lookup_variable(
"IFS",
variables,
automatic,
depth,
cwd~,
exported~,
state~,
)
guard make_shell_source(
script,
ifs,
force_shell=(shell != "" && !default_shell_profile(shell)) ||
(flags != "" && flags != "-c"),
)
is Some(script) else {
return ""
}
let environment = expansion_environment(
variables, automatic, depth, cwd, exported, state,
)
let (status, result) = if script == "" {
@stdio.stderr.write("sh: -c requires an argument\n")
(2, "")
} else {
capture_shell(script, environment, cwd)
}
variables[".SHELLSTATUS"] = { value: status.to_string(), immediate: true, }
let normalized = result.replace_all(old="\r\n", new="\n")
let mut length = normalized.length()
if trim_newlines {
while length > 0 && normalized[length - 1] == '\n' {
length -= 1
}
} else if length > 0 && normalized[length - 1] == '\n' {
length -= 1
}
normalized[:length].to_owned().replace_all(old="\n", new=" ")
}