Skip to content

Commit

Permalink
Format bibtex fields using macro
Browse files Browse the repository at this point in the history
  • Loading branch information
ja573 committed Feb 22, 2024
1 parent 50c1a3f commit cc86131
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 55 deletions.
82 changes: 27 additions & 55 deletions thoth-export-server/src/bibtex/bibtex_thoth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,61 +76,33 @@ impl fmt::Display for BibtexThothEntry {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Cite key must be unique and alphanumeric ("-_:" also permitted)
// Most records will have an ISBN, but fall back on publication date if not found
let mut citekey = self.isbn.clone().unwrap_or_default();
if citekey.is_empty() {
citekey = format!("{}-{}-{}", self.year, self.month, self.day);
}
writeln!(f, "@{}{{{},", self.entry_type, citekey)?;
write!(f, "\ttitle = {{{}}}", self.title)?;
if let Some(shorttitle) = &self.shorttitle {
write!(f, ",\n\tshorttitle = {{{shorttitle}}}")?;
}
if let Some(author) = &self.author {
write!(f, ",\n\tauthor = {{{author}}}")?;
}
if let Some(editor) = &self.editor {
write!(f, ",\n\teditor = {{{editor}}}")?;
}
write!(f, ",\n\tyear = {}", self.year)?;
write!(f, ",\n\tmonth = {}", self.month)?;
write!(f, ",\n\tday = {}", self.day)?;
write!(f, ",\n\tpublisher = {{{}}}", self.publisher)?;
if let Some(address) = &self.address {
write!(f, ",\n\taddress = {{{address}}}")?;
}
if let Some(series) = &self.series {
write!(f, ",\n\tseries = {{{series}}}")?;
}
if let Some(volume) = &self.volume {
write!(f, ",\n\tvolume = {volume}")?;
}
if let Some(booktitle) = &self.booktitle {
write!(f, ",\n\tbooktitle = {{{booktitle}}}")?;
}
if let Some(chapter) = &self.chapter {
write!(f, ",\n\tchapter = {chapter}")?;
}
if let Some(pages) = &self.pages {
write!(f, ",\n\tpages = {{{pages}}}")?;
}
if let Some(doi) = &self.doi {
write!(f, ",\n\tdoi = {{{doi}}}")?;
}
if let Some(isbn) = &self.isbn {
write!(f, ",\n\tisbn = {{{isbn}}}")?;
}
if let Some(issn) = &self.issn {
write!(f, ",\n\tissn = {{{issn}}}")?;
}
if let Some(url) = &self.url {
write!(f, ",\n\turl = {{{url}}}")?;
}
if let Some(copyright) = &self.copyright {
write!(f, ",\n\tcopyright = {{{copyright}}}")?;
}
if let Some(long_abstract) = &self.long_abstract {
write!(f, ",\n\tabstract = {{{long_abstract}}}")?;
}
let citekey = self
.isbn
.clone()
.unwrap_or_else(|| format!("{}-{}-{}", self.year, self.month, self.day));
write!(f, "@{}{{{}", self.entry_type, citekey)?;

write_field!(f, self, title);
write_optional_field!(f, self, shorttitle);
write_optional_field!(f, self, author);
write_optional_field!(f, self, editor);
write_field!(f, self, year, i64);
write_field!(f, self, month, i64);
write_field!(f, self, day, i64);
write_field!(f, self, publisher);
write_optional_field!(f, self, address);
write_optional_field!(f, self, series);
write_optional_field!(f, self, volume, i64);
write_optional_field!(f, self, booktitle);
write_optional_field!(f, self, chapter, i64);
write_optional_field!(f, self, pages);
write_optional_field!(f, self, doi);
write_optional_field!(f, self, isbn);
write_optional_field!(f, self, issn);
write_optional_field!(f, self, url);
write_optional_field!(f, self, copyright);
write_optional_field!(f, self, long_abstract, "abstract");

writeln!(f, "\n}}")
}
}
Expand Down
119 changes: 119 additions & 0 deletions thoth-export-server/src/bibtex/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,124 @@ pub(crate) trait BibtexEntry<T: BibtexSpecification> {
fn bibtex_entry(&self, w: &mut Vec<u8>) -> ThothResult<()>;
}

/// Macro to write a non-optional field in BibTeX format to a formatter.
///
/// This macro writes a non-optional field of a struct to a formatter in BibTeX format,
/// including the field name and its value. If a custom field name is not provided,
/// the stringified version of the field name will be used.
///
/// By default, the value will be enclosed in braces `{}`, which is intended to be used
/// with `String` or `Option<String>`. By passing a type onto the macro we instruct it
/// not to enclose the value, which is intended to be used with `i64` and `Option<i64>`.
///
/// # Examples
///
/// ```
/// # use std::fmt::Write;
/// # use thoth_export_server::write_field;
/// # struct BibtexThothEntry {
/// # title: String,
/// # year: i64,
/// # }
/// # fn run() -> Result<(), std::fmt::Error> {
/// # let mut f = String::new();
/// # let entry = BibtexThothEntry { title: "Example Title".to_string(), year: 2024 };
/// write_field!(f, entry, title);
/// assert_eq!(f, ",\n\ttitle = {Example Title}");
/// f.clear();
///
/// write_field!(f, entry, title, "abbr_title");
/// assert_eq!(f, ",\n\tabbr_title = {Example Title}");
/// f.clear();
///
/// write_field!(f, entry, year, i64);
/// assert_eq!(f, ",\n\tyear = 2024");
/// f.clear();
///
/// write_field!(f, entry, year, "date", i64);
/// assert_eq!(f, ",\n\tdate = 2024");
/// # Ok(())
/// # }
/// ```
#[macro_export]
macro_rules! write_field {
($f:ident, $self:ident, $field:ident) => {
write!($f, ",\n\t{} = {{{}}}", stringify!($field), $self.$field)?;
};
($f:ident, $self:ident, $field:ident, $t:ty) => {
write!($f, ",\n\t{} = {}", stringify!($field), $self.$field)?;
};
($f:ident, $self:ident, $field:ident, $field_name:expr) => {
write!($f, ",\n\t{} = {{{}}}", $field_name, $self.$field)?;
};
($f:ident, $self:ident, $field:ident, $field_name:expr, $t:ty) => {
write!($f, ",\n\t{} = {}", $field_name, $self.$field)?;
};
}

/// Macro to write an optional field in BibTeX format to a formatter.
///
/// This macro writes an optional field of a struct to a formatter in BibTeX format,
/// including the field name and its value, only if the field has a value.
/// If a custom field name is not provided, the stringified version of the field
/// name will be used.
///
/// By default, the value will be enclosed in braces `{}`, which is intended to be used
/// with `String` or `Option<String>`. By passing a type onto the macro we instruct it
/// not to enclose the value, which is intended to be used with `i64` and `Option<i64>`.
///
/// # Examples
///
/// ```
/// # use std::fmt::Write;
/// # use thoth_export_server::write_optional_field;
/// # struct BibtexThothEntry {
/// # title: Option<String>,
/// # year: Option<i64>,
/// # }
/// # fn run() -> Result<(), std::fmt::Error> {
/// # let mut f = String::new();
/// # let entry = BibtexThothEntry { title: Some("Example Title".to_string()), year: Some(2024) };
/// write_optional_field!(f, entry, title);
/// assert_eq!(f, ",\n\ttitle = {Example Title}");
/// f.clear();
///
/// write_optional_field!(f, entry, title, "abbr_title");
/// assert_eq!(f, ",\n\tabbr_title = {Example Title}");
/// f.clear();
///
/// write_optional_field!(f, entry, year, i64);
/// assert_eq!(f, ",\n\tyear = 2024");
/// f.clear();
///
/// write_optional_field!(f, entry, year, "date", i64);
/// assert_eq!(f, ",\n\tdate = 2024");
/// # Ok(())
/// # }
/// ```
#[macro_export]
macro_rules! write_optional_field {
($f:ident, $self:ident, $field:ident) => {
if let Some(value) = &$self.$field {
write!($f, ",\n\t{} = {{{}}}", stringify!($field), value)?;
}
};
($f:ident, $self:ident, $field:ident, $t:ty) => {
if let Some(value) = &$self.$field {
write!($f, ",\n\t{} = {}", stringify!($field), value)?;
}
};
($f:ident, $self:ident, $field:ident, $field_name:expr) => {
if let Some(value) = &$self.$field {
write!($f, ",\n\t{} = {{{}}}", $field_name, value)?;
}
};
($f:ident, $self:ident, $field:ident, $field_name:expr, $t:ty) => {
if let Some(value) = &$self.$field {
write!($f, ",\n\t{} = {}", $field_name, value)?;
}
};
}

mod bibtex_thoth;
pub(crate) use bibtex_thoth::BibtexThoth;

0 comments on commit cc86131

Please sign in to comment.