///|
fn expand_tokens_at(
value : String,
specification : DirectiveSpec,
context : ResolveContext,
source : @syntax.SourceLocation,
) -> String raise ResolveError {
let output = StringBuilder()
let chars : Array[Char] = []
for char in value {
chars.push(char)
}
let mut index = 0
while index < chars.length() {
let char = chars[index]
if char != '%' {
output.write_char(char)
index += 1
continue
}
if index + 1 >= chars.length() {
raise UnsupportedToken(token="%", keyword=specification.keyword, source~)
}
let token = chars[index + 1].to_string()
if token == "%" {
output.write_char('%')
index += 2
continue
}
if !is_member(token, specification.allowed_tokens) {
raise UnsupportedToken(
token="%\{token}",
keyword=specification.keyword,
source~,
)
}
let replacement = match token {
"h" => context.host
"n" => context.original_host
"p" => context.port.to_string()
"r" =>
match context.remote_user {
Some(value) => value
None =>
raise MissingTokenContext(
token="%r",
keyword=specification.keyword,
source~,
)
}
"u" =>
match context.local_user {
Some(value) => value
None =>
raise MissingTokenContext(
token="%u",
keyword=specification.keyword,
source~,
)
}
"d" =>
match context.home {
Some(value) => value
None =>
raise MissingTokenContext(
token="%d",
keyword=specification.keyword,
source~,
)
}
_ =>
raise UnsupportedToken(
token="%\{token}",
keyword=specification.keyword,
source~,
)
}
output.write_string(replacement)
index += 2
}
output.to_string()
}
///|
/// Expand only the documented P0 `%` tokens. This is a single scan: a token
/// introduced by an expansion is data, not another expansion request.
///
/// Direct callers do not have a configuration source location, so errors use
/// the stable synthetic `:1:1` location. The resolver's internal call
/// path uses the directive's real source location.
pub fn expand_tokens(
value : String,
specification : DirectiveSpec,
context : ResolveContext,
) -> String raise ResolveError {
expand_tokens_at(value, specification, context, {
path: "",
line: 1,
column: 1,
})
}