refkit 0.0.7__tar.gz → 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {refkit-0.0.7 → refkit-0.1.0}/Cargo.lock +2 -2
- {refkit-0.0.7 → refkit-0.1.0}/Cargo.toml +2 -2
- {refkit-0.0.7 → refkit-0.1.0}/PKG-INFO +1 -1
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/document.rs +3 -3
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/library/guard.rs +14 -14
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/raw/parse.rs +15 -13
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/raw/tests.rs +9 -0
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/raw.rs +32 -44
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/render/bibliography.rs +9 -4
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/render/html.rs +14 -9
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/render/mod.rs +8 -1
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/render_tree.rs +2 -2
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/tidy/keys.rs +22 -24
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/tidy/latex.rs +2 -2
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/tidy/mod.rs +8 -3
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/tidy/render/value.rs +3 -0
- {refkit-0.0.7 → refkit-0.1.0}/packages/refkit/rust/src/conversion.rs +2 -2
- {refkit-0.0.7 → refkit-0.1.0}/packages/refkit/rust/src/entry.rs +12 -12
- {refkit-0.0.7 → refkit-0.1.0}/packages/refkit/rust/src/library.rs +8 -8
- {refkit-0.0.7 → refkit-0.1.0}/pyproject.toml +1 -1
- {refkit-0.0.7 → refkit-0.1.0}/.agent-plugin/plugin.json +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/.agent-plugin/skills/refkit/SKILL.md +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/.agent-plugin/skills/refkit/agents/openai.yaml +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/.agent-plugin/skills/refkit/references/contracts.md +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/.agent-plugin/skills/refkit/references/edit.md +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/.agent-plugin/skills/refkit/references/inspect.md +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/.agent-plugin/skills/refkit/references/render.md +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/.agent-plugin/skills/refkit/references/tidy.md +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/LICENSE +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/NOTICE +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/README.md +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/build_backend.py +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/Cargo.toml +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/lib.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/library/diagnostic.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/library/mod.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/library/parse.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/library/recovery.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/library/source.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/raw/edit.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/raw/sanitize.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/render/citation.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/render/text.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/source.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/strings.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/style/validate.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/style.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/tidy/duplicates.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/tidy/options.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/tidy/references.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/tidy/render/sort.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/tidy/render.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/crates/refkit-core/src/tidy/unicode.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/packages/refkit/rust/Cargo.toml +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/packages/refkit/rust/src/citation.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/packages/refkit/rust/src/document.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/packages/refkit/rust/src/errors.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/packages/refkit/rust/src/filesystem.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/packages/refkit/rust/src/lib.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/packages/refkit/rust/src/module.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/packages/refkit/rust/src/raw.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/packages/refkit/rust/src/rendered.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/packages/refkit/rust/src/repr.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/packages/refkit/rust/src/style.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/packages/refkit/rust/src/tidy.rs +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/src/refkit/__init__.py +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/src/refkit/__init__.pyi +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/src/refkit/_native.pyi +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/src/refkit/agent.py +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/src/refkit/py.typed +0 -0
- {refkit-0.0.7 → refkit-0.1.0}/src/refkit/types.py +0 -0
|
@@ -455,7 +455,7 @@ dependencies = [
|
|
|
455
455
|
|
|
456
456
|
[[package]]
|
|
457
457
|
name = "refkit-core"
|
|
458
|
-
version = "0.0
|
|
458
|
+
version = "0.1.0"
|
|
459
459
|
dependencies = [
|
|
460
460
|
"biblatex",
|
|
461
461
|
"hayagriva",
|
|
@@ -468,7 +468,7 @@ dependencies = [
|
|
|
468
468
|
|
|
469
469
|
[[package]]
|
|
470
470
|
name = "refkit-native"
|
|
471
|
-
version = "0.0
|
|
471
|
+
version = "0.1.0"
|
|
472
472
|
dependencies = [
|
|
473
473
|
"pyo3",
|
|
474
474
|
"refkit-core",
|
|
@@ -3,7 +3,7 @@ members = ["crates/refkit-core", "packages/refkit/rust"]
|
|
|
3
3
|
resolver = "3"
|
|
4
4
|
|
|
5
5
|
[workspace.package]
|
|
6
|
-
version = "0.0
|
|
6
|
+
version = "0.1.0"
|
|
7
7
|
edition = "2024"
|
|
8
8
|
rust-version = "1.88"
|
|
9
9
|
license = "Apache-2.0"
|
|
@@ -11,7 +11,7 @@ repository = "https://github.com/peter-gy/refkit"
|
|
|
11
11
|
|
|
12
12
|
[workspace.dependencies]
|
|
13
13
|
pyo3 = "0.29.0"
|
|
14
|
-
refkit-core = { version = "0.0
|
|
14
|
+
refkit-core = { version = "0.1.0", path = "crates/refkit-core" }
|
|
15
15
|
serde = { version = "1.0.228", features = ["derive"] }
|
|
16
16
|
serde_json = "1.0.150"
|
|
17
17
|
|
|
@@ -97,12 +97,12 @@ impl Document {
|
|
|
97
97
|
)?;
|
|
98
98
|
let citations = rendered
|
|
99
99
|
.citations
|
|
100
|
-
.
|
|
101
|
-
.zip(
|
|
100
|
+
.into_iter()
|
|
101
|
+
.zip(requests)
|
|
102
102
|
.map(|(citation, request)| {
|
|
103
103
|
rendered_record_from_citation(
|
|
104
104
|
citation,
|
|
105
|
-
request.items.
|
|
105
|
+
request.items.into_iter().map(|item| item.key).collect(),
|
|
106
106
|
)
|
|
107
107
|
})
|
|
108
108
|
.collect::<Result<Vec<_>, _>>()
|
|
@@ -165,23 +165,22 @@ pub(crate) fn validate_raw(raw: &RawBibliography<'_>) -> Result<(), Diagnostic>
|
|
|
165
165
|
.iter()
|
|
166
166
|
.map(|pair| (pair.key.v, &pair.value.v))
|
|
167
167
|
.collect();
|
|
168
|
-
let
|
|
168
|
+
let has_references = raw
|
|
169
169
|
.entries
|
|
170
170
|
.iter()
|
|
171
|
-
.
|
|
172
|
-
.map(|(index, entry)| (entry.v.key.v, index))
|
|
173
|
-
.collect();
|
|
174
|
-
let mut graph = vec![Vec::new(); raw.entries.len()];
|
|
171
|
+
.any(|entry| entry.v.fields.iter().any(|field| is_reference(field.key.v)));
|
|
175
172
|
let mut weights = vec![1usize; raw.entries.len()];
|
|
176
173
|
let mut bytes = 0usize;
|
|
177
174
|
let mut expansion_steps = 0usize;
|
|
175
|
+
let mut value = String::new();
|
|
176
|
+
let mut ancestors = Vec::new();
|
|
178
177
|
for (index, entry) in raw.entries.iter().enumerate() {
|
|
179
178
|
for field in &entry.v.fields {
|
|
180
|
-
|
|
179
|
+
value.clear();
|
|
181
180
|
expand(
|
|
182
181
|
&field.value.v,
|
|
183
182
|
&abbreviations,
|
|
184
|
-
&mut
|
|
183
|
+
&mut ancestors,
|
|
185
184
|
&mut expansion_steps,
|
|
186
185
|
&mut value,
|
|
187
186
|
)
|
|
@@ -200,15 +199,16 @@ pub(crate) fn validate_raw(raw: &RawBibliography<'_>) -> Result<(), Diagnostic>
|
|
|
200
199
|
weights[index] = weights[index].saturating_add(value.len());
|
|
201
200
|
}
|
|
202
201
|
}
|
|
203
|
-
if
|
|
204
|
-
entry
|
|
205
|
-
.v
|
|
206
|
-
.fields
|
|
207
|
-
.iter()
|
|
208
|
-
.all(|field| !is_reference(field.key.v))
|
|
209
|
-
}) {
|
|
202
|
+
if !has_references {
|
|
210
203
|
return Ok(());
|
|
211
204
|
}
|
|
205
|
+
let entry_index: HashMap<_, _> = raw
|
|
206
|
+
.entries
|
|
207
|
+
.iter()
|
|
208
|
+
.enumerate()
|
|
209
|
+
.map(|(index, entry)| (entry.v.key.v, index))
|
|
210
|
+
.collect();
|
|
211
|
+
let mut graph = vec![Vec::new(); raw.entries.len()];
|
|
212
212
|
let mut detached = raw.clone();
|
|
213
213
|
for entry in &mut detached.entries {
|
|
214
214
|
entry.v.fields.retain(|field| {
|
|
@@ -346,6 +346,11 @@ fn parse_entry_key(body: &str) -> Result<(String, usize), String> {
|
|
|
346
346
|
|
|
347
347
|
fn find_at_block_end(source: &str, start: usize) -> Result<usize, (usize, String)> {
|
|
348
348
|
let escape_aware = find_at_block_end_with_escape_mode(source, start, true);
|
|
349
|
+
if let Ok(end) = escape_aware
|
|
350
|
+
&& !source[start..end].contains('\\')
|
|
351
|
+
{
|
|
352
|
+
return Ok(end);
|
|
353
|
+
}
|
|
349
354
|
let permissive = find_at_block_end_with_escape_mode(source, start, false);
|
|
350
355
|
match (escape_aware, permissive) {
|
|
351
356
|
(Ok(escaped_end), Ok(permissive_end))
|
|
@@ -381,7 +386,6 @@ fn find_at_block_end_with_escape_mode(
|
|
|
381
386
|
start: usize,
|
|
382
387
|
escape_aware: bool,
|
|
383
388
|
) -> Result<usize, (usize, String)> {
|
|
384
|
-
let next_block = find_recovery_block_start(source, start);
|
|
385
389
|
if is_line_style_comment(source, start) {
|
|
386
390
|
return Err((
|
|
387
391
|
take_line(source, start),
|
|
@@ -390,14 +394,12 @@ fn find_at_block_end_with_escape_mode(
|
|
|
390
394
|
}
|
|
391
395
|
let Some(open_rel) = source[start..].find(['{', '(']) else {
|
|
392
396
|
return Err((
|
|
393
|
-
|
|
397
|
+
find_recovery_block_start(source, start).unwrap_or_else(|| take_line(source, start)),
|
|
394
398
|
"entry opener is missing".to_string(),
|
|
395
399
|
));
|
|
396
400
|
};
|
|
397
401
|
let open = start + open_rel;
|
|
398
|
-
if let Some(next) =
|
|
399
|
-
&& next < open
|
|
400
|
-
{
|
|
402
|
+
if let Some(next) = find_recovery_block_start(&source[..open], start) {
|
|
401
403
|
return Err((next, "entry opener is missing".to_string()));
|
|
402
404
|
}
|
|
403
405
|
let opener = source[open..].chars().next().unwrap();
|
|
@@ -464,7 +466,7 @@ fn find_at_block_end_with_escape_mode(
|
|
|
464
466
|
}
|
|
465
467
|
|
|
466
468
|
Err((
|
|
467
|
-
|
|
469
|
+
find_recovery_block_start(source, start).unwrap_or(source.len()),
|
|
468
470
|
"entry ended before closing delimiter".to_string(),
|
|
469
471
|
))
|
|
470
472
|
}
|
|
@@ -539,17 +541,17 @@ fn is_parseable_at_start(source: &str, start: usize) -> bool {
|
|
|
539
541
|
}
|
|
540
542
|
|
|
541
543
|
fn is_line_style_comment(source: &str, start: usize) -> bool {
|
|
544
|
+
if !source[start..]
|
|
545
|
+
.get(.."@comment".len())
|
|
546
|
+
.is_some_and(|prefix| prefix.eq_ignore_ascii_case("@comment"))
|
|
547
|
+
{
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
542
550
|
let line_start = source[..start].rfind('\n').map_or(0, |index| index + 1);
|
|
543
551
|
if !source[line_start..start].chars().all(char::is_whitespace) {
|
|
544
552
|
return false;
|
|
545
553
|
}
|
|
546
554
|
let line = &source[start..take_line(source, start)];
|
|
547
|
-
let Some(prefix) = line.get(.."@comment".len()) else {
|
|
548
|
-
return false;
|
|
549
|
-
};
|
|
550
|
-
if !prefix.eq_ignore_ascii_case("@comment") {
|
|
551
|
-
return false;
|
|
552
|
-
}
|
|
553
555
|
let rest = line.get("@comment".len()..).unwrap_or_default();
|
|
554
556
|
if !rest
|
|
555
557
|
.chars()
|
|
@@ -568,12 +570,12 @@ pub(super) fn parse_value(
|
|
|
568
570
|
) -> Result<ParsedValue, String> {
|
|
569
571
|
let first = parse_value_atom(body, start, body_offset)?;
|
|
570
572
|
let mut cursor = first.1;
|
|
571
|
-
let mut atoms = first.4.clone();
|
|
572
573
|
let mut expression_cursor = cursor;
|
|
573
574
|
skip_field_space(body, &mut expression_cursor);
|
|
574
575
|
if !body[expression_cursor..].starts_with('#') {
|
|
575
576
|
return Ok(first);
|
|
576
577
|
}
|
|
578
|
+
let mut atoms = first.4;
|
|
577
579
|
|
|
578
580
|
while expression_cursor < body.len() && body[expression_cursor..].starts_with('#') {
|
|
579
581
|
expression_cursor += 1;
|
|
@@ -556,3 +556,12 @@ fn raw_document_rejects_unsafe_field_edits() {
|
|
|
556
556
|
"jcs # \" Extra\""
|
|
557
557
|
);
|
|
558
558
|
}
|
|
559
|
+
|
|
560
|
+
#[test]
|
|
561
|
+
fn adjacent_entries_preserve_unicode_values_and_source_writeback() {
|
|
562
|
+
let source = "@book{a,title={Été}}@book{b,title={研究}}@book{c,title={🙂}}";
|
|
563
|
+
let document = RawDocument::parse(source);
|
|
564
|
+
assert_eq!(document.entry_keys(), ["a", "b", "c"]);
|
|
565
|
+
assert_eq!(raw_field_value(&document, "b", "title"), "研究");
|
|
566
|
+
assert_eq!(document.render().unwrap(), source);
|
|
567
|
+
}
|
|
@@ -435,13 +435,18 @@ impl RawDocument {
|
|
|
435
435
|
self.data.blocks.iter().map(raw_block_info).collect()
|
|
436
436
|
}
|
|
437
437
|
|
|
438
|
-
|
|
438
|
+
#[cfg(test)]
|
|
439
|
+
fn syntax(&self) -> RawSyntaxDocument {
|
|
440
|
+
self.clone().into_syntax()
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
pub(crate) fn into_syntax(self) -> RawSyntaxDocument {
|
|
439
444
|
RawSyntaxDocument {
|
|
440
|
-
blocks: self.data.blocks.
|
|
445
|
+
blocks: self.data.blocks.into_iter().map(raw_syntax_block).collect(),
|
|
441
446
|
entries: self
|
|
442
447
|
.data
|
|
443
448
|
.entry_blocks
|
|
444
|
-
.
|
|
449
|
+
.into_iter()
|
|
445
450
|
.enumerate()
|
|
446
451
|
.map(|(entry_id, entry)| raw_syntax_entry(RawEntryId(entry_id), entry))
|
|
447
452
|
.collect(),
|
|
@@ -541,70 +546,53 @@ fn raw_block_info(block: &RawBlock) -> RawBlockInfo {
|
|
|
541
546
|
}
|
|
542
547
|
}
|
|
543
548
|
|
|
544
|
-
fn raw_syntax_block(block:
|
|
549
|
+
fn raw_syntax_block(block: RawBlock) -> RawSyntaxBlock {
|
|
545
550
|
match block {
|
|
546
|
-
RawBlock::Whitespace { raw, span } => RawSyntaxBlock::Whitespace {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
},
|
|
550
|
-
RawBlock::Comment { raw, span } => RawSyntaxBlock::Comment {
|
|
551
|
-
raw: raw.clone(),
|
|
552
|
-
span: span.clone(),
|
|
553
|
-
},
|
|
554
|
-
RawBlock::Preamble { raw, value, span } => RawSyntaxBlock::Preamble {
|
|
555
|
-
raw: raw.clone(),
|
|
556
|
-
value: value.clone(),
|
|
557
|
-
span: span.clone(),
|
|
558
|
-
},
|
|
551
|
+
RawBlock::Whitespace { raw, span } => RawSyntaxBlock::Whitespace { raw, span },
|
|
552
|
+
RawBlock::Comment { raw, span } => RawSyntaxBlock::Comment { raw, span },
|
|
553
|
+
RawBlock::Preamble { raw, value, span } => RawSyntaxBlock::Preamble { raw, value, span },
|
|
559
554
|
RawBlock::StringDef {
|
|
560
555
|
raw,
|
|
561
556
|
key,
|
|
562
557
|
value,
|
|
563
558
|
span,
|
|
564
559
|
} => RawSyntaxBlock::StringDef {
|
|
565
|
-
raw
|
|
566
|
-
key
|
|
567
|
-
value
|
|
568
|
-
span
|
|
560
|
+
raw,
|
|
561
|
+
key,
|
|
562
|
+
value,
|
|
563
|
+
span,
|
|
569
564
|
},
|
|
570
565
|
RawBlock::Entry { id, key, span } => RawSyntaxBlock::Entry {
|
|
571
|
-
id: RawEntryId(
|
|
572
|
-
key
|
|
573
|
-
span
|
|
574
|
-
},
|
|
575
|
-
RawBlock::Failed { raw, error, span } => RawSyntaxBlock::Failed {
|
|
576
|
-
raw: raw.clone(),
|
|
577
|
-
error: error.clone(),
|
|
578
|
-
span: span.clone(),
|
|
579
|
-
},
|
|
580
|
-
RawBlock::Other { raw, span } => RawSyntaxBlock::Other {
|
|
581
|
-
raw: raw.clone(),
|
|
582
|
-
span: span.clone(),
|
|
566
|
+
id: RawEntryId(id),
|
|
567
|
+
key,
|
|
568
|
+
span,
|
|
583
569
|
},
|
|
570
|
+
RawBlock::Failed { raw, error, span } => RawSyntaxBlock::Failed { raw, error, span },
|
|
571
|
+
RawBlock::Other { raw, span } => RawSyntaxBlock::Other { raw, span },
|
|
584
572
|
}
|
|
585
573
|
}
|
|
586
574
|
|
|
587
|
-
fn raw_syntax_entry(id: RawEntryId, entry:
|
|
575
|
+
fn raw_syntax_entry(id: RawEntryId, entry: RawEntryData) -> RawSyntaxEntry {
|
|
588
576
|
RawSyntaxEntry {
|
|
589
577
|
id,
|
|
590
|
-
key: entry.key
|
|
591
|
-
kind: entry.kind
|
|
578
|
+
key: entry.key,
|
|
579
|
+
kind: entry.kind,
|
|
592
580
|
fields: entry
|
|
593
581
|
.field_blocks
|
|
594
|
-
.
|
|
582
|
+
.into_iter()
|
|
595
583
|
.enumerate()
|
|
596
584
|
.map(|(field_id, field)| RawSyntaxField {
|
|
597
585
|
id: RawFieldId(field_id),
|
|
598
|
-
name: field.name
|
|
599
|
-
value: field.value
|
|
586
|
+
name: field.name,
|
|
587
|
+
value: field.value,
|
|
600
588
|
value_mode: field.value_mode,
|
|
601
|
-
value_atoms: field.value_atoms
|
|
602
|
-
span: field.span
|
|
603
|
-
patch_span: field.patch_span
|
|
589
|
+
value_atoms: field.value_atoms,
|
|
590
|
+
span: field.span,
|
|
591
|
+
patch_span: field.patch_span,
|
|
604
592
|
})
|
|
605
593
|
.collect(),
|
|
606
|
-
span: entry.span
|
|
607
|
-
raw: entry.raw
|
|
594
|
+
span: entry.span,
|
|
595
|
+
raw: entry.raw,
|
|
608
596
|
}
|
|
609
597
|
}
|
|
610
598
|
|
|
@@ -3,7 +3,6 @@ use std::fmt::{self, Write as _};
|
|
|
3
3
|
use hayagriva::{BibliographyItem, BufWriteFormat, RenderedBibliography};
|
|
4
4
|
|
|
5
5
|
use super::html::{render_child_html, render_children_html, write_html_escaped};
|
|
6
|
-
use super::text::elem_children_to_string;
|
|
7
6
|
|
|
8
7
|
pub(crate) fn bibliography_to_text_html(
|
|
9
8
|
bibliography: &RenderedBibliography,
|
|
@@ -57,11 +56,17 @@ fn write_bibliography_item_text(
|
|
|
57
56
|
.map_err(|err| err.to_string())?;
|
|
58
57
|
}
|
|
59
58
|
|
|
60
|
-
let
|
|
61
|
-
if
|
|
59
|
+
let label_end = output.len();
|
|
60
|
+
if label_end > item_start {
|
|
62
61
|
output.push(' ');
|
|
63
62
|
}
|
|
64
|
-
output.
|
|
63
|
+
let content_start = output.len();
|
|
64
|
+
item.content
|
|
65
|
+
.write_buf(output, BufWriteFormat::Plain)
|
|
66
|
+
.map_err(|err| err.to_string())?;
|
|
67
|
+
if output.len() == content_start {
|
|
68
|
+
output.truncate(label_end);
|
|
69
|
+
}
|
|
65
70
|
Ok(())
|
|
66
71
|
}
|
|
67
72
|
|
|
@@ -141,14 +141,19 @@ fn push_html_wrapper(output: &mut String, suffix: &mut String, start: &str, end:
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
pub(crate) fn write_html_escaped(output: &mut String, value: &str) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
'
|
|
148
|
-
'
|
|
149
|
-
'
|
|
150
|
-
'
|
|
151
|
-
|
|
152
|
-
|
|
144
|
+
let mut start = 0;
|
|
145
|
+
for (index, byte) in value.bytes().enumerate() {
|
|
146
|
+
let escaped = match byte {
|
|
147
|
+
b'&' => "&",
|
|
148
|
+
b'<' => "<",
|
|
149
|
+
b'>' => ">",
|
|
150
|
+
b'"' => """,
|
|
151
|
+
b'\'' => "'",
|
|
152
|
+
_ => continue,
|
|
153
|
+
};
|
|
154
|
+
output.push_str(&value[start..index]);
|
|
155
|
+
output.push_str(escaped);
|
|
156
|
+
start = index + 1;
|
|
153
157
|
}
|
|
158
|
+
output.push_str(&value[start..]);
|
|
154
159
|
}
|
|
@@ -25,7 +25,14 @@ pub struct RenderedOutput {
|
|
|
25
25
|
|
|
26
26
|
pub(crate) fn bundled_locales() -> &'static [CslLocale] {
|
|
27
27
|
static LOCALES: OnceLock<Vec<CslLocale>> = OnceLock::new();
|
|
28
|
-
LOCALES
|
|
28
|
+
LOCALES
|
|
29
|
+
.get_or_init(|| {
|
|
30
|
+
let mut locales = archive::locales();
|
|
31
|
+
// The engine searches these resources for every term, including its en-US fallback.
|
|
32
|
+
locales.sort_by_key(|locale| locale.lang.as_ref().is_none_or(|lang| lang.0 != "en-US"));
|
|
33
|
+
locales
|
|
34
|
+
})
|
|
35
|
+
.as_slice()
|
|
29
36
|
}
|
|
30
37
|
|
|
31
38
|
pub fn is_bundled_locale(code: &str) -> bool {
|
|
@@ -61,7 +61,7 @@ impl RenderedRecord {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
pub(crate) fn rendered_record_from_citation(
|
|
64
|
-
citation:
|
|
64
|
+
citation: RenderedCitation,
|
|
65
65
|
keys: Vec<String>,
|
|
66
66
|
) -> Result<RenderedRecord, String> {
|
|
67
67
|
Ok(RenderedRecord::new(
|
|
@@ -69,7 +69,7 @@ pub(crate) fn rendered_record_from_citation(
|
|
|
69
69
|
elem_children_to_html(&citation.citation)?,
|
|
70
70
|
None,
|
|
71
71
|
RenderedTree::Citation {
|
|
72
|
-
children: citation.citation
|
|
72
|
+
children: citation.citation,
|
|
73
73
|
keys,
|
|
74
74
|
},
|
|
75
75
|
))
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
use std::borrow::Cow;
|
|
1
2
|
use std::collections::{HashMap, HashSet};
|
|
2
3
|
|
|
3
4
|
use indexmap::IndexMap;
|
|
@@ -40,7 +41,7 @@ pub(crate) fn generated_keys<'a>(
|
|
|
40
41
|
let mut groups: IndexMap<String, Vec<&RawSyntaxEntry>> = IndexMap::new();
|
|
41
42
|
let mut reserved = HashSet::new();
|
|
42
43
|
for entry in entries {
|
|
43
|
-
if let Some(key) = generate_key(
|
|
44
|
+
if let Some(key) = generate_key(entry, &template, None)? {
|
|
44
45
|
groups.entry(key).or_default().push(entry);
|
|
45
46
|
} else {
|
|
46
47
|
reserved.insert(entry.key.clone());
|
|
@@ -54,11 +55,10 @@ pub(crate) fn generated_keys<'a>(
|
|
|
54
55
|
let duplicate = entries.len() > 1 || retained.contains(&base);
|
|
55
56
|
let mut suffix = 1;
|
|
56
57
|
for entry in entries {
|
|
57
|
-
let values = entry_values(entry);
|
|
58
58
|
let candidate = if duplicate {
|
|
59
59
|
let mut attempted = HashSet::new();
|
|
60
60
|
loop {
|
|
61
|
-
let mut candidate = generate_key(
|
|
61
|
+
let mut candidate = generate_key(entry, &template, Some(suffix))?
|
|
62
62
|
.unwrap_or_else(|| base.clone());
|
|
63
63
|
if !attempted.insert(candidate.clone()) {
|
|
64
64
|
candidate = format!("{base}{}", num_to_letter(suffix));
|
|
@@ -136,7 +136,7 @@ fn marker_parameter(marker: &str) -> (String, Option<usize>) {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
fn generate_key(
|
|
139
|
-
values: &
|
|
139
|
+
values: &RawSyntaxEntry,
|
|
140
140
|
template: &[TemplateToken],
|
|
141
141
|
duplicate: Option<usize>,
|
|
142
142
|
) -> Result<Option<String>, String> {
|
|
@@ -171,13 +171,13 @@ fn generate_key(
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
fn marker_words(
|
|
174
|
-
values: &
|
|
174
|
+
values: &RawSyntaxEntry,
|
|
175
175
|
marker: &str,
|
|
176
176
|
parameter: Option<usize>,
|
|
177
177
|
duplicate: Option<usize>,
|
|
178
178
|
) -> Result<Vec<String>, String> {
|
|
179
179
|
match marker {
|
|
180
|
-
"auth" => Ok(parse_name_list(value(values, "author"))
|
|
180
|
+
"auth" => Ok(parse_name_list(&value(values, "author"))
|
|
181
181
|
.into_iter()
|
|
182
182
|
.filter(|name| !name.last.is_empty())
|
|
183
183
|
.collect::<Vec<_>>()
|
|
@@ -185,7 +185,7 @@ fn marker_words(
|
|
|
185
185
|
.map(|name| vec![name.last.clone()])
|
|
186
186
|
.unwrap_or_default()),
|
|
187
187
|
"authEtAl" => {
|
|
188
|
-
let authors = parse_name_list(value(values, "author"))
|
|
188
|
+
let authors = parse_name_list(&value(values, "author"))
|
|
189
189
|
.into_iter()
|
|
190
190
|
.filter(|name| !name.last.is_empty())
|
|
191
191
|
.collect::<Vec<_>>();
|
|
@@ -199,14 +199,14 @@ fn marker_words(
|
|
|
199
199
|
}
|
|
200
200
|
Ok(words)
|
|
201
201
|
}
|
|
202
|
-
"authors" => Ok(parse_name_list(value(values, "author"))
|
|
202
|
+
"authors" => Ok(parse_name_list(&value(values, "author"))
|
|
203
203
|
.into_iter()
|
|
204
204
|
.filter(|name| !name.last.is_empty())
|
|
205
205
|
.map(|name| name.last)
|
|
206
206
|
.collect()),
|
|
207
207
|
"authorsN" => {
|
|
208
208
|
let limit = parameter.unwrap_or(0);
|
|
209
|
-
let authors = parse_name_list(value(values, "author"))
|
|
209
|
+
let authors = parse_name_list(&value(values, "author"))
|
|
210
210
|
.into_iter()
|
|
211
211
|
.filter(|name| !name.last.is_empty())
|
|
212
212
|
.collect::<Vec<_>>();
|
|
@@ -248,7 +248,7 @@ fn marker_words(
|
|
|
248
248
|
duplicate.map(|value| value.to_string()).unwrap_or_default(),
|
|
249
249
|
]),
|
|
250
250
|
marker if marker == marker.to_uppercase() => {
|
|
251
|
-
Ok(words(value(values, &marker.to_lowercase())))
|
|
251
|
+
Ok(words(&value(values, &marker.to_lowercase())))
|
|
252
252
|
}
|
|
253
253
|
_ => Err(format!("Invalid citation key token {marker}")),
|
|
254
254
|
}
|
|
@@ -270,21 +270,13 @@ fn apply_modifier(words: Vec<String>, modifier: &str) -> Result<Vec<String>, Str
|
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
-
fn
|
|
274
|
-
entry
|
|
275
|
-
.fields
|
|
276
|
-
.iter()
|
|
277
|
-
.map(|field| (field.name.to_lowercase(), rendered_field_value(field)))
|
|
278
|
-
.collect()
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
fn rendered_field_value(field: &RawSyntaxField) -> String {
|
|
273
|
+
fn rendered_field_value(field: &RawSyntaxField) -> Cow<'_, str> {
|
|
282
274
|
match field.value_mode {
|
|
283
|
-
RawValueMode::Expression => expression_text(&field.value_atoms),
|
|
275
|
+
RawValueMode::Expression => Cow::Owned(expression_text(&field.value_atoms)),
|
|
284
276
|
RawValueMode::Bare
|
|
285
277
|
| RawValueMode::Braced
|
|
286
278
|
| RawValueMode::Missing
|
|
287
|
-
| RawValueMode::Quoted => field.value
|
|
279
|
+
| RawValueMode::Quoted => Cow::Borrowed(&field.value),
|
|
288
280
|
}
|
|
289
281
|
}
|
|
290
282
|
|
|
@@ -371,7 +363,7 @@ fn is_prefix_token(token: &str) -> bool {
|
|
|
371
363
|
.is_some_and(|ch| ch.is_ascii_lowercase())
|
|
372
364
|
}
|
|
373
365
|
|
|
374
|
-
fn title(values: &
|
|
366
|
+
fn title(values: &RawSyntaxEntry) -> String {
|
|
375
367
|
let title = value(values, "title");
|
|
376
368
|
if title.is_empty() {
|
|
377
369
|
value(values, "booktitle").to_string()
|
|
@@ -380,8 +372,14 @@ fn title(values: &HashMap<String, String>) -> String {
|
|
|
380
372
|
}
|
|
381
373
|
}
|
|
382
374
|
|
|
383
|
-
fn value<'a>(
|
|
384
|
-
|
|
375
|
+
fn value<'a>(entry: &'a RawSyntaxEntry, key: &str) -> Cow<'a, str> {
|
|
376
|
+
entry
|
|
377
|
+
.fields
|
|
378
|
+
.iter()
|
|
379
|
+
.rev()
|
|
380
|
+
.find(|field| field.name.to_lowercase() == key)
|
|
381
|
+
.map(rendered_field_value)
|
|
382
|
+
.unwrap_or(Cow::Borrowed(""))
|
|
385
383
|
}
|
|
386
384
|
|
|
387
385
|
fn non_function_words(value: &str) -> Vec<String> {
|
|
@@ -61,8 +61,8 @@ fn read_command(chars: &[char], index: &mut usize) -> String {
|
|
|
61
61
|
let mut output = String::from("\\");
|
|
62
62
|
*index += 1;
|
|
63
63
|
let symbol = chars.get(*index).copied().filter(|character| {
|
|
64
|
-
!character.is_alphabetic()
|
|
65
|
-
|
|
64
|
+
!(character.is_alphabetic()
|
|
65
|
+
|| *character == '\\'
|
|
66
66
|
&& chars
|
|
67
67
|
.get(*index + 1)
|
|
68
68
|
.is_some_and(|next| next.is_alphabetic()))
|
|
@@ -6,6 +6,7 @@ mod references;
|
|
|
6
6
|
mod render;
|
|
7
7
|
mod unicode;
|
|
8
8
|
|
|
9
|
+
use std::borrow::Cow;
|
|
9
10
|
use std::fmt;
|
|
10
11
|
|
|
11
12
|
use crate::raw::{RawDocument, RawEntryId, RawSyntaxBlock};
|
|
@@ -103,7 +104,7 @@ pub fn tidy_bibtex(input: &str, options: TidyOptions) -> Result<TidyResult, Tidy
|
|
|
103
104
|
})?;
|
|
104
105
|
let input = normalize_newlines(input);
|
|
105
106
|
let doc = RawDocument::parse(&input);
|
|
106
|
-
let mut syntax = doc.
|
|
107
|
+
let mut syntax = doc.into_syntax();
|
|
107
108
|
|
|
108
109
|
if let Some((raw, error, byte)) = syntax.blocks.iter().find_map(first_failed_block) {
|
|
109
110
|
return Err(syntax_error(&input, raw, error, byte));
|
|
@@ -222,8 +223,12 @@ fn line_column(input: &str, byte: usize) -> (usize, usize) {
|
|
|
222
223
|
(line, column)
|
|
223
224
|
}
|
|
224
225
|
|
|
225
|
-
fn normalize_newlines(input: &str) ->
|
|
226
|
-
input.
|
|
226
|
+
fn normalize_newlines(input: &str) -> Cow<'_, str> {
|
|
227
|
+
if input.contains('\r') {
|
|
228
|
+
Cow::Owned(input.replace("\r\n", "\n").replace('\r', "\n"))
|
|
229
|
+
} else {
|
|
230
|
+
Cow::Borrowed(input)
|
|
231
|
+
}
|
|
227
232
|
}
|
|
228
233
|
|
|
229
234
|
#[cfg(test)]
|
|
@@ -192,6 +192,9 @@ fn unwrap_text(value: &str) -> String {
|
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
fn wrap_braced_value(field: &RawSyntaxField, value: &str, options: &TidyOptions) -> String {
|
|
195
|
+
if options.wrap.is_none() && !value.contains("\n\n") {
|
|
196
|
+
return value.to_string();
|
|
197
|
+
}
|
|
195
198
|
let indent = if options.tab {
|
|
196
199
|
"\t".to_string()
|
|
197
200
|
} else {
|
|
@@ -92,10 +92,10 @@ pub(crate) fn parse_projection_keys(
|
|
|
92
92
|
Ok(Some(parsed))
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
pub(crate) fn project_rows_to_py(
|
|
95
|
+
pub(crate) fn project_rows_to_py<'a>(
|
|
96
96
|
py: Python<'_>,
|
|
97
97
|
fields: &[ProjectionField],
|
|
98
|
-
records: &
|
|
98
|
+
records: impl IntoIterator<Item = &'a EntryRecord>,
|
|
99
99
|
) -> PyResult<Py<PyAny>> {
|
|
100
100
|
let rows = PyList::empty(py);
|
|
101
101
|
for record in records {
|
|
@@ -23,23 +23,23 @@ impl Entry {
|
|
|
23
23
|
#[pymethods]
|
|
24
24
|
impl Entry {
|
|
25
25
|
#[getter]
|
|
26
|
-
fn key(&self) ->
|
|
27
|
-
self.data.key
|
|
26
|
+
fn key(&self) -> &str {
|
|
27
|
+
&self.data.key
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
#[getter]
|
|
31
|
-
fn entry_type(&self) ->
|
|
32
|
-
self.data.entry_type
|
|
31
|
+
fn entry_type(&self) -> &str {
|
|
32
|
+
&self.data.entry_type
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
#[getter]
|
|
36
|
-
fn title(&self) -> Option
|
|
37
|
-
self.data.title.
|
|
36
|
+
fn title(&self) -> Option<&str> {
|
|
37
|
+
self.data.title.as_deref()
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
#[getter]
|
|
41
|
-
fn date(&self) -> Option
|
|
42
|
-
self.data.date.
|
|
41
|
+
fn date(&self) -> Option<&str> {
|
|
42
|
+
self.data.date.as_deref()
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
#[getter]
|
|
@@ -53,13 +53,13 @@ impl Entry {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
#[getter]
|
|
56
|
-
fn volume(&self) -> Option
|
|
57
|
-
self.data.volume.
|
|
56
|
+
fn volume(&self) -> Option<&str> {
|
|
57
|
+
self.data.volume.as_deref()
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
#[getter]
|
|
61
|
-
fn doi(&self) -> Option
|
|
62
|
-
self.data.doi.
|
|
61
|
+
fn doi(&self) -> Option<&str> {
|
|
62
|
+
self.data.doi.as_deref()
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
fn __repr__(&self) -> String {
|
|
@@ -147,14 +147,14 @@ impl Library {
|
|
|
147
147
|
) -> PyResult<Py<PyAny>> {
|
|
148
148
|
let fields = parse_project_fields_arg(fields)?;
|
|
149
149
|
let keys = parse_projection_keys(self.inner.as_ref(), keys)?;
|
|
150
|
-
|
|
151
|
-
Some(keys) =>
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
.
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
150
|
+
match keys {
|
|
151
|
+
Some(keys) => project_rows_to_py(
|
|
152
|
+
py,
|
|
153
|
+
&fields,
|
|
154
|
+
keys.iter().filter_map(|key| self.inner.get_record(key)),
|
|
155
|
+
),
|
|
156
|
+
None => project_rows_to_py(py, &fields, self.inner.records()),
|
|
157
|
+
}
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
fn __len__(&self) -> usize {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|