///|
fn unquote(value : String) -> String {
let value = value.trim().to_owned()
if value.length() >= 2 {
let first = value.code_unit_at(0).to_int()
let last = value.code_unit_at(value.length() - 1).to_int()
if (first == '"'.to_int() && last == '"'.to_int()) ||
(first == '\''.to_int() && last == '\''.to_int()) {
return value[1:value.length() - 1].to_owned()
}
}
value
}
///|
fn parse_bool(value : String, fallback : Bool) -> Bool {
match value.trim().to_lower() {
"true" => true
"false" => false
_ => fallback
}
}
///|
fn parse_string_array(value : String) -> Array[String] {
let value = value.trim().to_owned()
if value.length() < 2 || !value.has_prefix("[") || !value.has_suffix("]") {
return []
}
let result : Array[String] = []
for item in value[1:value.length() - 1].to_owned().split(",") {
let item = unquote(item.to_owned())
if !item.is_empty() {
result.push(item)
}
}
result
}
///|
/// Parse the practical subset of `moonbook.toml` used by MoonBook.
pub fn parse_config(source : String) -> BookConfig {
let config = BookConfig::default()
let mut section = ""
for raw_line in source.split("\n") {
let line = raw_line.trim().to_owned()
if line.is_empty() || line.has_prefix("#") {
continue
}
if line.has_prefix("[") && line.has_suffix("]") {
section = line[1:line.length() - 1].to_owned()
continue
}
let equal = find_text(line, "=", 0)
if equal < 0 {
continue
}
let key = line[0:equal].trim().to_owned()
let value = line[equal + 1:].trim().to_owned()
match (section, key) {
("book", "title") => config.title = unquote(value)
("book", "authors") => config.authors = parse_string_array(value)
("book", "language") => config.language = unquote(value)
("book", "src") => config.source_dir = unquote(value)
("build", "build-dir") => config.build_dir = unquote(value)
("build", "incremental") =>
config.incremental = parse_bool(value, config.incremental)
("output.html", "theme") => config.theme = unquote(value)
("output.html", "template") => {
let path = unquote(value)
config.template = if path.is_empty() { None } else { Some(path) }
}
("output.html", "static-dir") => config.static_dir = unquote(value)
("output.html", "additional-css") =>
config.additional_css = parse_string_array(value)
("output.html", "additional-js") =>
config.additional_js = parse_string_array(value)
("output.html.search", "enable") =>
config.search = parse_bool(value, config.search)
_ => ()
}
}
config
}
///|
pub fn default_config(title : String) -> String {
"[book]\ntitle = \"\{title}\"\nauthors = []\nlanguage = \"zh-CN\"\nsrc = \"src\"\n\n[build]\nbuild-dir = \"book\"\nincremental = true\n\n[output.html]\ntheme = \"light\"\ntemplate = \"\"\nstatic-dir = \"static\"\nadditional-css = []\nadditional-js = []\n\n[output.html.search]\nenable = true\n"
}
///|
fn known_config_key(section : String, key : String) -> Bool {
match (section, key) {
("book", "title" | "authors" | "language" | "src") => true
("build", "build-dir" | "incremental") => true
(
"output.html",
"theme"
| "template"
| "static-dir"
| "additional-css"
| "additional-js",
) => true
("output.html.search", "enable") => true
_ => false
}
}
///|
/// Validate recognized configuration and report ignored or malformed values.
pub fn validate_config(source : String) -> Array[Diagnostic] {
let diagnostics : Array[Diagnostic] = []
let mut section = ""
let mut line_number = 0
for raw_line in source.split("\n") {
line_number += 1
let line = raw_line.trim().to_owned()
if line.is_empty() || line.has_prefix("#") {
continue
}
if line.has_prefix("[") && line.has_suffix("]") {
section = line[1:line.length() - 1].to_owned()
if section != "book" &&
section != "build" &&
section != "output.html" &&
section != "output.html.search" {
diagnostics.push({
severity: "warning",
path: "moonbook.toml",
message: "第 \{line_number} 行:未知配置段 [\{section}]",
})
}
continue
}
let equal = find_text(line, "=", 0)
if equal < 0 {
diagnostics.push({
severity: "error",
path: "moonbook.toml",
message: "第 \{line_number} 行:缺少 =",
})
continue
}
let key = line[0:equal].trim().to_owned()
let value = line[equal + 1:].trim().to_owned()
if !known_config_key(section, key) {
diagnostics.push({
severity: "warning",
path: "moonbook.toml",
message: "第 \{line_number} 行:未知配置项 \{section}.\{key}",
})
}
if (key == "incremental" || key == "enable") &&
value != "true" &&
value != "false" {
diagnostics.push({
severity: "error",
path: "moonbook.toml",
message: "第 \{line_number} 行:\{key} 必须是 true 或 false",
})
}
if (key == "authors" || key == "additional-css" || key == "additional-js") &&
(!value.has_prefix("[") || !value.has_suffix("]")) {
diagnostics.push({
severity: "error",
path: "moonbook.toml",
message: "第 \{line_number} 行:\{key} 必须是字符串数组",
})
}
}
diagnostics
}
///|
pub fn parse_book(
config_text : String,
summary_text : String,
) -> Result[Book, MoonBookError] {
match parse_summary(summary_text) {
Err(error) => Err(error)
Ok(summary) =>
Ok({
config: parse_config(config_text),
chapters: summary_chapters(summary),
summary,
})
}
}