From 3e493e04d91366bfa75d971db859b69bdd3d005b Mon Sep 17 00:00:00 2001 From: Edward Loveall Date: Thu, 7 Jul 2022 13:16:56 -0400 Subject: [PATCH] Add bullet list style options This change adds a new option `--list-style` so users can choose which character all bulleted lists use for their marker. The [commonmark spec] supports three options for bullet markers: `-`, `*`, `+` which is also now the case here. Previously, when comrak (or cmark-gfm) rendered markdown, it would pick `-` which is now the default. This also adds the ability to optionally provide custom ComrakOptions when testing. If the defaults are fine, you can pass `None`. [commonmark spec]: https://spec.commonmark.org/0.30/#bullet-list-marker --- src/cm.rs | 3 ++- src/lib.rs | 1 + src/main.rs | 16 +++++++++++++- src/parser/mod.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++++- src/tests.rs | 39 ++++++++++++++++++++++++++++----- 5 files changed, 106 insertions(+), 8 deletions(-) diff --git a/src/cm.rs b/src/cm.rs index 9e71dda0..9e562f75 100644 --- a/src/cm.rs +++ b/src/cm.rs @@ -411,7 +411,8 @@ impl<'a, 'o> CommonMarkFormatter<'a, 'o> { if entering { if parent.list_type == ListType::Bullet { - write!(self, "- ").unwrap(); + let bullet = char::from(self.options.render.list_style as u8); + write!(self, "{} ", bullet).unwrap(); } else { self.write_all(&listmarker).unwrap(); } diff --git a/src/lib.rs b/src/lib.rs index 84f271b5..2c70bd04 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -112,6 +112,7 @@ pub use html::Anchorizer; pub use parser::{ parse_document, parse_document_with_broken_link_callback, ComrakExtensionOptions, ComrakOptions, ComrakParseOptions, ComrakPlugins, ComrakRenderOptions, ComrakRenderPlugins, + ListStyleType, }; pub use typed_arena::Arena; diff --git a/src/main.rs b/src/main.rs index 3ff58a63..0a05c9e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,7 @@ extern crate xdg; use comrak::{ Arena, ComrakExtensionOptions, ComrakOptions, ComrakParseOptions, ComrakPlugins, - ComrakRenderOptions, + ComrakRenderOptions, ListStyleType, }; use comrak::adapters::SyntaxHighlighterAdapter; @@ -161,6 +161,15 @@ if the file does not exist.\ .value_name("THEME") .help("Syntax highlighting for codefence blocks. Choose a theme or 'none' for disabling.") .default_value("base16-ocean.dark"), + ) + .arg( + clap::Arg::with_name("list-style") + .long("list-style") + .takes_value(true) + .possible_values(&["dash", "plus", "star"]) + .default_value("dash") + .value_name("LIST_STYLE") + .help("Specify bullet character for lists (-, +, *) in CommonMark ouput"), ); let mut matches = app.clone().get_matches(); @@ -220,6 +229,11 @@ if the file does not exist.\ .unwrap_or(0), unsafe_: matches.is_present("unsafe"), escape: matches.is_present("escape"), + list_style: matches + .value_of("list-style") + .unwrap_or("dash") + .parse::() + .expect("unknown list style"), }, }; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index ae172204..260ad481 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -18,7 +18,7 @@ use std::cmp::min; use std::collections::HashMap; use std::fmt::{Debug, Formatter}; use std::mem; -use std::str; +use std::str::{self, FromStr}; use strings; use typed_arena::Arena; @@ -437,6 +437,29 @@ pub struct ComrakRenderOptions { /// "

<i>italic text</i>

\n"); /// ``` pub escape: bool, + + /// Set the type of [bullet list marker](https://spec.commonmark.org/0.30/#bullet-list-marker) to use. Options are: + /// + /// * `ListStyleType::Dash` to use `-` (default) + /// * `ListStyleType::Plus` to use `+` + /// * `ListStyleType::Star` to use `*` + /// + /// ```rust + /// # use comrak::{markdown_to_commonmark, ComrakOptions, ListStyleType}; + /// let mut options = ComrakOptions::default(); + /// let input = "- one\n- two\n- three"; + /// assert_eq!(markdown_to_commonmark(input, &options), + /// "- one\n- two\n- three\n"); // default is Dash + /// + /// options.render.list_style = ListStyleType::Plus; + /// assert_eq!(markdown_to_commonmark(input, &options), + /// "+ one\n+ two\n+ three\n"); + /// + /// options.render.list_style = ListStyleType::Star; + /// assert_eq!(markdown_to_commonmark(input, &options), + /// "* one\n* two\n* three\n"); + /// ``` + pub list_style: ListStyleType, } #[derive(Default, Debug)] @@ -1891,3 +1914,33 @@ pub enum AutolinkType { URI, Email, } + +#[derive(Debug, Clone, Copy)] +/// Options for bulleted list redering in markdown. See `link_style` in [ComrakRenderOptions] for more details. +pub enum ListStyleType { + /// The `-` character + Dash = 45, + /// The `+` character + Plus = 43, + /// The `*` character + Star = 42, +} + +impl Default for ListStyleType { + fn default() -> Self { + ListStyleType::Dash + } +} + +impl FromStr for ListStyleType { + type Err = (); + + fn from_str(input: &str) -> Result { + match input { + "dash" => Ok(ListStyleType::Dash), + "plus" => Ok(ListStyleType::Plus), + "star" => Ok(ListStyleType::Star), + _ => Err(()), + } + } +} diff --git a/src/tests.rs b/src/tests.rs index d9ab37b8..24cf2ca7 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -37,6 +37,7 @@ fn fuzz_doesnt_crash(md: String) { width: 80, unsafe_: true, escape: false, + list_style: ::ListStyleType::Dash, }, }; @@ -62,12 +63,12 @@ fn compare_strs(output: &str, expected: &str, kind: &str) { } #[track_caller] -fn commonmark(input: &str, expected: &str) { +fn commonmark(input: &str, expected: &str, opts: Option<&::ComrakOptions>) { let arena = ::Arena::new(); - let mut options = ::ComrakOptions::default(); - options.render.width = 72; + let defaults = ::ComrakOptions::default(); + let options = opts.unwrap_or(&defaults); - let root = ::parse_document(&arena, input, &options); + let root = ::parse_document(&arena, input, options); let mut output = vec![]; cm::format_document(root, &options, &mut output).unwrap(); compare_strs(&String::from_utf8(output).unwrap(), expected, "regular"); @@ -253,6 +254,31 @@ fn lists() { ); } +#[test] +fn markdown_list_bullets() { + let dash = concat!("- a\n"); + let plus = concat!("+ a\n"); + let star = concat!("* a\n"); + let mut dash_opts = ::ComrakOptions::default(); + dash_opts.render.list_style = ::ListStyleType::Dash; + let mut plus_opts = ::ComrakOptions::default(); + plus_opts.render.list_style = ::ListStyleType::Plus; + let mut star_opts = ::ComrakOptions::default(); + star_opts.render.list_style = ::ListStyleType::Star; + + commonmark(dash, dash, Some(&dash_opts)); + commonmark(plus, dash, Some(&dash_opts)); + commonmark(star, dash, Some(&dash_opts)); + + commonmark(dash, plus, Some(&plus_opts)); + commonmark(plus, plus, Some(&plus_opts)); + commonmark(star, plus, Some(&plus_opts)); + + commonmark(dash, star, Some(&star_opts)); + commonmark(plus, star, Some(&star_opts)); + commonmark(star, star, Some(&star_opts)); +} + #[test] fn thematic_breaks() { html( @@ -263,6 +289,8 @@ fn thematic_breaks() { #[test] fn width_breaks() { + let mut options = ::ComrakOptions::default(); + options.render.width = 72; let input = concat!( "this should break because it has breakable characters. break right here newline\n", "\n", @@ -279,7 +307,7 @@ fn width_breaks() { "a-long-line-that-won't-break-because-there-is-no-character-it-can-break-on\n" ); - commonmark(input, output); + commonmark(input, output, Some(&options)); } #[test] @@ -1272,6 +1300,7 @@ fn exercise_full_api<'a>() { width: 123456, unsafe_: false, escape: false, + list_style: ::ListStyleType::Dash, }, };