1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "camelCase")]
8pub struct Bibliography {
9 #[serde(default)]
11 pub style: CitationStyle,
12
13 pub entries: Vec<BibliographyEntry>,
15}
16
17impl Bibliography {
18 #[must_use]
20 pub fn new(style: CitationStyle) -> Self {
21 Self {
22 style,
23 entries: Vec::new(),
24 }
25 }
26
27 pub fn add_entry(&mut self, entry: BibliographyEntry) {
29 self.entries.push(entry);
30 }
31
32 #[must_use]
34 pub fn get(&self, id: &str) -> Option<&BibliographyEntry> {
35 self.entries.iter().find(|e| e.id == id)
36 }
37
38 #[must_use]
40 pub fn contains(&self, id: &str) -> bool {
41 self.entries.iter().any(|e| e.id == id)
42 }
43
44 #[must_use]
46 pub fn len(&self) -> usize {
47 self.entries.len()
48 }
49
50 #[must_use]
52 pub fn is_empty(&self) -> bool {
53 self.entries.is_empty()
54 }
55}
56
57impl Default for Bibliography {
58 fn default() -> Self {
59 Self::new(CitationStyle::default())
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, strum::Display)]
65#[serde(rename_all = "lowercase")]
66pub enum CitationStyle {
67 #[default]
69 #[strum(serialize = "APA")]
70 Apa,
71 #[strum(serialize = "MLA")]
73 Mla,
74 Chicago,
76 #[strum(serialize = "IEEE")]
78 Ieee,
79 Harvard,
81 Vancouver,
83 #[strum(serialize = "ACM")]
85 Acm,
86 Custom,
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
92#[serde(rename_all = "camelCase")]
93pub struct BibliographyEntry {
94 pub id: String,
96
97 pub entry_type: EntryType,
99
100 pub title: String,
102
103 #[serde(default, skip_serializing_if = "Vec::is_empty")]
105 pub authors: Vec<Author>,
106
107 #[serde(default, skip_serializing_if = "Option::is_none")]
109 pub issued: Option<PartialDate>,
110
111 #[serde(default, skip_serializing_if = "Option::is_none")]
113 pub container_title: Option<String>,
114
115 #[serde(default, skip_serializing_if = "Option::is_none")]
117 pub volume: Option<String>,
118
119 #[serde(default, skip_serializing_if = "Option::is_none")]
121 pub issue: Option<String>,
122
123 #[serde(default, skip_serializing_if = "Option::is_none")]
125 pub page: Option<String>,
126
127 #[serde(default, skip_serializing_if = "Option::is_none")]
129 pub doi: Option<String>,
130
131 #[serde(default, skip_serializing_if = "Option::is_none")]
133 pub url: Option<String>,
134
135 #[serde(default, skip_serializing_if = "Option::is_none")]
137 pub isbn: Option<String>,
138
139 #[serde(default, skip_serializing_if = "Option::is_none")]
141 pub issn: Option<String>,
142
143 #[serde(default, skip_serializing_if = "Option::is_none")]
145 pub publisher: Option<String>,
146
147 #[serde(default, skip_serializing_if = "Option::is_none")]
149 pub publisher_place: Option<String>,
150
151 #[serde(default, skip_serializing_if = "Option::is_none")]
153 pub edition: Option<String>,
154
155 #[serde(default, skip_serializing_if = "Vec::is_empty")]
157 pub editors: Vec<Author>,
158
159 #[serde(default, skip_serializing_if = "Option::is_none")]
161 pub abstract_text: Option<String>,
162
163 #[serde(default, skip_serializing_if = "Vec::is_empty")]
165 pub keywords: Vec<String>,
166
167 #[serde(default, skip_serializing_if = "Option::is_none")]
169 pub language: Option<String>,
170
171 #[serde(default, skip_serializing_if = "Option::is_none")]
173 pub accessed: Option<PartialDate>,
174
175 #[serde(default, skip_serializing_if = "Option::is_none")]
177 pub note: Option<String>,
178}
179
180impl BibliographyEntry {
181 #[must_use]
183 pub fn new(id: impl Into<String>, entry_type: EntryType, title: impl Into<String>) -> Self {
184 Self {
185 id: id.into(),
186 entry_type,
187 title: title.into(),
188 authors: Vec::new(),
189 issued: None,
190 container_title: None,
191 volume: None,
192 issue: None,
193 page: None,
194 doi: None,
195 url: None,
196 isbn: None,
197 issn: None,
198 publisher: None,
199 publisher_place: None,
200 edition: None,
201 editors: Vec::new(),
202 abstract_text: None,
203 keywords: Vec::new(),
204 language: None,
205 accessed: None,
206 note: None,
207 }
208 }
209
210 #[must_use]
212 pub fn with_author(mut self, author: Author) -> Self {
213 self.authors.push(author);
214 self
215 }
216
217 #[must_use]
219 pub fn with_authors(mut self, authors: Vec<Author>) -> Self {
220 self.authors = authors;
221 self
222 }
223
224 #[must_use]
226 pub fn with_issued(mut self, date: PartialDate) -> Self {
227 self.issued = Some(date);
228 self
229 }
230
231 #[must_use]
233 pub fn with_container(mut self, container: impl Into<String>) -> Self {
234 self.container_title = Some(container.into());
235 self
236 }
237
238 #[must_use]
240 pub fn with_volume_issue(mut self, volume: impl Into<String>, issue: Option<String>) -> Self {
241 self.volume = Some(volume.into());
242 self.issue = issue;
243 self
244 }
245
246 #[must_use]
248 pub fn with_pages(mut self, pages: impl Into<String>) -> Self {
249 self.page = Some(pages.into());
250 self
251 }
252
253 #[must_use]
255 pub fn with_doi(mut self, doi: impl Into<String>) -> Self {
256 self.doi = Some(doi.into());
257 self
258 }
259
260 #[must_use]
262 pub fn with_url(mut self, url: impl Into<String>) -> Self {
263 self.url = Some(url.into());
264 self
265 }
266
267 #[must_use]
269 pub fn with_publisher(mut self, publisher: impl Into<String>, place: Option<String>) -> Self {
270 self.publisher = Some(publisher.into());
271 self.publisher_place = place;
272 self
273 }
274}
275
276#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, strum::Display)]
278#[serde(rename_all = "camelCase")]
279#[strum(serialize_all = "lowercase")]
280pub enum EntryType {
281 Article,
283 Book,
285 Chapter,
287 Conference,
289 Thesis,
291 Report,
293 Webpage,
295 Patent,
297 Dataset,
299 Software,
301 #[strum(serialize = "legal-case")]
303 LegalCase,
304 Legislation,
306 Personal,
308 Manuscript,
310 Other,
312}
313
314#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
316#[serde(rename_all = "camelCase")]
317pub struct Author {
318 #[serde(default, skip_serializing_if = "Option::is_none")]
320 pub given: Option<String>,
321
322 #[serde(default, skip_serializing_if = "Option::is_none")]
324 pub family: Option<String>,
325
326 #[serde(default, skip_serializing_if = "Option::is_none")]
328 pub literal: Option<String>,
329
330 #[serde(default, skip_serializing_if = "Option::is_none")]
332 pub orcid: Option<String>,
333
334 #[serde(default, skip_serializing_if = "Option::is_none")]
336 pub affiliation: Option<String>,
337}
338
339impl Author {
340 #[must_use]
342 pub fn new(given: impl Into<String>, family: impl Into<String>) -> Self {
343 Self {
344 given: Some(given.into()),
345 family: Some(family.into()),
346 literal: None,
347 orcid: None,
348 affiliation: None,
349 }
350 }
351
352 #[must_use]
354 pub fn literal(name: impl Into<String>) -> Self {
355 Self {
356 given: None,
357 family: None,
358 literal: Some(name.into()),
359 orcid: None,
360 affiliation: None,
361 }
362 }
363
364 #[must_use]
366 pub fn with_orcid(mut self, orcid: impl Into<String>) -> Self {
367 self.orcid = Some(orcid.into());
368 self
369 }
370
371 #[must_use]
373 pub fn with_affiliation(mut self, affiliation: impl Into<String>) -> Self {
374 self.affiliation = Some(affiliation.into());
375 self
376 }
377
378 #[must_use]
380 pub fn display_name(&self) -> String {
381 if let Some(literal) = &self.literal {
382 return literal.clone();
383 }
384 match (&self.family, &self.given) {
385 (Some(family), Some(given)) => format!("{family}, {given}"),
386 (Some(family), None) => family.clone(),
387 (None, Some(given)) => given.clone(),
388 (None, None) => String::new(),
389 }
390 }
391}
392
393#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
395#[serde(rename_all = "camelCase")]
396pub struct PartialDate {
397 pub year: i32,
399
400 #[serde(default, skip_serializing_if = "Option::is_none")]
402 pub month: Option<u8>,
403
404 #[serde(default, skip_serializing_if = "Option::is_none")]
406 pub day: Option<u8>,
407
408 #[serde(default, skip_serializing_if = "Option::is_none")]
410 pub season: Option<String>,
411}
412
413impl PartialDate {
414 #[must_use]
416 pub const fn year(year: i32) -> Self {
417 Self {
418 year,
419 month: None,
420 day: None,
421 season: None,
422 }
423 }
424
425 #[must_use]
427 pub const fn year_month(year: i32, month: u8) -> Self {
428 Self {
429 year,
430 month: Some(month),
431 day: None,
432 season: None,
433 }
434 }
435
436 #[must_use]
438 pub const fn full(year: i32, month: u8, day: u8) -> Self {
439 Self {
440 year,
441 month: Some(month),
442 day: Some(day),
443 season: None,
444 }
445 }
446
447 #[must_use]
449 pub fn seasonal(year: i32, season: impl Into<String>) -> Self {
450 Self {
451 year,
452 month: None,
453 day: None,
454 season: Some(season.into()),
455 }
456 }
457}
458
459impl std::fmt::Display for PartialDate {
460 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
461 if let Some(season) = &self.season {
462 return write!(f, "{} {}", season, self.year);
463 }
464 match (self.month, self.day) {
465 (Some(month), Some(day)) => write!(f, "{}-{:02}-{:02}", self.year, month, day),
466 (Some(month), None) => write!(f, "{}-{:02}", self.year, month),
467 _ => write!(f, "{}", self.year),
468 }
469 }
470}