Skip to content

Commit

Permalink
Add citation tool (#28)
Browse files Browse the repository at this point in the history
* Pull functionality from brainglobe-meta branch

* Pull the tests across from bg-meta branch

* brainglobe -> brainglobe_utils for citation imports

* Add citation.cff for brainglobe-utils (this repo)!

* Adapt tests for python 3.11 functionality

* Update merged CITATION.cff refs

* Mention citation tool in README

* List -f keys as well as supported file extensions

* Automate some repository aliasing

* Apply suggestions from code review

Co-authored-by: Alessandro Felder <[email protected]>

* Switch default output format to text

* Stage with list of brainglobe files that are reference-able(?)

* Remove bibtex-specific comment in general Format class

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update MANIFEST.in

---------

Co-authored-by: Alessandro Felder <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Feb 9, 2024
1 parent 5a2a2d6 commit b9181a5
Show file tree
Hide file tree
Showing 19 changed files with 1,804 additions and 1 deletion.
16 changes: 16 additions & 0 deletions CITATION.cff
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
cff-version: 1.2.0
title: brainglobe-utils
message: >-
If you use this software, please cite it using the
metadata from this file.
type: software
authors:
- given-names: Brainglobe
family-names: Developers
email: [email protected]
repository-code: 'https://github.com/brainglobe/brainglobe-utils'
url: 'https://brainglobe.info/'
abstract: Shared general purpose tools for the BrainGlobe project.
license: BSD-3-Clause
year: 2024
month: 01
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
exclude .pre-commit-config.yaml

include CITATION.cff
include LICENSE
include README.md

Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# brainglobe-utils

Shared general purpose tools for the BrainGlobe project
Shared general purpose tools for the BrainGlobe project, including [citation generation](#citations-for-brainglobe-tools).

## Installation

Expand All @@ -20,3 +20,8 @@ For development, clone this repository and install the dependencies with one of
pip install -e .[dev]
pip install -e .[dev,napari]
```

## Citations for BrainGlobe tools

`brainglobe-utils` comes with the `cite-brainglobe` command line tool, to write citations for BrainGlobe tools for you so you don't need to worry about fetching the data yourself.
You can read about [how to use the tool](https://brainglobe.info/documentation/brainglobe-utils/citation-module.html) on the documentation website.
Empty file.
208 changes: 208 additions & 0 deletions brainglobe_utils/citation/bibtex_fmt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import inspect
import sys
from string import ascii_letters, digits
from typing import Any, ClassVar, Dict

from brainglobe_utils.citation.format import Format


class BibTexEntry(Format):
"""
An abstract base class for generating BibTex entries from
CITATION.cff yaml-content.
Constructed by passing a dict containing the yaml-processed
content of the CITATION.cff file to the constructor.
Required fields are checked for on instantiation.
Missing optional fields will be set to None if not provided.
Parameters
----------
information : Dict[str, str]
yaml-processed content of CITATION.cff.
cite_key : str, default = "BrainGlobeReference"
Citation key to add to the BibTex reference that is generated.
warn_on_not_used: bool, default = False
If True, warn the user about the fields in the information
input that are not required nor optional, so are ignored.
Attributes
----------
cite_key : str
The citation key that appears in the BibTex reference
"""

author_separator: ClassVar[str] = " and "
cite_key: str

@classmethod
def validate_citation_key(cls, key: str) -> bool:
"""
Return True if the citation key provided is valid for use,
otherwise return False.
Valid citation keys may contain;
- Alphanumeric characters,
- Underscores (_),
- Single dashes (-),
- Semicolons (:),
and no others.
"""
bad_characters = key.lower()
for valid_character in ascii_letters + digits + "_-:":
bad_characters = bad_characters.replace(valid_character, "")

if bad_characters:
# Some characters in the string provided are not permitted,
# as we have removed all of the permitted characters.
return False
return True

def __init__(
self,
information: Dict[str, Any],
cite_key: str = "BrainGlobeReference",
warn_on_not_used: bool = False,
) -> None:
""" """
# So we don't delete information that we may need in other places
# (C++ memory sharing rights plz Python)
information = information.copy()

# Add the citation key if provided,
# or use the default otherwise
if self.validate_citation_key(cite_key):
self.cite_key = cite_key
else:
raise ValueError(
f"Citation key {cite_key} is not valid."
" Citation keys may only be composed of"
"alphanumeric characters, digits, '_', '-', and ':'"
)

# If type information is available, ensure that we are reading
# into the correct reference type!
if "type" in information.keys():
assert information["type"] == self.entry_type(), (
"Attempting to read reference of type"
f" {information['type']} into {self.entry_type()}"
)
# Remove type field from information dict,
# so we don't try to assign it to a field later.
information.pop("type")

super().__init__(information, warn_on_not_used=warn_on_not_used)

return

def generate_ref_string(self) -> str:
"""
Generate a string that encodes the reference, in preparation for
writing to an output format.
"""
output_string = f"@{self.entry_type()}{{{self.cite_key},\n"

# Tracks current indentation level
indent_level = 1

# Required fields are guaranteed to exist
for req_field in self.required:
output_string += (
self.indent_character * indent_level
+ f'{req_field} = "{getattr(self, req_field)}",\n'
)

# Optional fields may be skipped
for opt_field in self.optional:
if getattr(self, opt_field):
output_string += (
self.indent_character * indent_level
+ f'{opt_field} = "{getattr(self, opt_field)}",\n'
)

indent_level -= 1
output_string += "}"

return output_string


class Article(BibTexEntry):
"""
Derived class for writing BibTex references to articles.
"""

required = ["authors", "title", "journal", "year"]
optional = [
"volume",
"number",
"pages",
"month",
"note",
"doi",
"issn",
"zblnumber",
"eprint",
]


class Software(BibTexEntry):
"""
Derived class for writing BibTex references to software.
"""

required = ["authors", "title", "url", "year"]
optional = [
"abstract",
"date",
"doi",
"eprint",
"eprintclass",
"eprinttype",
"file",
"hal_id",
"hal_version",
"institution",
"license",
"month",
"note",
"organization",
"publisher",
"related",
"relatedtype",
"relatedstring",
"repository",
"swhid",
"urldate",
"version",
]


def supported_bibtex_entry_types() -> Dict[str, BibTexEntry]:
"""
Create a dict of all the classes in this module that can be used
to write a bibtex reference of a particular entry type.
keys are the entry type as it will appear in the .tex entry.
values are the corresponding derived class to use when writing a
reference of that type.
Returns
-------
Dict[str, @_BibTexEntry]
Dict of classes derived from BibTexEntry that can handle entry
types, indexed by the entry type they support.
"""
this_module = sys.modules[__name__]

dict_of_formats: Dict[str, BibTexEntry] = {
cls.entry_type(): cls
for cls in (
getattr(this_module, name)
for name, _ in inspect.getmembers(this_module, inspect.isclass)
if name != "BibTexEntry" and name != "Format"
)
if issubclass(cls, BibTexEntry)
}

return dict_of_formats
Loading

0 comments on commit b9181a5

Please sign in to comment.