///|
priv struct ManifestPlan {
current : Version
updated : String
}
///|
fn config_field(ast : @moon_config.Ast, key : StringView) -> @moon_config.Ast? {
guard ast is Obj(entries, ..) else { return None }
for entry in entries {
let (entry_key, value) = entry
if entry_key[:] == key {
return Some(value)
}
}
None
}
///|
fn source_offset(source : String, char_offset : Int) -> Int raise SyncError {
if char_offset == source[:].char_length() {
return source.length()
}
match source[:].offset_of_nth_char(char_offset) {
Some(offset) => offset
None => raise SyncError("invalid source location from moon_config")
}
}
///|
fn replace_char_range(
source : String,
start_char : Int,
end_char : Int,
replacement : String,
) -> String raise SyncError {
let start = source_offset(source, start_char)
let end = source_offset(source, end_char)
let result = StringBuilder(size_hint=source.length() + replacement.length())
result.write_substring(source, 0, start)
result.write_string(replacement)
result.write_substring(source, end, source.length() - end)
result.to_string()
}
///|
fn insert_version_after_name(
source : String,
name_ast : @moon_config.Ast,
target : Version,
) -> String raise SyncError {
guard name_ast is Str(_, loc~) else {
raise SyncError("moon_config returned a non-string module name")
}
let name_end = source_offset(source, loc.end.cnum)
let suffix = source[name_end:]
match suffix.find("\n") {
Some(relative_newline) => {
let insertion = name_end + relative_newline + 1
let newline = if insertion >= 2 && source[insertion - 2] == '\r' {
"\r\n"
} else {
"\n"
}
replace_char_range(
source,
source[:].char_length() - source[insertion:].char_length(),
source[:].char_length() - source[insertion:].char_length(),
"version = \"\{target.to_string()}\"\{newline}",
)
}
None => "\{source}\nversion = \"\{target.to_string()}\"\n"
}
}
///|
fn plan_moon_mod(
path : StringView,
source : String,
target : Version,
) -> ManifestPlan raise SyncError {
let (ast, parse_reports) = @moon_config.parse_moon_mod(
source,
name=path.to_owned(),
)
let validation_reports = @moon_config.validate_moon_mod(ast)
if !parse_reports.is_empty() || !validation_reports.is_empty() {
let message = StringBuilder()
message <+ "invalid `\{path}`"
for report in parse_reports {
message <+ "\n\{report}"
}
for report in validation_reports {
message <+ "\n\{report}"
}
raise SyncError(message.to_string())
}
guard config_field(ast, "name") is Some(name_ast) else {
raise SyncError("invalid `\{path}`: missing module name")
}
match config_field(ast, "version") {
Some(Str(version, loc~)) => {
let current = parse_current_version(version)
{
current,
updated: replace_char_range(
source,
loc.start.cnum,
loc.end.cnum,
"\"\{target.to_string()}\"",
),
}
}
Some(_) => raise SyncError("invalid `\{path}`: version must be a string")
None =>
{
current: { major: 0, minor: 0, patch: 0, },
updated: insert_version_after_name(source, name_ast, target),
}
}
}