1use crate::grouping::{GroupSort, GroupSortKey, SortKey as GroupSortKey_};
31use crate::options::multilingual::{
32 MultilingualConfig, MultilingualMode, MultilingualSegment, MultilingualView, ScriptConfig,
33 SegmentWrap,
34};
35use crate::options::{
36 AndOptions, ContributorConfig, DateConfig, DelimiterPrecedesLast, DemoteNonDroppingParticle,
37 DisplayAsSort, MonthFormat, NameForm, ShortenListOptions, Sort, SortKey, SortSpec, Substitute,
38 SubstituteKey, TitleRendering, TitlesConfig,
39};
40#[cfg(feature = "schema")]
41use schemars::JsonSchema;
42use serde::{Deserialize, Serialize};
43use std::collections::HashMap;
44
45#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
51#[cfg_attr(feature = "schema", derive(JsonSchema))]
52#[serde(rename_all = "kebab-case")]
53#[non_exhaustive]
54pub enum ContributorPreset {
55 Apa,
59 Chicago,
63 Vancouver,
67 Ieee,
71 Harvard,
75 Springer,
79 NumericCompact,
83 NumericMedium,
86 NumericTight,
90 NumericLarge,
94 NumericAllAuthors,
98 NumericGivenDot,
102 AnnualReviews,
106 MathPhys,
111 SocSciFirst,
116 PhysicsNumeric,
121}
122
123impl ContributorPreset {
124 fn config_named_presets(&self) -> ContributorConfig {
126 match self {
127 ContributorPreset::Apa => ContributorConfig {
128 display_as_sort: Some(DisplayAsSort::First),
129 and: Some(AndOptions::Symbol),
130 delimiter: Some(", ".to_string()),
131 delimiter_precedes_last: Some(DelimiterPrecedesLast::Always),
132 initialize_with: Some(". ".to_string()),
133 name_form: Some(NameForm::Initials),
134 shorten: Some(ShortenListOptions {
135 min: 21,
136 use_first: 19,
137 ..Default::default()
138 }),
139 ..Default::default()
140 },
141 ContributorPreset::Chicago => ContributorConfig {
142 display_as_sort: Some(DisplayAsSort::First),
143 and: Some(AndOptions::Text),
144 delimiter: Some(", ".to_string()),
145 delimiter_precedes_last: Some(DelimiterPrecedesLast::Contextual),
146 ..Default::default()
147 },
148 ContributorPreset::Vancouver => ContributorConfig {
149 display_as_sort: Some(DisplayAsSort::All),
150 and: Some(AndOptions::None),
151 delimiter: Some(", ".to_string()),
152 initialize_with: Some("".to_string()),
153 name_form: Some(NameForm::Initials),
154 shorten: Some(ShortenListOptions {
155 min: 7,
156 use_first: 6,
157 ..Default::default()
158 }),
159 ..Default::default()
160 },
161 ContributorPreset::Ieee => ContributorConfig {
162 display_as_sort: Some(DisplayAsSort::None),
163 and: Some(AndOptions::Text),
164 delimiter: Some(", ".to_string()),
165 delimiter_precedes_last: Some(DelimiterPrecedesLast::Always),
166 initialize_with: Some(". ".to_string()),
167 name_form: Some(NameForm::Initials),
168 ..Default::default()
169 },
170 ContributorPreset::Harvard => ContributorConfig {
171 display_as_sort: Some(DisplayAsSort::All),
172 and: Some(AndOptions::Text),
173 delimiter: Some(", ".to_string()),
174 delimiter_precedes_last: Some(DelimiterPrecedesLast::Always),
175 initialize_with: Some(".".to_string()),
176 name_form: Some(NameForm::Initials),
177 ..Default::default()
178 },
179 ContributorPreset::Springer => ContributorConfig {
180 display_as_sort: Some(DisplayAsSort::All),
181 and: Some(AndOptions::None),
182 delimiter: Some(", ".to_string()),
183 delimiter_precedes_last: Some(DelimiterPrecedesLast::Always),
184 initialize_with: Some("".to_string()),
185 name_form: Some(NameForm::Initials),
186 sort_separator: Some(" ".to_string()),
187 shorten: Some(ShortenListOptions {
188 min: 5,
189 use_first: 3,
190 ..Default::default()
191 }),
192 ..Default::default()
193 },
194 #[allow(clippy::unreachable, reason = "Subset of variants handled here")]
195 _ => unreachable!(),
196 }
197 }
198
199 fn config_numeric_presets(&self) -> ContributorConfig {
201 match self {
202 ContributorPreset::NumericCompact => ContributorConfig {
203 display_as_sort: Some(DisplayAsSort::All),
204 and: Some(AndOptions::None),
205 delimiter: Some(", ".to_string()),
206 delimiter_precedes_last: Some(DelimiterPrecedesLast::Always),
207 initialize_with: Some("".to_string()),
208 name_form: Some(NameForm::Initials),
209 sort_separator: Some(" ".to_string()),
210 demote_non_dropping_particle: Some(DemoteNonDroppingParticle::SortOnly),
211 shorten: Some(ShortenListOptions {
212 min: 7,
213 use_first: 6,
214 ..Default::default()
215 }),
216 ..Default::default()
217 },
218 ContributorPreset::NumericMedium => ContributorConfig {
219 display_as_sort: Some(DisplayAsSort::All),
220 and: Some(AndOptions::None),
221 delimiter: Some(", ".to_string()),
222 delimiter_precedes_last: Some(DelimiterPrecedesLast::Always),
223 initialize_with: Some("".to_string()),
224 name_form: Some(NameForm::Initials),
225 sort_separator: Some(" ".to_string()),
226 demote_non_dropping_particle: Some(DemoteNonDroppingParticle::SortOnly),
227 shorten: Some(ShortenListOptions {
228 min: 4,
229 use_first: 3,
230 ..Default::default()
231 }),
232 ..Default::default()
233 },
234 ContributorPreset::NumericTight => ContributorConfig {
235 display_as_sort: Some(DisplayAsSort::All),
236 and: Some(AndOptions::None),
237 delimiter: Some(", ".to_string()),
238 delimiter_precedes_last: Some(DelimiterPrecedesLast::Always),
239 initialize_with: Some("".to_string()),
240 name_form: Some(NameForm::Initials),
241 sort_separator: Some(" ".to_string()),
242 demote_non_dropping_particle: Some(DemoteNonDroppingParticle::SortOnly),
243 shorten: Some(ShortenListOptions {
244 min: 7,
245 use_first: 3,
246 ..Default::default()
247 }),
248 ..Default::default()
249 },
250 ContributorPreset::NumericLarge => ContributorConfig {
251 display_as_sort: Some(DisplayAsSort::All),
252 and: Some(AndOptions::None),
253 delimiter: Some(", ".to_string()),
254 delimiter_precedes_last: Some(DelimiterPrecedesLast::Always),
255 initialize_with: Some("".to_string()),
256 name_form: Some(NameForm::Initials),
257 sort_separator: Some(" ".to_string()),
258 demote_non_dropping_particle: Some(DemoteNonDroppingParticle::SortOnly),
259 shorten: Some(ShortenListOptions {
260 min: 11,
261 use_first: 10,
262 ..Default::default()
263 }),
264 ..Default::default()
265 },
266 ContributorPreset::NumericAllAuthors => ContributorConfig {
267 display_as_sort: Some(DisplayAsSort::All),
268 and: Some(AndOptions::None),
269 delimiter: Some(", ".to_string()),
270 delimiter_precedes_last: Some(DelimiterPrecedesLast::Always),
271 initialize_with: Some("".to_string()),
272 name_form: Some(NameForm::Initials),
273 sort_separator: Some(" ".to_string()),
274 demote_non_dropping_particle: Some(DemoteNonDroppingParticle::Never),
275 ..Default::default()
276 },
277 ContributorPreset::NumericGivenDot => ContributorConfig {
278 display_as_sort: Some(DisplayAsSort::None),
279 and: Some(AndOptions::None),
280 delimiter: Some(", ".to_string()),
281 delimiter_precedes_last: Some(DelimiterPrecedesLast::Always),
282 initialize_with: Some(".".to_string()),
283 name_form: Some(NameForm::Initials),
284 demote_non_dropping_particle: Some(DemoteNonDroppingParticle::SortOnly),
285 ..Default::default()
286 },
287 #[allow(clippy::unreachable, reason = "Subset of variants handled here")]
288 _ => unreachable!(),
289 }
290 }
291
292 fn config_specialty_presets(&self) -> ContributorConfig {
294 match self {
295 ContributorPreset::AnnualReviews => ContributorConfig {
296 display_as_sort: Some(DisplayAsSort::All),
297 and: Some(AndOptions::None),
298 delimiter: Some(", ".to_string()),
299 delimiter_precedes_last: Some(DelimiterPrecedesLast::Never),
300 initialize_with: Some("".to_string()),
301 name_form: Some(NameForm::Initials),
302 sort_separator: Some(" ".to_string()),
303 demote_non_dropping_particle: Some(DemoteNonDroppingParticle::Never),
304 shorten: Some(ShortenListOptions {
305 min: 7,
306 use_first: 5,
307 ..Default::default()
308 }),
309 ..Default::default()
310 },
311 ContributorPreset::MathPhys => ContributorConfig {
312 display_as_sort: Some(DisplayAsSort::All),
313 and: Some(AndOptions::None),
314 delimiter: Some(", ".to_string()),
315 delimiter_precedes_last: Some(DelimiterPrecedesLast::Always),
316 initialize_with: Some(".".to_string()),
317 name_form: Some(NameForm::Initials),
318 sort_separator: Some(", ".to_string()),
319 demote_non_dropping_particle: Some(DemoteNonDroppingParticle::SortOnly),
320 ..Default::default()
321 },
322 ContributorPreset::SocSciFirst => ContributorConfig {
323 display_as_sort: Some(DisplayAsSort::First),
324 and: Some(AndOptions::None),
325 delimiter: Some(", ".to_string()),
326 delimiter_precedes_last: Some(DelimiterPrecedesLast::Always),
327 initialize_with: Some(". ".to_string()),
328 name_form: Some(NameForm::Initials),
329 sort_separator: Some(", ".to_string()),
330 demote_non_dropping_particle: Some(DemoteNonDroppingParticle::SortOnly),
331 ..Default::default()
332 },
333 ContributorPreset::PhysicsNumeric => ContributorConfig {
334 display_as_sort: Some(DisplayAsSort::None),
335 and: Some(AndOptions::None),
336 delimiter: Some(", ".to_string()),
337 initialize_with: Some(". ".to_string()),
338 name_form: Some(NameForm::Initials),
339 demote_non_dropping_particle: Some(DemoteNonDroppingParticle::SortOnly),
340 ..Default::default()
341 },
342 #[allow(clippy::unreachable, reason = "Subset of variants handled here")]
343 _ => unreachable!(),
344 }
345 }
346
347 pub fn config(&self) -> ContributorConfig {
349 match self {
350 ContributorPreset::Apa
351 | ContributorPreset::Chicago
352 | ContributorPreset::Vancouver
353 | ContributorPreset::Ieee
354 | ContributorPreset::Harvard
355 | ContributorPreset::Springer => self.config_named_presets(),
356 ContributorPreset::NumericCompact
357 | ContributorPreset::NumericMedium
358 | ContributorPreset::NumericTight
359 | ContributorPreset::NumericLarge
360 | ContributorPreset::NumericAllAuthors
361 | ContributorPreset::NumericGivenDot => self.config_numeric_presets(),
362 ContributorPreset::AnnualReviews
363 | ContributorPreset::MathPhys
364 | ContributorPreset::SocSciFirst
365 | ContributorPreset::PhysicsNumeric => self.config_specialty_presets(),
366 }
367 }
368}
369
370#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
376#[cfg_attr(feature = "schema", derive(JsonSchema))]
377#[serde(rename_all = "kebab-case")]
378#[non_exhaustive]
379pub enum DatePreset {
380 Long,
383 Short,
386 Numeric,
389 Iso,
392}
393
394impl DatePreset {
395 pub fn config(&self) -> DateConfig {
397 match self {
398 DatePreset::Long => DateConfig {
399 month: MonthFormat::Long,
400 ..Default::default()
401 },
402 DatePreset::Short => DateConfig {
403 month: MonthFormat::Short,
404 ..Default::default()
405 },
406 DatePreset::Numeric => DateConfig {
407 month: MonthFormat::Numeric,
408 ..Default::default()
409 },
410 DatePreset::Iso => DateConfig {
411 month: MonthFormat::Numeric,
412 uncertainty_marker: None,
413 approximation_marker: None,
414 ..Default::default()
415 },
416 }
417 }
418}
419
420#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
426#[cfg_attr(feature = "schema", derive(JsonSchema))]
427#[serde(rename_all = "kebab-case")]
428#[non_exhaustive]
429pub enum TitlePreset {
430 Apa,
433 Chicago,
436 Ieee,
439 Humanities,
443 JournalEmphasis,
446 Scientific,
449}
450
451impl TitlePreset {
452 pub fn config(&self) -> TitlesConfig {
454 use crate::options::titles::TextCase;
455 let emph_rendering = TitleRendering {
456 emph: Some(true),
457 ..Default::default()
458 };
459 match self {
460 TitlePreset::Apa => TitlesConfig {
461 component: Some(TitleRendering {
462 text_case: Some(TextCase::SentenceApa),
463 ..Default::default()
464 }),
465 monograph: Some(TitleRendering {
466 text_case: Some(TextCase::SentenceApa),
467 emph: Some(true),
468 ..Default::default()
469 }),
470 periodical: Some(emph_rendering),
471 ..Default::default()
472 },
473 TitlePreset::Chicago | TitlePreset::Ieee => TitlesConfig {
474 component: Some(TitleRendering {
475 quote: Some(true),
476 ..Default::default()
477 }),
478 monograph: Some(emph_rendering.clone()),
479 periodical: Some(emph_rendering),
480 ..Default::default()
481 },
482 TitlePreset::Humanities => TitlesConfig {
483 component: Some(TitleRendering::default()),
484 monograph: Some(emph_rendering.clone()),
485 periodical: Some(emph_rendering.clone()),
486 serial: Some(emph_rendering),
487 ..Default::default()
488 },
489 TitlePreset::JournalEmphasis => TitlesConfig {
490 component: Some(TitleRendering::default()),
491 periodical: Some(emph_rendering.clone()),
492 serial: Some(emph_rendering),
493 ..Default::default()
494 },
495 TitlePreset::Scientific => TitlesConfig {
496 component: Some(TitleRendering {
497 text_case: Some(TextCase::SentenceNlm),
498 ..Default::default()
499 }),
500 monograph: Some(TitleRendering {
501 text_case: Some(TextCase::SentenceNlm),
502 ..Default::default()
503 }),
504 periodical: Some(TitleRendering::default()),
505 ..Default::default()
506 },
507 }
508 }
509}
510
511#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
516#[cfg_attr(feature = "schema", derive(JsonSchema))]
517#[serde(rename_all = "kebab-case")]
518#[non_exhaustive]
519pub enum SortPreset {
520 AuthorDateTitle,
522 AuthorTitleDate,
524 CitationNumber,
526}
527
528impl SortPreset {
529 pub fn sort(&self) -> Sort {
531 match self {
532 SortPreset::AuthorDateTitle => Sort {
533 shorten_names: false,
534 render_substitutions: false,
535 template: vec![
536 SortSpec {
537 key: SortKey::Author,
538 ascending: true,
539 },
540 SortSpec {
541 key: SortKey::Year,
542 ascending: true,
543 },
544 SortSpec {
545 key: SortKey::Title,
546 ascending: true,
547 },
548 ],
549 },
550 SortPreset::AuthorTitleDate => Sort {
551 shorten_names: false,
552 render_substitutions: false,
553 template: vec![
554 SortSpec {
555 key: SortKey::Author,
556 ascending: true,
557 },
558 SortSpec {
559 key: SortKey::Title,
560 ascending: true,
561 },
562 SortSpec {
563 key: SortKey::Year,
564 ascending: true,
565 },
566 ],
567 },
568 SortPreset::CitationNumber => Sort {
569 shorten_names: false,
570 render_substitutions: false,
571 template: vec![SortSpec {
572 key: SortKey::CitationNumber,
573 ascending: true,
574 }],
575 },
576 }
577 }
578
579 pub fn group_sort(&self) -> GroupSort {
581 let keys: Vec<GroupSortKey> = match self {
582 SortPreset::AuthorDateTitle => vec![
583 GroupSortKey {
584 key: GroupSortKey_::Author,
585 ascending: true,
586 order: None,
587 sort_order: None,
588 },
589 GroupSortKey {
590 key: GroupSortKey_::Issued,
591 ascending: true,
592 order: None,
593 sort_order: None,
594 },
595 GroupSortKey {
596 key: GroupSortKey_::Title,
597 ascending: true,
598 order: None,
599 sort_order: None,
600 },
601 ],
602 SortPreset::AuthorTitleDate => vec![
603 GroupSortKey {
604 key: GroupSortKey_::Author,
605 ascending: true,
606 order: None,
607 sort_order: None,
608 },
609 GroupSortKey {
610 key: GroupSortKey_::Title,
611 ascending: true,
612 order: None,
613 sort_order: None,
614 },
615 GroupSortKey {
616 key: GroupSortKey_::Issued,
617 ascending: true,
618 order: None,
619 sort_order: None,
620 },
621 ],
622 SortPreset::CitationNumber => vec![],
623 };
624 GroupSort { template: keys }
625 }
626}
627
628#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
634#[cfg_attr(feature = "schema", derive(JsonSchema))]
635#[serde(rename_all = "kebab-case")]
636#[non_exhaustive]
637pub enum SubstitutePreset {
638 Standard,
641 EditorFirst,
644 TitleFirst,
647 EditorShort,
649 EditorLong,
651 EditorTranslatorShort,
653 EditorTranslatorLong,
655 EditorTitleShort,
657 EditorTitleLong,
659 EditorTranslatorTitleShort,
661 EditorTranslatorTitleLong,
663}
664
665impl SubstitutePreset {
666 pub fn config(&self) -> Substitute {
668 match self {
669 SubstitutePreset::Standard => Substitute {
670 contributor_role_form: None,
671 template: vec![
672 SubstituteKey::Editor,
673 SubstituteKey::Title,
674 SubstituteKey::Translator,
675 ],
676 overrides: HashMap::new(),
677 role_substitute: HashMap::new(),
678 unknown_fields: std::collections::BTreeMap::new(),
679 },
680 SubstitutePreset::EditorFirst => Substitute {
681 contributor_role_form: None,
682 template: vec![
683 SubstituteKey::Editor,
684 SubstituteKey::Translator,
685 SubstituteKey::Title,
686 ],
687 overrides: HashMap::new(),
688 role_substitute: HashMap::new(),
689 unknown_fields: std::collections::BTreeMap::new(),
690 },
691 SubstitutePreset::TitleFirst => Substitute {
692 contributor_role_form: None,
693 template: vec![
694 SubstituteKey::Title,
695 SubstituteKey::Editor,
696 SubstituteKey::Translator,
697 ],
698 overrides: HashMap::new(),
699 role_substitute: HashMap::new(),
700 unknown_fields: std::collections::BTreeMap::new(),
701 },
702 SubstitutePreset::EditorShort => Substitute {
703 contributor_role_form: Some("short".to_string()),
704 template: vec![SubstituteKey::Editor],
705 overrides: HashMap::new(),
706 role_substitute: HashMap::new(),
707 unknown_fields: std::collections::BTreeMap::new(),
708 },
709 SubstitutePreset::EditorLong => Substitute {
710 contributor_role_form: Some("long".to_string()),
711 template: vec![SubstituteKey::Editor],
712 overrides: HashMap::new(),
713 role_substitute: HashMap::new(),
714 unknown_fields: std::collections::BTreeMap::new(),
715 },
716 SubstitutePreset::EditorTranslatorShort => Substitute {
717 contributor_role_form: Some("short".to_string()),
718 template: vec![SubstituteKey::Editor, SubstituteKey::Translator],
719 overrides: HashMap::new(),
720 role_substitute: HashMap::new(),
721 unknown_fields: std::collections::BTreeMap::new(),
722 },
723 SubstitutePreset::EditorTranslatorLong => Substitute {
724 contributor_role_form: Some("long".to_string()),
725 template: vec![SubstituteKey::Editor, SubstituteKey::Translator],
726 overrides: HashMap::new(),
727 role_substitute: HashMap::new(),
728 unknown_fields: std::collections::BTreeMap::new(),
729 },
730 SubstitutePreset::EditorTitleShort => Substitute {
731 contributor_role_form: Some("short".to_string()),
732 template: vec![SubstituteKey::Editor, SubstituteKey::Title],
733 overrides: HashMap::new(),
734 role_substitute: HashMap::new(),
735 unknown_fields: std::collections::BTreeMap::new(),
736 },
737 SubstitutePreset::EditorTitleLong => Substitute {
738 contributor_role_form: Some("long".to_string()),
739 template: vec![SubstituteKey::Editor, SubstituteKey::Title],
740 overrides: HashMap::new(),
741 role_substitute: HashMap::new(),
742 unknown_fields: std::collections::BTreeMap::new(),
743 },
744 SubstitutePreset::EditorTranslatorTitleShort => Substitute {
745 contributor_role_form: Some("short".to_string()),
746 template: vec![
747 SubstituteKey::Editor,
748 SubstituteKey::Translator,
749 SubstituteKey::Title,
750 ],
751 overrides: HashMap::new(),
752 role_substitute: HashMap::new(),
753 unknown_fields: std::collections::BTreeMap::new(),
754 },
755 SubstitutePreset::EditorTranslatorTitleLong => Substitute {
756 contributor_role_form: Some("long".to_string()),
757 template: vec![
758 SubstituteKey::Editor,
759 SubstituteKey::Translator,
760 SubstituteKey::Title,
761 ],
762 overrides: HashMap::new(),
763 role_substitute: HashMap::new(),
764 unknown_fields: std::collections::BTreeMap::new(),
765 },
766 }
767 }
768}
769
770#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
788#[cfg_attr(feature = "schema", derive(JsonSchema))]
789#[serde(rename_all = "kebab-case")]
790#[non_exhaustive]
791pub enum MultilingualPreset {
792 RomanizedTranslated,
803 RomanizedOnly,
811 RomanizedScriptTranslated,
825}
826
827impl MultilingualPreset {
828 pub fn config(self) -> MultilingualConfig {
830 match self {
831 MultilingualPreset::RomanizedTranslated => MultilingualConfig {
832 title_mode: Some(MultilingualMode::Combined),
833 name_mode: Some(MultilingualMode::Transliterated),
834 preferred_script: Some("Latn".to_string()),
835 ..Default::default()
836 },
837 MultilingualPreset::RomanizedOnly => MultilingualConfig {
838 title_mode: Some(MultilingualMode::Transliterated),
839 name_mode: Some(MultilingualMode::Transliterated),
840 preferred_script: Some("Latn".to_string()),
841 ..Default::default()
842 },
843 MultilingualPreset::RomanizedScriptTranslated => {
844 let mut scripts = HashMap::new();
845 scripts.insert(
846 "Han".to_string(),
847 ScriptConfig {
848 use_native_ordering: true,
849 ..Default::default()
850 },
851 );
852 scripts.insert(
853 "Hangul".to_string(),
854 ScriptConfig {
855 use_native_ordering: true,
856 ..Default::default()
857 },
858 );
859 MultilingualConfig {
860 title_mode: Some(MultilingualMode::Pattern(vec![
861 MultilingualSegment {
862 view: MultilingualView::Transliterated,
863 wrap: SegmentWrap::None,
864 },
865 MultilingualSegment {
866 view: MultilingualView::OriginalScript,
867 wrap: SegmentWrap::None,
868 },
869 MultilingualSegment {
870 view: MultilingualView::Translated,
871 wrap: SegmentWrap::Brackets,
872 },
873 ])),
874 name_mode: Some(MultilingualMode::Pattern(vec![
875 MultilingualSegment {
876 view: MultilingualView::Transliterated,
877 wrap: SegmentWrap::None,
878 },
879 MultilingualSegment {
880 view: MultilingualView::OriginalScript,
881 wrap: SegmentWrap::None,
882 },
883 ])),
884 preferred_script: Some("Latn".to_string()),
885 scripts,
886 ..Default::default()
887 }
888 }
889 }
890 }
891}
892
893#[derive(Debug, Clone, Serialize, PartialEq)]
909#[cfg_attr(feature = "schema", derive(JsonSchema))]
910#[serde(untagged)]
911pub enum MultilingualConfigEntry {
912 Preset(MultilingualPreset),
914 Explicit(Box<MultilingualConfig>),
916}
917
918impl MultilingualConfigEntry {
919 pub fn resolve(self) -> MultilingualConfig {
921 match self {
922 MultilingualConfigEntry::Preset(p) => p.config(),
923 MultilingualConfigEntry::Explicit(c) => *c,
924 }
925 }
926}
927
928impl<'de> serde::Deserialize<'de> for MultilingualConfigEntry {
939 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
940 where
941 D: serde::Deserializer<'de>,
942 {
943 struct EntryVisitor;
944
945 impl<'de> serde::de::Visitor<'de> for EntryVisitor {
946 type Value = MultilingualConfigEntry;
947
948 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
949 write!(f, "a multilingual preset name or an explicit config block")
950 }
951
952 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
953 let preset =
954 MultilingualPreset::deserialize(serde::de::value::StrDeserializer::new(v))?;
955 Ok(MultilingualConfigEntry::Preset(preset))
956 }
957
958 fn visit_map<A: serde::de::MapAccess<'de>>(
959 self,
960 map: A,
961 ) -> Result<Self::Value, A::Error> {
962 let config = MultilingualConfig::deserialize(
963 serde::de::value::MapAccessDeserializer::new(map),
964 )?;
965 Ok(MultilingualConfigEntry::Explicit(Box::new(config)))
966 }
967 }
968
969 deserializer.deserialize_any(EntryVisitor)
970 }
971}
972
973#[cfg(test)]
974#[allow(
975 clippy::unwrap_used,
976 clippy::expect_used,
977 clippy::panic,
978 clippy::indexing_slicing,
979 clippy::todo,
980 clippy::unimplemented,
981 clippy::unreachable,
982 clippy::get_unwrap,
983 reason = "Panicking is acceptable and often desired in tests."
984)]
985mod tests {
986 use super::*;
987
988 #[test]
989 fn test_contributor_preset_apa() {
990 let config = ContributorPreset::Apa.config();
991 assert_eq!(config.and, Some(AndOptions::Symbol));
992 assert_eq!(config.display_as_sort, Some(DisplayAsSort::First));
993 let shorten = config.shorten.unwrap();
994 assert_eq!(shorten.min, 21);
995 assert_eq!(shorten.use_first, 19);
996 }
997
998 #[test]
999 fn test_contributor_preset_chicago() {
1000 let config = ContributorPreset::Chicago.config();
1001 assert_eq!(config.and, Some(AndOptions::Text));
1002 assert_eq!(config.display_as_sort, Some(DisplayAsSort::First));
1003 }
1004
1005 #[test]
1006 fn test_contributor_preset_vancouver() {
1007 let config = ContributorPreset::Vancouver.config();
1008 assert_eq!(config.and, Some(AndOptions::None));
1009 assert_eq!(config.display_as_sort, Some(DisplayAsSort::All));
1010 }
1011
1012 #[test]
1013 fn test_contributor_preset_springer() {
1014 let config = ContributorPreset::Springer.config();
1015 assert_eq!(config.and, Some(AndOptions::None));
1016 assert_eq!(config.display_as_sort, Some(DisplayAsSort::All));
1017 assert_eq!(config.sort_separator, Some(" ".to_string()));
1018 let shorten = config.shorten.unwrap();
1019 assert_eq!(shorten.min, 5);
1020 assert_eq!(shorten.use_first, 3);
1021 }
1022
1023 #[test]
1024 fn test_contributor_preset_numeric_compact() {
1025 let config = ContributorPreset::NumericCompact.config();
1026 assert_eq!(config.and, Some(AndOptions::None));
1027 assert_eq!(config.display_as_sort, Some(DisplayAsSort::All));
1028 assert_eq!(config.sort_separator, Some(" ".to_string()));
1029 assert_eq!(
1030 config.demote_non_dropping_particle,
1031 Some(DemoteNonDroppingParticle::SortOnly)
1032 );
1033 let shorten = config.shorten.unwrap();
1034 assert_eq!(shorten.min, 7);
1035 assert_eq!(shorten.use_first, 6);
1036 }
1037
1038 #[test]
1039 fn test_contributor_preset_numeric_all_authors() {
1040 let config = ContributorPreset::NumericAllAuthors.config();
1041 assert_eq!(config.and, Some(AndOptions::None));
1042 assert_eq!(config.display_as_sort, Some(DisplayAsSort::All));
1043 assert_eq!(config.sort_separator, Some(" ".to_string()));
1044 assert_eq!(config.initialize_with, Some("".to_string()));
1045 assert_eq!(
1046 config.demote_non_dropping_particle,
1047 Some(DemoteNonDroppingParticle::Never)
1048 );
1049 assert!(config.shorten.is_none());
1050 }
1051
1052 #[test]
1053 fn test_contributor_preset_numeric_given_dot() {
1054 let config = ContributorPreset::NumericGivenDot.config();
1055 assert_eq!(config.and, Some(AndOptions::None));
1056 assert_eq!(config.display_as_sort, Some(DisplayAsSort::None));
1057 assert_eq!(config.initialize_with, Some(".".to_string()));
1058 assert_eq!(
1059 config.demote_non_dropping_particle,
1060 Some(DemoteNonDroppingParticle::SortOnly)
1061 );
1062 assert_eq!(
1063 config.delimiter_precedes_last,
1064 Some(DelimiterPrecedesLast::Always)
1065 );
1066 }
1067
1068 #[test]
1069 fn test_date_preset_long() {
1070 let config = DatePreset::Long.config();
1071 assert_eq!(config.month, MonthFormat::Long);
1072 assert!(config.uncertainty_marker.is_some());
1073 }
1074
1075 #[test]
1076 fn test_date_preset_iso() {
1077 let config = DatePreset::Iso.config();
1078 assert_eq!(config.month, MonthFormat::Numeric);
1079 assert!(config.uncertainty_marker.is_none());
1081 assert!(config.approximation_marker.is_none());
1082 }
1083
1084 #[test]
1085 fn test_title_preset_apa() {
1086 let config = TitlePreset::Apa.config();
1087 let component = config.component.unwrap();
1089 assert!(component.quote.is_none() || component.quote == Some(false));
1090 let monograph = config.monograph.unwrap();
1092 assert_eq!(monograph.emph, Some(true));
1093 }
1094
1095 #[test]
1096 fn test_title_preset_chicago() {
1097 let config = TitlePreset::Chicago.config();
1098 let component = config.component.unwrap();
1100 assert_eq!(component.quote, Some(true));
1101 let monograph = config.monograph.unwrap();
1103 assert_eq!(monograph.emph, Some(true));
1104 }
1105
1106 #[test]
1107 fn test_preset_yaml_roundtrip() {
1108 let yaml = r#"apa"#;
1109 let preset: ContributorPreset = serde_yaml::from_str(yaml).unwrap();
1110 assert_eq!(preset, ContributorPreset::Apa);
1111
1112 let serialized = serde_yaml::to_string(&preset).unwrap();
1113 assert!(serialized.contains("apa"));
1114 }
1115
1116 #[test]
1117 fn test_all_presets_serialize() {
1118 let contributor_presets = vec![
1120 ContributorPreset::Apa,
1121 ContributorPreset::Chicago,
1122 ContributorPreset::Vancouver,
1123 ContributorPreset::Ieee,
1124 ContributorPreset::Harvard,
1125 ContributorPreset::Springer,
1126 ContributorPreset::NumericCompact,
1127 ContributorPreset::NumericMedium,
1128 ContributorPreset::NumericTight,
1129 ContributorPreset::NumericLarge,
1130 ContributorPreset::NumericAllAuthors,
1131 ContributorPreset::NumericGivenDot,
1132 ContributorPreset::AnnualReviews,
1133 ContributorPreset::MathPhys,
1134 ContributorPreset::SocSciFirst,
1135 ContributorPreset::PhysicsNumeric,
1136 ];
1137 for preset in contributor_presets {
1138 let yaml = serde_yaml::to_string(&preset).unwrap();
1139 let _: ContributorPreset = serde_yaml::from_str(&yaml).unwrap();
1140 }
1141
1142 let date_presets = vec![
1143 DatePreset::Long,
1144 DatePreset::Short,
1145 DatePreset::Numeric,
1146 DatePreset::Iso,
1147 ];
1148 for preset in date_presets {
1149 let yaml = serde_yaml::to_string(&preset).unwrap();
1150 let _: DatePreset = serde_yaml::from_str(&yaml).unwrap();
1151 }
1152
1153 let title_presets = vec![
1154 TitlePreset::Apa,
1155 TitlePreset::Chicago,
1156 TitlePreset::Ieee,
1157 TitlePreset::Humanities,
1158 TitlePreset::JournalEmphasis,
1159 TitlePreset::Scientific,
1160 ];
1161 for preset in title_presets {
1162 let yaml = serde_yaml::to_string(&preset).unwrap();
1163 let _: TitlePreset = serde_yaml::from_str(&yaml).unwrap();
1164 }
1165
1166 let substitute_presets = vec![
1167 SubstitutePreset::Standard,
1168 SubstitutePreset::EditorFirst,
1169 SubstitutePreset::TitleFirst,
1170 SubstitutePreset::EditorShort,
1171 SubstitutePreset::EditorLong,
1172 SubstitutePreset::EditorTranslatorShort,
1173 SubstitutePreset::EditorTranslatorLong,
1174 SubstitutePreset::EditorTitleShort,
1175 SubstitutePreset::EditorTitleLong,
1176 SubstitutePreset::EditorTranslatorTitleShort,
1177 SubstitutePreset::EditorTranslatorTitleLong,
1178 ];
1179 for preset in substitute_presets {
1180 let yaml = serde_yaml::to_string(&preset).unwrap();
1181 let _: SubstitutePreset = serde_yaml::from_str(&yaml).unwrap();
1182 }
1183 }
1184
1185 #[test]
1186 fn test_substitute_preset_standard() {
1187 let config = SubstitutePreset::Standard.config();
1188 assert_eq!(
1189 config.template,
1190 vec![
1191 SubstituteKey::Editor,
1192 SubstituteKey::Title,
1193 SubstituteKey::Translator,
1194 ]
1195 );
1196 }
1197
1198 #[test]
1199 fn test_substitute_preset_title_first() {
1200 let config = SubstitutePreset::TitleFirst.config();
1201 assert_eq!(config.template[0], SubstituteKey::Title);
1202 }
1203}