Skip to content

Commit a8eb0a6

Browse files
committed
use linkedhashmap to preserve key order
1 parent e5a4cb8 commit a8eb0a6

File tree

5 files changed

+34
-31
lines changed

5 files changed

+34
-31
lines changed

crates/quarto-markdown-pandoc/src/pandoc/meta.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::pandoc::block::Blocks;
77
use crate::pandoc::inline::Inlines;
88
use crate::readers;
99
use crate::{pandoc::RawBlock, utils::output::VerboseOutput};
10-
use std::collections::HashMap;
10+
use hashlink::LinkedHashMap;
1111
use std::{io, mem};
1212
use yaml_rust2::parser::{Event, MarkedEventReceiver, Parser};
1313

@@ -20,16 +20,16 @@ pub enum MetaValue {
2020
MetaInlines(Inlines),
2121
MetaBlocks(Blocks),
2222
MetaList(Vec<MetaValue>),
23-
MetaMap(HashMap<String, MetaValue>),
23+
MetaMap(LinkedHashMap<String, MetaValue>),
2424
}
2525

2626
impl Default for MetaValue {
2727
fn default() -> Self {
28-
MetaValue::MetaMap(HashMap::new())
28+
MetaValue::MetaMap(LinkedHashMap::new())
2929
}
3030
}
3131

32-
pub type Meta = HashMap<String, MetaValue>;
32+
pub type Meta = LinkedHashMap<String, MetaValue>;
3333

3434
fn extract_between_delimiters(input: &str) -> Option<&str> {
3535
let parts: Vec<&str> = input.split("---").collect();
@@ -41,7 +41,7 @@ fn extract_between_delimiters(input: &str) -> Option<&str> {
4141
}
4242

4343
enum ContextFrame {
44-
Map(HashMap<String, MetaValue>, Option<String>),
44+
Map(LinkedHashMap<String, MetaValue>, Option<String>),
4545
List(Vec<MetaValue>),
4646
Root,
4747
}
@@ -100,7 +100,7 @@ impl MarkedEventReceiver for YamlEventHandler {
100100
match ev {
101101
Event::StreamStart | Event::DocumentStart => {}
102102
Event::MappingStart(..) => {
103-
self.stack.push(ContextFrame::Map(HashMap::new(), None));
103+
self.stack.push(ContextFrame::Map(LinkedHashMap::new(), None));
104104
}
105105
Event::MappingEnd => {
106106
if let Some(ContextFrame::Map(map, _)) = self.stack.pop() {

crates/quarto-markdown-pandoc/src/readers/json.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@
44
*/
55

66
use crate::pandoc::{
7-
Attr, Block, BlockQuote, BulletList, Caption, Citation, CitationMode, Cite, Code, CodeBlock,
8-
DefinitionList, Div, Emph, Figure, Header, HorizontalRule, Image, Inline, Inlines,
9-
LineBlock, Link, ListAttributes, ListNumberDelim, ListNumberStyle, Math, MathType,
10-
Meta, MetaValue, Note, OrderedList, Pandoc, Paragraph, Plain, Quoted, QuoteType,
11-
RawBlock, RawInline, SmallCaps, SoftBreak, Space, Span, Str, Strikeout, Strong,
7+
Attr, Block, BlockQuote, BulletList, Caption, Citation, CitationMode, Cite, Code, CodeBlock,
8+
DefinitionList, Div, Emph, Figure, Header, HorizontalRule, Image, Inline, Inlines,
9+
LineBlock, Link, ListAttributes, ListNumberDelim, ListNumberStyle, Math, MathType,
10+
Meta, MetaValue, Note, OrderedList, Pandoc, Paragraph, Plain, Quoted, QuoteType,
11+
RawBlock, RawInline, SmallCaps, SoftBreak, Space, Span, Str, Strikeout, Strong,
1212
Subscript, Superscript, Underline
1313
};
1414
use crate::pandoc::block::MetaBlock;
1515
use crate::pandoc::location::{Range, Location, SourceInfo};
16+
use hashlink::LinkedHashMap;
1617
use serde_json::Value;
17-
use std::collections::HashMap;
1818

1919
#[derive(Debug)]
2020
pub enum JsonReadError {
@@ -576,12 +576,12 @@ fn read_block(value: &Value) -> Result<Block> {
576576

577577
fn read_meta(value: &Value) -> Result<Meta> {
578578
let obj = value.as_object().ok_or_else(|| JsonReadError::InvalidType("Expected object for Meta".to_string()))?;
579-
580-
let mut meta = HashMap::new();
579+
580+
let mut meta = LinkedHashMap::new();
581581
for (key, val) in obj {
582582
meta.insert(key.clone(), read_meta_value(val)?);
583583
}
584-
584+
585585
Ok(meta)
586586
}
587587

@@ -617,7 +617,7 @@ fn read_meta_value(value: &Value) -> Result<MetaValue> {
617617
"MetaMap" => {
618618
let c = obj.get("c").ok_or_else(|| JsonReadError::MissingField("c".to_string()))?;
619619
let arr = c.as_array().ok_or_else(|| JsonReadError::InvalidType("MetaMap content must be array".to_string()))?;
620-
let mut map = HashMap::new();
620+
let mut map = LinkedHashMap::new();
621621
for item in arr {
622622
let kv_arr = item.as_array().ok_or_else(|| JsonReadError::InvalidType("MetaMap item must be array".to_string()))?;
623623
if kv_arr.len() != 2 {

crates/quarto-markdown-pandoc/src/writers/qmd.rs

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -198,13 +198,9 @@ fn meta_value_to_yaml(value: &MetaValue) -> std::io::Result<Yaml> {
198198
Ok(Yaml::Array(yaml_list))
199199
}
200200
MetaValue::MetaMap(map) => {
201-
// Sort keys to ensure deterministic output
202-
let mut sorted_keys: Vec<_> = map.keys().collect();
203-
sorted_keys.sort();
204-
201+
// LinkedHashMap preserves insertion order
205202
let mut yaml_map = LinkedHashMap::new();
206-
for key in sorted_keys {
207-
let val = &map[key];
203+
for (key, val) in map {
208204
yaml_map.insert(Yaml::String(key.clone()), meta_value_to_yaml(val)?);
209205
}
210206
Ok(Yaml::Hash(yaml_map))
@@ -217,13 +213,9 @@ fn write_meta<T: std::io::Write + ?Sized>(meta: &Meta, buf: &mut T) -> std::io::
217213
Ok(false)
218214
} else {
219215
// Convert Meta to YAML
220-
// Sort keys to ensure deterministic output
221-
let mut sorted_keys: Vec<_> = meta.keys().collect();
222-
sorted_keys.sort();
223-
216+
// LinkedHashMap preserves insertion order
224217
let mut yaml_map = LinkedHashMap::new();
225-
for key in sorted_keys {
226-
let value = &meta[key];
218+
for (key, value) in meta {
227219
yaml_map.insert(Yaml::String(key.clone()), meta_value_to_yaml(value)?);
228220
}
229221
let yaml = Yaml::Hash(yaml_map);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
zebra: last
3+
apple: first
4+
middle: second
5+
banana: third
6+
---
7+
8+
# Test Document
9+
10+
This tests that YAML key order is preserved.

crates/quarto-markdown-pandoc/tests/test_json_roundtrip.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ use quarto_markdown_pandoc::pandoc::{Pandoc, Block, Paragraph, Inline, Str};
77
use quarto_markdown_pandoc::pandoc::location::SourceInfo;
88
use quarto_markdown_pandoc::writers::json;
99
use quarto_markdown_pandoc::readers;
10+
use hashlink::LinkedHashMap;
1011
use std::collections::HashMap;
1112

1213
#[test]
1314
fn test_json_roundtrip_simple_paragraph() {
1415
// Create a simple Pandoc document
1516
let original = Pandoc {
16-
meta: HashMap::new(),
17+
meta: LinkedHashMap::new(),
1718
blocks: vec![Block::Paragraph(Paragraph {
1819
content: vec![Inline::Str(Str {
1920
text: "Hello, world!".to_string(),
@@ -68,7 +69,7 @@ fn test_json_roundtrip_complex_document() {
6869
// Create a more complex document with multiple block types
6970
let original = Pandoc {
7071
meta: {
71-
let mut meta = HashMap::new();
72+
let mut meta = LinkedHashMap::new();
7273
meta.insert(
7374
"title".to_string(),
7475
quarto_markdown_pandoc::pandoc::MetaValue::MetaString("Test Document".to_string())
@@ -182,7 +183,7 @@ fn test_json_write_then_read_matches_original_structure() {
182183
// with the same basic structure, even if exact equality is not possible
183184

184185
let original = Pandoc {
185-
meta: HashMap::new(),
186+
meta: LinkedHashMap::new(),
186187
blocks: vec![
187188
Block::Plain(quarto_markdown_pandoc::pandoc::Plain {
188189
content: vec![Inline::Str(Str {

0 commit comments

Comments
 (0)