///|
/// MoonBit string literal for `value`, using only the escapes MoonBit's lexer
/// accepts.
///
/// The inverse of `unescape`: a value that went through `parse_moon_mod` and
/// comes back out here is decoded exactly once, so regenerating a generated
/// file never adds a backslash.
pub fn quote(value : String) -> String {
let buf = StringBuilder()
buf.write_char('"')
for ch in value {
match ch {
'"' => buf.write_string("\\\"")
'\\' => buf.write_string("\\\\")
'\n' => buf.write_string("\\n")
'\r' => buf.write_string("\\r")
'\t' => buf.write_string("\\t")
_ => buf.write_char(ch)
}
}
buf.write_char('"')
buf.to_string()
}
///|
fn quote_array(values : Array[String]) -> String {
"[" + values.map(quote).join(", ") + "]"
}
///|
/// Render one struct literal field, e.g. `name: "acme/demo",`.
fn field(name : String, value : String) -> String {
" \{name}: \{value},"
}
///|
fn array_field(name : String, values : Array[String]) -> String {
field(name, quote_array(values))
}
///|
/// Render the implementation that `dev_build` writes into a virtual package
/// implementation: a `module_info()` accessor returning `@pkg_alias.ModuleInfo`.
///
/// It is a function rather than a `let` on purpose: `moon` type-checks a
/// virtual package's implementations against its `pkg.mbti`, and only function
/// signatures in the `pkg.mbti` form survive that check for a type coming from
/// another package.
///
/// `pkg_path` is how the implementation package reaches this one. It is a
/// parameter because the implementation lives somewhere else and its hand
/// written `pkg.mbti` has to spell the type the same way: `mooninfo` from a
/// sibling package, `PaiGack/moon_shadow/mooninfo` from another module.
///
/// The output is `moon fmt` clean, so a regenerated file never shows up as a
/// diff just because the formatter has an opinion about commas.
pub fn render_module_info(info : ModuleInfo, pkg_path~ : String) -> String {
let lines : Array[String] = [
"///|",
"/// Generated from `moon.mod`. Keep in sync when `moon.mod` changes.",
"pub fn module_info() -> @\{pkg_path}.ModuleInfo {",
" {",
field("name", quote(info.name)),
field("version", quote(info.version)),
field("description", quote(info.description)),
field("license", quote(info.license)),
field("repository", quote(info.repository)),
field("readme", quote(info.readme)),
array_field("keywords", info.keywords),
field("preferred_target", quote(info.preferred_target)),
array_field("supported_targets", info.supported_targets),
]
if info.deps.is_empty() {
lines.push(field("deps", "[]"))
} else {
lines.push(" deps: [")
for dep in info.deps {
lines.push(
" { name: \{quote(dep.name)}, version: \{quote(dep.version)} },",
)
}
lines.push(" ],")
}
lines.push(" }")
lines.push("}")
lines.join("\n") + "\n"
}