-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a footnote transformer, which just assigns sequential numbers to footnotes starting at `1`. A note on `drain_pending_footnotes`: in this change, we transform the `&'md String`s into full `String`s during the drain, so that we can easily use them as a sort key. We could have done something more clever, like creating an enum that captures either the `&md String` or the `usize` of the transformation, and then compares those; but that felt like more effort than we really need, especially given that the current implementation of `FootnoteTransformer::write` eventually String-ifies the `usize` anyway; this just does it ahead of time. In practice, we only expect to see a handful of footnotes, and each of them will only be a handful of chars long (even 10,000 footnotes would only be 5-chars long at their longest!) Resolves #89.
- Loading branch information
Showing
10 changed files
with
242 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
use crate::output::{Output, SimpleWrite}; | ||
use std::collections::HashMap; | ||
|
||
pub struct FootnoteTransformer<'md> { | ||
mappings: Option<HashMap<&'md str, usize>>, | ||
} | ||
|
||
pub struct FootnoteTransformerToString<'a, 'md> { | ||
transformer: &'a mut FootnoteTransformer<'md>, | ||
scratch: Output<String>, | ||
} | ||
|
||
impl<'md> FootnoteTransformer<'md> { | ||
pub fn new(active: bool) -> Self { | ||
Self { | ||
mappings: if active { Some(HashMap::default()) } else { None }, | ||
} | ||
} | ||
|
||
pub fn write<W>(&mut self, out: &mut Output<W>, label: &'md str) | ||
where | ||
W: SimpleWrite, | ||
{ | ||
match &mut self.mappings { | ||
None => out.write_str(label), | ||
Some(mapping) => { | ||
let current_mapping_len = mapping.len(); | ||
let num = mapping.entry(label).or_insert(current_mapping_len + 1); | ||
out.write_str(&num.to_string()); | ||
} | ||
} | ||
} | ||
|
||
pub fn new_to_stringer<'a>(&'a mut self) -> FootnoteTransformerToString<'a, 'md> { | ||
FootnoteTransformerToString::new(self) | ||
} | ||
} | ||
|
||
impl<'a, 'md> FootnoteTransformerToString<'a, 'md> { | ||
pub fn transform(&mut self, label: &'md str) -> String { | ||
let len = self.transformed_label_len(label); | ||
_ = self.scratch.replace_underlying(String::with_capacity(len)).unwrap(); | ||
self.transformer.write(&mut self.scratch, label); | ||
self.scratch.take_underlying().unwrap() | ||
} | ||
|
||
fn new(transformer: &'a mut FootnoteTransformer<'md>) -> Self { | ||
Self { | ||
transformer, | ||
scratch: Output::new(String::new()), | ||
} | ||
} | ||
|
||
fn transformed_label_len(&mut self, label: &str) -> usize { | ||
match &mut self.transformer.mappings { | ||
None => label.len(), | ||
Some(mapping) => { | ||
let renumbered_to = mapping.get(label).copied().unwrap_or(mapping.len() + 1); | ||
let renumbered_log10 = renumbered_to.checked_ilog10().unwrap_or(0); | ||
// Try to convert the u32 to usize; if we can't, just guess a length of 3. | ||
// That should be plenty! | ||
usize::try_from(renumbered_log10 + 1).unwrap_or(3) | ||
} | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use crate::footnote_transform::FootnoteTransformer; | ||
use crate::output::Output; | ||
|
||
#[test] | ||
fn inactive() { | ||
let mut transformer = FootnoteTransformer::new(false); | ||
check("abc", &mut transformer, "abc", 3); | ||
check("1", &mut transformer, "1", 1); | ||
check("3", &mut transformer, "3", 1); | ||
|
||
// remember the old value | ||
check("1", &mut transformer, "1", 1); | ||
} | ||
|
||
#[test] | ||
fn active() { | ||
let mut transformer = FootnoteTransformer::new(true); | ||
check("abc", &mut transformer, "1", 1); | ||
check("1", &mut transformer, "2", 1); | ||
check("3", &mut transformer, "3", 1); | ||
|
||
// remember the old value | ||
check("1", &mut transformer, "2", 1); | ||
} | ||
|
||
#[test] | ||
fn active_with_ten_footnotes() { | ||
let mut transformer = FootnoteTransformer::new(true); | ||
|
||
// write nine labels; we don't care about the results | ||
let nine_labels: Vec<_> = (1..10).map(|i| format!("footnote-{i}")).collect(); | ||
for label in &nine_labels { | ||
transformer.write(&mut Output::new(String::new()), &label); | ||
} | ||
|
||
// the tenth label should remap to "10" with an expected len of 2 | ||
check("z", &mut transformer, "10", 2); | ||
} | ||
|
||
fn check<'a>( | ||
input: &'a str, | ||
transformer: &mut FootnoteTransformer<'a>, | ||
expect: &str, | ||
expect_transformed_len: usize, | ||
) { | ||
// len-calculation should work before and after we first officially see the label. So, try | ||
// this once before transformer.write, and then later we'll try it again. | ||
assert_eq!( | ||
transformer.new_to_stringer().transformed_label_len(input), | ||
expect_transformed_len | ||
); | ||
|
||
let mut output = Output::new(String::with_capacity(expect.len())); | ||
transformer.write(&mut output, input); | ||
let actual = output.take_underlying().unwrap(); | ||
assert_eq!(&actual, expect); | ||
|
||
assert_eq!( | ||
transformer.new_to_stringer().transformed_label_len(input), | ||
expect_transformed_len | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.