///|
priv struct ExportPolicy {
explicit : Map[String, Bool]
defaults : Set[String]
mut all : Bool
mut target_all : Bool
}
///|
fn ExportPolicy::new(names : Array[String]) -> ExportPolicy {
let explicit : Map[String, Bool] = Map([])
for name in names {
explicit[name] = true
}
{ explicit, defaults: Set([]), all: false, target_all: false, }
}
///|
fn ExportPolicy::add(self : ExportPolicy, name : String) -> Unit {
self.explicit[name] = true
}
///|
fn ExportPolicy::remove(self : ExportPolicy, name : String) -> Unit {
self.explicit[name] = false
}
///|
fn default_export_name(name : String) -> Bool {
let mut first = true
for char in name {
if !(char is ('A'..<='Z' | 'a'..<='z' | '_')) &&
(first || !(char is ('0'..<='9'))) {
return false
}
first = false
}
!first
}
///|
fn ExportPolicy::names(
self : ExportPolicy,
variables : Map[String, Variable],
) -> Array[String] {
variables
.keys()
.filter(name => {
match self.explicit.get(name) {
Some(exporting) => exporting
None =>
self.all && !self.defaults.contains(name) && default_export_name(name)
}
})
.collect()
}
///|
fn ExportPolicy::finish_reading(self : ExportPolicy) -> Unit {
// GNU applies this target after reading all makefiles, so it does not change
// the environment of an earlier parse-time shell expansion.
if self.target_all {
self.all = true
}
}
///|
fn export_directive(line : String) -> (Bool, String)? {
for (keyword, exporting) in [("export", true), ("unexport", false)] {
if !line.has_prefix(keyword) {
continue
}
let rest = line[keyword.length():]
if !rest.is_empty() &&
!(rest[0] is (' ' | '\t' | '\r' | '\n' | '\u000b' | '\u000c')) {
continue
}
// GNU tries the complete variable definition before treating its first
// token as a modifier. Nonblank whitespace may belong to that name.
if parse_assignment(line) is Some(_) {
return None
}
return Some((exporting, trim_make_space_start(rest.to_owned())))
}
None
}
///|
fn mark_export(database : Database, name : String, exporting : Bool) -> Unit {
// Naming an undefined variable creates an empty simple variable. In
// particular, a later ?= must see it as defined even when unexported.
if !database.variables.contains(name) {
database.variables[name] = { value: "", immediate: true, }
}
if exporting {
database.exported.add(name)
} else {
database.exported.remove(name)
}
}
///|
fn export_assignment(
source : String,
exporting : Bool,
) -> (String, String, String, Bool)? raise MakeError {
let mut source = source
let mut exporting = exporting
let mut unsupported = None
for ;; {
if export_directive(source) is Some((next, remaining)) {
exporting = next
source = remaining
continue
}
if make_function_name(source) is Some("override" | "private" as modifier) &&
!(parse_assignment(source) is Some((name, _, _)) && name == modifier) {
unsupported = Some(modifier)
source = trim_make_space_start(source[modifier.length():].to_owned())
continue
}
return match parse_assignment(source) {
Some((name, operator, value)) => {
if unsupported is Some(modifier) {
raise MakeError(
"assignment modifier '\{modifier}' is outside the supported make profile",
)
}
Some((name, operator, value, exporting))
}
None => None
}
}
}