feat(cli)!: preserve source list markers instead of normalizing
BREAKING CHANGE: the --bullet option is removed; list markers now follow the source instead of being normalized.
This commit is contained in:
@@ -16,12 +16,9 @@ Options:
|
||||
--wrap MODE keep paragraph line breaks as written (keep, the
|
||||
default), join each paragraph onto one line (no), or
|
||||
reflow paragraphs to a column width (a number)
|
||||
--bullet MARKER
|
||||
write bullet list items with "-" (the default),
|
||||
"*", or "+"
|
||||
--ordered STYLE
|
||||
number ordered list items counting up from the first
|
||||
item's number (increment, the default) or with "1."
|
||||
item's number (increment, the default) or with "1"
|
||||
for every item after the first (one)
|
||||
-h, --help print this help and exit
|
||||
-v, --version
|
||||
|
||||
-16
@@ -9,7 +9,6 @@ use std::process::ExitCode;
|
||||
|
||||
use lexopt::prelude::*;
|
||||
|
||||
use render::Bullet;
|
||||
use render::Ordered;
|
||||
use render::Style;
|
||||
use render::Wrap;
|
||||
@@ -26,9 +25,6 @@ Options:
|
||||
--wrap MODE keep paragraph line breaks as written (keep, the
|
||||
default), join each paragraph onto one line (no), or
|
||||
reflow paragraphs to a column width (a number)
|
||||
--bullet MARKER
|
||||
write bullet list items with \"-\" (the default),
|
||||
\"*\", or \"+\"
|
||||
--ordered STYLE
|
||||
number ordered list items counting up from the first
|
||||
item's number (increment, the default) or with \"1.\"
|
||||
@@ -90,9 +86,6 @@ fn parse_args() -> Result<Option<Cli>, lexopt::Error> {
|
||||
Long("wrap") => {
|
||||
style.wrap = parser.value()?.parse_with(parse_wrap)?;
|
||||
}
|
||||
Long("bullet") => {
|
||||
style.bullet = parser.value()?.parse_with(parse_bullet)?;
|
||||
}
|
||||
Long("ordered") => {
|
||||
style.ordered = parser.value()?.parse_with(parse_ordered)?;
|
||||
}
|
||||
@@ -118,15 +111,6 @@ fn parse_wrap(value: &str) -> Result<Wrap, String> {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_bullet(value: &str) -> Result<Bullet, String> {
|
||||
match value {
|
||||
"-" => Ok(Bullet::Dash),
|
||||
"*" => Ok(Bullet::Star),
|
||||
"+" => Ok(Bullet::Plus),
|
||||
_ => Err(String::from(r#"expected "-", "*", or "+""#)),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_ordered(value: &str) -> Result<Ordered, String> {
|
||||
match value {
|
||||
"increment" => Ok(Ordered::Increment),
|
||||
|
||||
+45
-48
@@ -22,25 +22,6 @@ pub(crate) enum Wrap {
|
||||
Columns(usize),
|
||||
}
|
||||
|
||||
/// Marker written before every unordered list item.
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub(crate) enum Bullet {
|
||||
#[default]
|
||||
Dash,
|
||||
Star,
|
||||
Plus,
|
||||
}
|
||||
|
||||
impl Bullet {
|
||||
fn marker(self) -> &'static str {
|
||||
match self {
|
||||
Bullet::Dash => "- ",
|
||||
Bullet::Star => "* ",
|
||||
Bullet::Plus => "+ ",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Numbering written on ordered list items after the first. The
|
||||
/// first item always keeps its source number, which sets the list's
|
||||
/// start.
|
||||
@@ -54,13 +35,12 @@ pub(crate) enum Ordered {
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub(crate) struct Style {
|
||||
pub(crate) wrap: Wrap,
|
||||
pub(crate) bullet: Bullet,
|
||||
pub(crate) ordered: Ordered,
|
||||
}
|
||||
|
||||
/// Parses `input` as `CommonMark` (plus tables, footnotes, strikethrough,
|
||||
/// task lists, and YAML metadata blocks) and re-emits it in one
|
||||
/// consistent style: ATX headings, uniform list markers per `style`,
|
||||
/// consistent style: ATX headings, source list markers preserved,
|
||||
/// `*` emphasis, fenced code blocks, and padded tables.
|
||||
///
|
||||
/// Reference-style links keep their labels. The parser reports the
|
||||
@@ -384,7 +364,7 @@ impl Renderer {
|
||||
first_item: true,
|
||||
});
|
||||
}
|
||||
Tag::Item => self.start_item(),
|
||||
Tag::Item => self.start_item(&source[range.clone()]),
|
||||
Tag::FootnoteDefinition(name) => {
|
||||
self.block_start();
|
||||
self.push_prefix(format!("[^{name}]: "), " ".to_owned());
|
||||
@@ -484,18 +464,18 @@ impl Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
fn start_item(&mut self) {
|
||||
fn start_item(&mut self, item_source: &str) {
|
||||
let list = self.lists.last_mut().expect("item event outside a list");
|
||||
let marker = match list.next_number.as_mut() {
|
||||
Some(number) => {
|
||||
let marker = format!("{number}. ");
|
||||
let marker = format!("{number}{} ", ordered_delimiter(item_source));
|
||||
*number = match self.style.ordered {
|
||||
Ordered::Increment => *number + 1,
|
||||
Ordered::One => 1,
|
||||
};
|
||||
marker
|
||||
}
|
||||
None => String::from(self.style.bullet.marker()),
|
||||
None => format!("{} ", bullet_marker(item_source)),
|
||||
};
|
||||
let separate = list.loose && !list.first_item;
|
||||
list.first_item = false;
|
||||
@@ -1096,6 +1076,30 @@ fn compute_list_looseness(events: &[(Event<'_>, Range<usize>)]) -> Vec<bool> {
|
||||
loose
|
||||
}
|
||||
|
||||
/// A marker change is what separates two adjacent lists, so the source
|
||||
/// marker is read and preserved rather than normalized, which would
|
||||
/// risk merging adjacent lists.
|
||||
fn bullet_marker(item_source: &str) -> char {
|
||||
item_source
|
||||
.trim_start()
|
||||
.chars()
|
||||
.next()
|
||||
.filter(|c| matches!(c, '-' | '*' | '+'))
|
||||
.unwrap_or('-')
|
||||
}
|
||||
|
||||
/// The delimiter (`.` or `)`) after an ordered item's number, read from
|
||||
/// its source for the same reason as [`bullet_marker`].
|
||||
fn ordered_delimiter(item_source: &str) -> char {
|
||||
item_source
|
||||
.trim_start()
|
||||
.trim_start_matches(|c: char| c.is_ascii_digit())
|
||||
.chars()
|
||||
.next()
|
||||
.filter(|c| matches!(c, '.' | ')'))
|
||||
.unwrap_or('.')
|
||||
}
|
||||
|
||||
/// Longest run of consecutive backticks in `s`, used to size fences and
|
||||
/// inline code delimiters so they cannot collide with the content.
|
||||
fn max_backtick_run(s: &str) -> usize {
|
||||
@@ -1460,7 +1464,6 @@ fn delimiter_line(widths: &[usize], alignments: &[Alignment]) -> String {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Bullet;
|
||||
use super::Ordered;
|
||||
use super::Style;
|
||||
use super::Wrap;
|
||||
@@ -1497,29 +1500,23 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bullets_normalize_to_dashes() {
|
||||
assert_eq!(format_markdown("* a\n+ b\n"), "- a\n\n- b\n");
|
||||
assert_eq!(format_markdown("* a\n* b\n"), "- a\n- b\n");
|
||||
fn bullet_markers_are_preserved() {
|
||||
assert_eq!(format_markdown("* a\n* b\n"), "* a\n* b\n");
|
||||
assert_eq!(format_markdown("+ a\n+ b\n"), "+ a\n+ b\n");
|
||||
assert_eq!(format_markdown("- [ ] todo\n"), "- [ ] todo\n");
|
||||
assert_eq!(format_markdown("* a\n + b\n"), "* a\n + b\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bullet_marker_is_selectable() {
|
||||
let star = Style {
|
||||
bullet: Bullet::Star,
|
||||
..Style::default()
|
||||
};
|
||||
let plus = Style {
|
||||
bullet: Bullet::Plus,
|
||||
..Style::default()
|
||||
};
|
||||
assert_eq!(
|
||||
super::format_markdown("- a\n + b\n", star),
|
||||
"* a\n * b\n"
|
||||
);
|
||||
assert_eq!(
|
||||
super::format_markdown("* [ ] todo\n", plus),
|
||||
"+ [ ] todo\n"
|
||||
);
|
||||
fn adjacent_lists_with_distinct_markers_stay_separate() {
|
||||
assert_reparses_same("- a\n\n* b\n\n- c\n");
|
||||
assert_reparses_same("1. a\n\n1) b\n");
|
||||
assert_eq!(format_markdown("* a\n+ b\n"), "* a\n\n+ b\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ordered_delimiters_are_preserved() {
|
||||
assert_eq!(format_markdown("1) a\n2) b\n"), "1) a\n2) b\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1605,7 +1602,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn nested_tight_list() {
|
||||
assert_eq!(format_markdown("- a\n * b\n- c\n"), "- a\n - b\n- c\n");
|
||||
assert_eq!(format_markdown("- a\n * b\n- c\n"), "- a\n * b\n- c\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1665,7 +1662,7 @@ mod tests {
|
||||
assert_eq!(format_markdown("~~gone~~\n"), "~~gone~~\n");
|
||||
assert_eq!(
|
||||
format_markdown("* [X] done\n* [ ] todo\n"),
|
||||
"- [x] done\n- [ ] todo\n"
|
||||
"* [x] done\n* [ ] todo\n"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user