Repository: rust-lang/mdBook (language: rust)

Issue:
Rename `curly-quotes` config option to `smart-punctuation`
See [here](https://daringfireball.net/projects/smartypants/). What `pulldown-cmark` does is replace quotes **but also dashes and ellipses**.

Basically this turns something like:
```
"He took the boat Paris---Rome ... When he arrived he exclamated -- instantly -- 'Heureka!'."
```

Into:

“He took the boat Paris—Rome … When he arrived he exclamated – instantly – ‘Heureka, I found a secret waterway!’.”




Discussion / hints:
Hi @virtualritz,

You might already know this, but for completeness: the [`curly-quotes` option](https://rust-lang.github.io/mdBook/format/configuration/renderers.html) does the last part. However, it doesn't localize the quotes based on the language and it doesn't handle dashes either.
I updated this ticket. The feature is actually fully there as of lately but wrongly named now.

And w/o support for foreign quotes -- I opened [a ticket in `pulldown-cmark`](https://github.com/raphlinus/pulldown-cmark/issues/802) for this.

Fix patch:
diff --git a/guide/src/format/configuration/renderers.md b/guide/src/format/configuration/renderers.md
index 7e7430141a..3ff6b3a092 100644
--- a/guide/src/format/configuration/renderers.md
+++ b/guide/src/format/configuration/renderers.md
@@ -97,7 +97,7 @@ description = "The example book covers examples."
 theme = "my-theme"
 default-theme = "light"
 preferred-dark-theme = "navy"
-curly-quotes = true
+smart-punctuation = true
 mathjax-support = false
 copy-fonts = true
 additional-css = ["custom.css", "custom2.css"]
@@ -122,8 +122,10 @@ The following configuration options are available:
   the browser requests the dark version of the site via the
   ['prefers-color-scheme'](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme)
   CSS media query. Defaults to `navy`.
-- **curly-quotes:** Convert straight quotes to curly quotes, except for those
-  that occur in code blocks and code spans. Defaults to `false`.
+- **smart-punctuation:** Converts quotes to curly quotes, `...` to `…`, `--` to en-dash, and `---` to em-dash.
+  See [Smart Punctuation].
+  Defaults to `false`.
+- **curly-quotes:** Deprecated alias for `smart-punctuation`.
 - **mathjax-support:** Adds support for [MathJax](../mathjax.md). Defaults to
   `false`.
 - **copy-fonts:** (**Deprecated**) If `true` (the default), mdBook uses its built-in fonts which are copied to the output directory.
diff --git a/guide/src/format/markdown.md b/guide/src/format/markdown.md
index 7c9b22bb25..f837d54c9c 100644
--- a/guide/src/format/markdown.md
+++ b/guide/src/format/markdown.md
@@ -214,12 +214,12 @@ characters:
 So, no need to manually enter those Unicode characters!
 
 This feature is disabled by default.
-To enable it, see the [`output.html.curly-quotes`] config option.
+To enable it, see the [`output.html.smart-punctuation`] config option.
 
 [strikethrough]: https://github.github.com/gfm/#strikethrough-extension-
 [tables]: https://github.github.com/gfm/#tables-extension-
 [task list extension]: https://github.github.com/gfm/#task-list-items-extension-
-[`output.html.curly-quotes`]: configuration/renderers.md#html-renderer-options
+[`output.html.smart-punctuation`]: configuration/renderers.md#html-renderer-options
 
 ### Heading attributes
 
diff --git a/src/book/mod.rs b/src/book/mod.rs
index 166f6511bf..c0ab8a5461 100644
--- a/src/book/mod.rs
+++ b/src/book/mod.rs
@@ -70,18 +70,24 @@ impl MDBook {
 
         config.update_from_env();
 
-        if config
-            .html_config()
-            .map_or(false, |html| html.google_analytics.is_some())
-        {
-            warn!(
-                "The output.html.google-analytics field has been deprecated; \
-                 it will be removed in a future release.\n\
-                 Consider placing the appropriate site tag code into the \
-                 theme/head.hbs file instead.\n\
-                 The tracking code may be found in the Google Analytics Admin page.\n\
-               "
-            );
+        if let Some(html_config) = config.html_config() {
+            if html_config.google_analytics.is_some() {
+                warn!(
+                    "The output.html.google-analytics field has been deprecated; \
+                     it will be removed in a future release.\n\
+                     Consider placing the appropriate site tag code into the \
+                     theme/head.hbs file instead.\n\
+                     The tracking code may be found in the Google Analytics Admin page.\n\
+                   "
+                );
+            }
+            if html_config.curly_quotes {
+                warn!(
+                    "The output.html.curly-quotes field has been renamed to \
+                     output.html.smart-punctuation.\n\
+                     Use the new name in book.toml to remove this warning."
+                );
+            }
         }
 
         if log_enabled!(log::Level::Trace) {
diff --git a/src/config.rs b/src/config.rs
index 67b1a700e8..4840c03047 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -526,7 +526,9 @@ pub struct HtmlConfig {
     /// The theme to use if the browser requests the dark version of the site.
     /// Defaults to 'navy'.
     pub preferred_dark_theme: Option<String>,
-    /// Use "smart quotes" instead of the usual `"` character.
+    /// Supports smart quotes, apostrophes, ellipsis, en-dash, and em-dash.
+    pub smart_punctuation: bool,
+    /// Deprecated alias for `smart_punctuation`.
     pub curly_quotes: bool,
     /// Should mathjax be enabled?
     pub mathjax_support: bool,
@@ -590,6 +592,7 @@ impl Default for HtmlConfig {
             theme: None,
             default_theme: None,
             preferred_dark_theme: None,
+            smart_punctuation: false,
             curly_quotes: false,
             mathjax_support: false,
             copy_fonts: true,
@@ -623,6 +626,11 @@ impl HtmlConfig {
             None => root.join("theme"),
         }
     }
+
+    /// Returns `true` if smart punctuation is enabled.
+    pub fn smart_punctuation(&self) -> bool {
+        self.smart_punctuation || self.curly_quotes
+    }
 }
 
 /// Configuration for how to render the print icon, print.html, and print.css.
@@ -798,7 +806,7 @@ mod tests {
         [output.html]
         theme = "./themedir"
         default-theme = "rust"
-        curly-quotes = true
+        smart-punctuation = true
         google-analytics = "123456"
         additional-css = ["./foo/bar/baz.css"]
         git-repository-url = "https://foo.com/"
@@ -845,7 +853,7 @@ mod tests {
             runnable: true,
         };
         let html_should_be = HtmlConfig {
-            curly_quotes: true,
+            smart_punctuation: true,
             google_analytics: Some(String::from("123456")),
             additional_css: vec![PathBuf::from("./foo/bar/baz.css")],
             theme: Some(PathBuf::from("./themedir")),
@@ -1025,7 +1033,7 @@ mod tests {
         [output.html]
         destination = "my-book" # the output files will be generated in `root/my-book` instead of `root/book`
         theme = "my-theme"
-        curly-quotes = true
+        smart-punctuation = true
         google-analytics = "123456"
         additional-css = ["custom.css", "custom2.css"]
         additional-js = ["custom.js"]
@@ -1050,7 +1058,7 @@ mod tests {
 
         let html_should_be = HtmlConfig {
             theme: Some(PathBuf::from("my-theme")),
-            curly_quotes: true,
+            smart_punctuation: true,
             google_analytics: Some(String::from("123456")),
             additional_css: vec![PathBuf::from("custom.css"), PathBuf::from("custom2.css")],
             additional_js: vec![PathBuf::from("custom.js")],
@@ -1320,4 +1328,37 @@ mod tests {
         assert!(html_config.print.enable);
         assert!(!html_config.print.page_break);
     }
+
+    #[test]
+    fn curly_quotes_or_smart_punctuation() {
+        let src = r#"
+        [book]
+        title = "mdBook Documentation"
+
+        [output.html]
+        smart-punctuation = true
+        "#;
+        let config = Config::from_str(src).unwrap();
+        assert_eq!(config.html_config().unwrap().smart_punctuation(), true);
+
+        let src = r#"
+        [book]
+        title = "mdBook Documentation"
+
+        [output.html]
+        curly-quotes = true
+        "#;
+        let config = Config::from_str(src).unwrap();
+        assert_eq!(config.html_config().unwrap().smart_punctuation(), true);
+
+        let src = r#"
+        [book]
+        title = "mdBook Documentation"
+        "#;
+        let config = Config::from_str(src).unwrap();
+        assert_eq!(
+            config.html_config().unwrap_or_default().smart_punctuation(),
+            false
+        );
+    }
 }
diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs
index b706108e64..30d4c84b8c 100644
--- a/src/renderer/html_handlebars/hbs_renderer.rs
+++ b/src/renderer/html_handlebars/hbs_renderer.rs
@@ -54,10 +54,13 @@ impl HtmlHandlebars {
                 .insert("git_repository_edit_url".to_owned(), json!(edit_url));
         }
 
-        let content = utils::render_markdown(&ch.content, ctx.html_config.curly_quotes);
+        let content = utils::render_markdown(&ch.content, ctx.html_config.smart_punctuation());
 
-        let fixed_content =
-            utils::render_markdown_with_path(&ch.content, ctx.html_config.curly_quotes, Some(path));
+        let fixed_content = utils::render_markdown_with_path(
+            &ch.content,
+            ctx.html_config.smart_punctuation(),
+            Some(path),
+        );
         if !ctx.is_index && ctx.html_config.print.page_break {
             // Add page break between chapters
             // See https://developer.mozilla.org/en-US/docs/Web/CSS/break-before and https://developer.mozilla.org/en-US/docs/Web/CSS/page-break-before
@@ -164,7 +167,8 @@ impl HtmlHandlebars {
                     .to_string()
             }
         };
-        let html_content_404 = utils::render_markdown(&content_404, html_config.curly_quotes);
+        let html_content_404 =
+            utils::render_markdown(&content_404, html_config.smart_punctuation());
 
         let mut data_404 = data.clone();
         let base_url = if let Some(site_url) = &html_config.site_url {
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
index c0be203044..2b17cc7d84 100644
--- a/src/utils/mod.rs
+++ b/src/utils/mod.rs
@@ -190,26 +190,30 @@ fn adjust_links<'a>(event: Event<'a>, path: Option<&Path>) -> Event<'a> {
 }
 
 /// Wrapper around the pulldown-cmark parser for rendering markdown to HTML.
-pub fn render_markdown(text: &str, curly_quotes: bool) -> String {
-    render_markdown_with_path(text, curly_quotes, None)
+pub fn render_markdown(text: &str, smart_punctuation: bool) -> String {
+    render_markdown_with_path(text, smart_punctuation, None)
 }
 
-pub fn new_cmark_parser(text: &str, curly_quotes: bool) -> Parser<'_> {
+pub fn new_cmark_parser(text: &str, smart_punctuation: bool) -> Parser<'_> {
     let mut opts = Options::empty();
     opts.insert(Options::ENABLE_TABLES);
     opts.insert(Options::ENABLE_FOOTNOTES);
     opts.insert(Options::ENABLE_STRIKETHROUGH);
     opts.insert(Options::ENABLE_TASKLISTS);
     opts.insert(Options::ENABLE_HEADING_ATTRIBUTES);
-    if curly_quotes {
+    if smart_punctuation {
         opts.insert(Options::ENABLE_SMART_PUNCTUATION);
     }
     Parser::new_ext(text, opts)
 }
 
-pub fn render_markdown_with_path(text: &str, curly_quotes: bool, path: Option<&Path>) -> String {
+pub fn render_markdown_with_path(
+    text: &str,
+    smart_punctuation: bool,
+    path: Option<&Path>,
+) -> String {
     let mut s = String::with_capacity(text.len() * 3 / 2);
-    let p = new_cmark_parser(text, curly_quotes);
+    let p = new_cmark_parser(text, smart_punctuation);
     let events = p
         .map(clean_codeblock_headers)
         .map(|event| adjust_links(event, path))


Test patch: