Remove 'default' property on languages, use book.language instead

This commit is contained in:
Ruin0x11 2020-08-28 12:26:08 -07:00
parent a042cfc72b
commit ee740aceff
9 changed files with 89 additions and 60 deletions

View File

@ -15,7 +15,7 @@ pub fn load_book<P: AsRef<Path>>(
cfg: &Config,
build_opts: &BuildOpts,
) -> Result<LoadedBook> {
if cfg.language.has_localized_dir_structure() {
if cfg.has_localized_dir_structure() {
match build_opts.language_ident {
// Build a single book's translation.
Some(_) => Ok(LoadedBook::Single(load_single_book_translation(

View File

@ -14,18 +14,18 @@ pub struct BookBuilder {
create_gitignore: bool,
config: Config,
copy_theme: bool,
language_ident: String
language_ident: String,
}
fn add_default_language(cfg: &mut Config, language_ident: String) {
let language = Language {
name: String::from("English"),
default: true,
title: None,
authors: None,
description: None,
};
cfg.language.0.insert(language_ident, language);
cfg.language.0.insert(language_ident.clone(), language);
cfg.book.language = Some(language_ident);
}
impl BookBuilder {
@ -41,13 +41,16 @@ impl BookBuilder {
create_gitignore: false,
config: cfg,
copy_theme: false,
language_ident: language_ident
language_ident: language_ident,
}
}
/// Get the output source directory of the builder.
pub fn source_dir(&self) -> PathBuf {
let src = self.config.get_localized_src_path(Some(&self.language_ident)).unwrap();
let src = self
.config
.get_localized_src_path(Some(&self.language_ident))
.unwrap();
self.root.join(src)
}

View File

@ -19,7 +19,7 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
.arg_from_usage("-o, --open 'Opens the compiled book in a web browser'")
.arg_from_usage(
"-l, --language=[language] 'Language to render the compiled book in.{n}\
Only valid if the [languages] table in the config is not empty.{n}\
Only valid if the [language] table in the config is not empty.{n}\
If omitted, builds all translations and provides a menu in the generated output for switching between them.'",
)
}

View File

@ -20,7 +20,7 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
)
.arg_from_usage(
"-l, --language=[language] 'Language to render the compiled book in.{n}\
Only valid if the [languages] table in the config is not empty.{n}\
Only valid if the [language] table in the config is not empty.{n}\
If omitted, builds all translations and provides a menu in the generated output for switching between them.'",
)
}

View File

@ -52,7 +52,7 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
.arg_from_usage("-o, --open 'Opens the book server in a web browser'")
.arg_from_usage(
"-l, --language=[language] 'Language to render the compiled book in.{n}\
Only valid if the [languages] table in the config is not empty.{n}\
Only valid if the [language] table in the config is not empty.{n}\
If omitted, builds all translations and provides a menu in the generated output for switching between them.'",
)
}
@ -86,7 +86,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
let language: Option<String> = match build_opts.language_ident {
// index.html will be at the root directory.
Some(_) => None,
None => match book.config.language.default_language() {
None => match book.config.default_language() {
// If book has translations, index.html will be under src/en/ or
// similar.
Some(lang_ident) => Some(lang_ident.clone()),

View File

@ -26,7 +26,7 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
.empty_values(false)
.help("A comma-separated list of directories to add to {n}the crate search path when building tests"))
.arg_from_usage("-l, --language=[language] 'Language to render the compiled book in.{n}\
Only valid if the [languages] table in the config is not empty.{n}\
Only valid if the [language] table in the config is not empty.{n}\
If omitted, builds all translations and provides a menu in the generated output for switching between them.'")
}

View File

@ -25,7 +25,7 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
.arg_from_usage("-o, --open 'Open the compiled book in a web browser'")
.arg_from_usage(
"-l, --language=[language] 'Language to render the compiled book in.{n}\
Only valid if the [languages] table in the config is not empty.{n}\
Only valid if the [language] table in the config is not empty.{n}\
If omitted, builds all translations and provides a menu in the generated output for switching between them.'",
)
}

View File

@ -255,7 +255,7 @@ impl Config {
/// Gets the language configured for a book.
pub fn get_language<I: AsRef<str>>(&self, index: Option<I>) -> Result<Option<String>> {
match self.language.default_language() {
match self.default_language() {
// Languages have been specified, assume directory structure with
// language subfolders.
Some(ref default) => match index {
@ -342,7 +342,7 @@ impl Config {
/// missing in a localization, any links to them will gracefully degrade to
/// the files that exist in this directory.
pub fn get_fallback_src_path(&self) -> PathBuf {
match self.language.default_language() {
match self.default_language() {
// Languages have been specified, assume directory structure with
// language subfolders.
Some(default) => {
@ -358,6 +358,31 @@ impl Config {
}
}
/// If true, mdBook should assume there are subdirectories under src/
/// corresponding to the localizations in the config. If false, src/ is a
/// single directory containing the summary file and the rest.
pub fn has_localized_dir_structure(&self) -> bool {
!self.language.0.is_empty()
}
/// Obtains the default language for this config.
pub fn default_language(&self) -> Option<String> {
if self.has_localized_dir_structure() {
let language_ident = self
.book
.language
.clone()
.expect("Config has [language] table, but `book.language` not was declared");
self.language.0.get(&language_ident).expect(&format!(
"Expected [language.{}] to be declared in book.toml",
language_ident
));
Some(language_ident)
} else {
None
}
}
fn from_legacy(mut table: Value) -> Config {
let mut cfg = Config::default();
@ -455,12 +480,28 @@ impl<'de> Deserialize<'de> for Config {
if !language.0.is_empty() {
let default_languages = language.0.iter().filter(|(_, lang)| lang.default).count();
if default_languages != 1 {
return Err(D::Error::custom(
"If [languages] table is specified, exactly one entry must be set as 'default'",
"If the [language] table is specified, then `book.language` must be declared",
));
}
let language_ident = book.language.clone().unwrap();
if language.0.get(&language_ident).is_none() {
use serde::de::Error;
return Err(D::Error::custom(format!(
"Expected [language.{}] to be declared in book.toml",
language_ident
)));
}
for (ident, language) in language.0.iter() {
if language.name.is_empty() {
use serde::de::Error;
return Err(D::Error::custom(format!(
"`name` property for [language.{}] must be non-empty",
ident
)));
}
}
}
Ok(Config {
@ -492,7 +533,8 @@ impl Serialize for Config {
}
if !self.language.0.is_empty() {
let language_config = Value::try_from(&self.language).expect("should always be serializable");
let language_config =
Value::try_from(&self.language).expect("should always be serializable");
table.insert("language", language_config);
}
@ -543,9 +585,7 @@ pub struct BookConfig {
pub description: Option<String>,
/// Location of the book source relative to the book's root directory.
pub src: PathBuf,
/// Does this book support more than one language? (Deprecated.)
pub multilingual: bool,
/// The main language of the book. (Deprecated.)
/// The main language of the book.
pub language: Option<String>,
}
@ -556,7 +596,6 @@ impl Default for BookConfig {
authors: Vec::new(),
description: None,
src: PathBuf::from("src"),
multilingual: true,
language: Some(String::from("en")),
}
}
@ -830,9 +869,6 @@ pub struct LanguageConfig(pub HashMap<String, Language>);
pub struct Language {
/// Human-readable name of the language.
pub name: String,
/// If true, this language is the default. There must be exactly one default
/// language in the config.
pub default: bool,
/// Localized title of the book.
pub title: Option<String>,
/// The authors of the translation.
@ -841,22 +877,7 @@ pub struct Language {
pub description: Option<String>,
}
impl LanguageConfig {
/// If true, mdBook should assume there are subdirectories under src/
/// corresponding to the localizations in the config. If false, src/ is a
/// single directory containing the summary file and the rest.
pub fn has_localized_dir_structure(&self) -> bool {
self.default_language().is_some()
}
/// Returns the default language specified in the config.
pub fn default_language(&self) -> Option<&String> {
self.0
.iter()
.find(|(_, lang)| lang.default)
.map(|(lang_ident, _)| lang_ident)
}
}
impl LanguageConfig {}
/// Allows you to "update" any arbitrary field in a struct by round-tripping via
/// a `toml::Value`.
@ -923,7 +944,6 @@ mod tests {
[language.en]
name = "English"
default = true
[language.ja]
name = "日本語"
@ -940,7 +960,6 @@ mod tests {
title: Some(String::from("Some Book")),
authors: vec![String::from("Michael-F-Bryan <michaelfbryan@gmail.com>")],
description: Some(String::from("A completely useless book")),
multilingual: true,
src: PathBuf::from("source"),
language: Some(String::from("ja")),
};
@ -981,7 +1000,6 @@ mod tests {
String::from("en"),
Language {
name: String::from("English"),
default: true,
title: None,
description: None,
authors: None,
@ -991,7 +1009,6 @@ mod tests {
String::from("ja"),
Language {
name: String::from("日本語"),
default: false,
title: Some(String::from("なんかの本")),
description: Some(String::from("何の役にも立たない本")),
authors: Some(vec![String::from("Ruin0x11")]),
@ -1354,10 +1371,10 @@ mod tests {
#[test]
#[should_panic(expected = "Invalid configuration file")]
fn validate_default_language() {
fn validate_config_default_language_must_exist_in_languages_table() {
let src = r#"
[language.en]
name = "English"
[language.ja]
name = "日本語"
"#;
Config::from_str(src).unwrap();
@ -1365,15 +1382,12 @@ mod tests {
#[test]
#[should_panic(expected = "Invalid configuration file")]
fn validate_default_language_2() {
fn validate_language_config_must_have_name() {
let src = r#"
[language.en]
name = "English"
default = true
[book]
language = "en"
[language.fr]
name = "Français"
default = true
[language.en]
"#;
Config::from_str(src).unwrap();

View File

@ -10,7 +10,13 @@ use tempfile::Builder as TempFileBuilder;
/// are created.
#[test]
fn base_mdbook_init_should_create_default_content() {
let created_files = vec!["book", "src", "src/en", "src/en/SUMMARY.md", "src/en/chapter_1.md"];
let created_files = vec![
"book",
"src",
"src/en",
"src/en/SUMMARY.md",
"src/en/chapter_1.md",
];
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
for file in &created_files {
@ -28,7 +34,7 @@ fn base_mdbook_init_should_create_default_content() {
let contents = fs::read_to_string(temp.path().join("book.toml")).unwrap();
assert_eq!(
contents,
"[book]\nauthors = []\nlanguage = \"en\"\nmultilingual = true\nsrc = \"src\"\n[language.en]\ndefault = true\nname = \"English\"\n"
"[book]\nauthors = []\nlanguage = \"en\"\nsrc = \"src\"\n[language.en]\nname = \"English\"\n"
);
}
@ -66,7 +72,13 @@ fn run_mdbook_init_should_create_content_from_summary() {
/// files, then call `mdbook init`.
#[test]
fn run_mdbook_init_with_custom_book_and_src_locations() {
let created_files = vec!["out", "in", "in/en", "in/en/SUMMARY.md", "in/en/chapter_1.md"];
let created_files = vec![
"out",
"in",
"in/en",
"in/en/SUMMARY.md",
"in/en/chapter_1.md",
];
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
for file in &created_files {