///|
/// A configuration file format. `parse` takes the text and the origin, for
/// example a file path, and gives a value tree with a struct root.
pub(all) struct Format {
name : String
extensions : Array[String]
parse : (String, String) -> ConfigValue raise ConfigError
}
///|
/// A copy that does not share the extension list with `self`.
fn Format::copy(self : Format) -> Format {
{ ..self, extensions: self.extensions.copy() }
}
///|
/// Parse the text of the file `path` with the format that its extension
/// selects from `formats`. The extension compares in lowercase. No match
/// raises `UnsupportedFormat` with the registered extensions.
pub fn parse_file(
formats : Array[Format],
path : String,
text : String,
) -> ConfigValue raise ConfigError {
parse_with(select_format(formats, path), path, text)
}
///|
/// The first format in `formats` that reads the extension of `path`.
fn select_format(
formats : Array[Format],
path : String,
) -> Format raise ConfigError {
let name = match path.rev_find("/") {
Some(index) => path[index + 1:].to_owned()
None => path
}
if name.rev_find(".") is Some(index) && index > 0 {
let extension = name[index + 1:].to_owned().to_lower()
for format in formats {
if format.extensions.iter().any(item => item.to_lower() == extension) {
return format
}
}
}
raise UnsupportedFormat(
path~,
extensions=formats.map(format => format.extensions).flatten(),
)
}