diff --git a/.gitignore b/.gitignore index 420650e7c..5ef011927 100644 --- a/.gitignore +++ b/.gitignore @@ -105,3 +105,6 @@ ENV/ # IDE .idea .vscode + +# Vim temp files +*.swp diff --git a/README.dev.md b/README.dev.md index 4bdbd398d..f04d17937 100644 --- a/README.dev.md +++ b/README.dev.md @@ -66,12 +66,13 @@ Tests pertaining to a specific exporter have been marked accordingly with one of `pytest` configuration section in `pyproject.toml`): 1. `apalike` -2. `bibtex` -3. `codemeta` -4. `endnote` -5. `ris` -6. `schemaorg` -7. `zenodo` +2. `biblatex` +3. `bibtex` +4. `codemeta` +5. `endnote` +6. `ris` +7. `schemaorg` +8. `zenodo` Additionally, there are markers for CLI tests and for library tests: diff --git a/pyproject.toml b/pyproject.toml index 5b819f115..032ac389d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -110,6 +110,7 @@ src_paths = ["src", "tests"] addopts = "--no-cov --verbose --maxfail=1 --strict-markers --import-mode=importlib --cache-clear --strict-config" markers = [ "apalike: Tests pertaining to 'apalike' export behavior", + "biblatex: Tests pertaining to 'biblatex' export behavior", "bibtex: Tests pertaining to 'bibtex' export behavior", "cli: Tests pertaining to cffconvert's command line interface", "codemeta: Tests pertaining to 'codemeta' export behavior", diff --git a/src/cffconvert/cli/cli.py b/src/cffconvert/cli/cli.py index ab3e4b9be..4f905c24f 100644 --- a/src/cffconvert/cli/cli.py +++ b/src/cffconvert/cli/cli.py @@ -21,6 +21,7 @@ "outputformat": { "type": click.Choice([ "apalike", + "biblatex", "bibtex", "cff", "codemeta", diff --git a/src/cffconvert/cli/validate_or_write_output.py b/src/cffconvert/cli/validate_or_write_output.py index d437e39ef..c3c62a4c5 100644 --- a/src/cffconvert/cli/validate_or_write_output.py +++ b/src/cffconvert/cli/validate_or_write_output.py @@ -26,6 +26,7 @@ def validate_or_write_output(outfile, outputformat, validate_only, citation, ver ctx.exit(1) outstr = { "apalike": citation.as_apalike, + "biblatex": citation.as_biblatex, "bibtex": citation.as_bibtex, "cff": citation.as_cff, "codemeta": citation.as_codemeta, diff --git a/src/cffconvert/gcloud/gcloud.py b/src/cffconvert/gcloud/gcloud.py index e326f1c5e..1266dc2aa 100644 --- a/src/cffconvert/gcloud/gcloud.py +++ b/src/cffconvert/gcloud/gcloud.py @@ -64,13 +64,14 @@ def cffconvert(request): outstr += f"Data passes validation according to Citation File Format schema version {citation.cffversion}." return Response(outstr, mimetype=mimetype_plain) - acceptable_output_formats = ["apalike", "bibtex", "cff", "codemeta", "endnote", "schema.org", "ris", "zenodo"] + acceptable_output_formats = ["apalike", "biblatex", "bibtex", "cff", "codemeta", "endnote", "schema.org", "ris", "zenodo"] if outputformat not in acceptable_output_formats: outstr += f"\n\n'outputformat' should be one of [{', '.join(acceptable_output_formats)}]" return Response(outstr, mimetype=mimetype_plain) outstr += { "apalike": citation.as_apalike, + "biblatex": citation.as_biblatex, "bibtex": citation.as_bibtex, "cff": citation.as_cff, "codemeta": citation.as_codemeta, diff --git a/src/cffconvert/lib/cff_1_0_x/biblatex.py b/src/cffconvert/lib/cff_1_0_x/biblatex.py new file mode 100644 index 000000000..006beaf07 --- /dev/null +++ b/src/cffconvert/lib/cff_1_0_x/biblatex.py @@ -0,0 +1,43 @@ +from cffconvert.lib.cff_1_x_x.authors.biblatex import BiblatexAuthor +from cffconvert.lib.cff_1_x_x.biblatex import BiblatexObjectShared as Shared +from cffconvert.lib.cff_1_x_x.urls.biblatex import BiblatexUrl + + +class BiblatexObject(Shared): + + supported_cff_versions = [ + "1.0.1", + "1.0.2", + "1.0.3" + ] + + def add_author(self): + authors_cff = self.cffobj.get("authors", []) + authors_biblatex = [BiblatexAuthor(a).as_string() for a in authors_cff] + authors_biblatex_filtered = [a for a in authors_biblatex if a is not None] + self.author = "author = {" + " and ".join(authors_biblatex_filtered) + "}" + return self + + def add_doi(self): + if "doi" in self.cffobj.keys(): + self.doi = "doi = {" + self.cffobj["doi"] + "}" + return self + + def add_month(self): + if "date-released" in self.cffobj.keys(): + self.month = "month = {" + str(self.cffobj["date-released"].month) + "}" + return self + + def add_url(self): + self.url = BiblatexUrl(self.cffobj).as_string() + return self + + def add_year(self): + if "date-released" in self.cffobj.keys(): + self.year = "year = {" + str(self.cffobj["date-released"].year) + "}" + return self + + def add_date(self): + if "date-released" in self.cffobj.keys(): + self.date = "date = {" + self.cffobj["date-released"].isoformat() + "}" + return self diff --git a/src/cffconvert/lib/cff_1_0_x/citation.py b/src/cffconvert/lib/cff_1_0_x/citation.py index dada54502..ce483c931 100644 --- a/src/cffconvert/lib/cff_1_0_x/citation.py +++ b/src/cffconvert/lib/cff_1_0_x/citation.py @@ -3,6 +3,7 @@ from ruamel.yaml import SafeConstructor from ruamel.yaml import YAML from cffconvert.lib.cff_1_0_x.apalike import ApalikeObject +from cffconvert.lib.cff_1_0_x.biblatex import BiblatexObject from cffconvert.lib.cff_1_0_x.bibtex import BibtexObject from cffconvert.lib.cff_1_0_x.codemeta import CodemetaObject from cffconvert.lib.cff_1_0_x.endnote import EndnoteObject @@ -57,6 +58,9 @@ def _parse(self): def as_apalike(self): return ApalikeObject(self.cffobj).as_string() + def as_biblatex(self): + return BiblatexObject(self.cffobj).as_string() + def as_bibtex(self): return BibtexObject(self.cffobj).as_string() diff --git a/src/cffconvert/lib/cff_1_1_x/biblatex.py b/src/cffconvert/lib/cff_1_1_x/biblatex.py new file mode 100644 index 000000000..6936fcdfc --- /dev/null +++ b/src/cffconvert/lib/cff_1_1_x/biblatex.py @@ -0,0 +1,47 @@ +from cffconvert.lib.cff_1_x_x.authors.biblatex import BiblatexAuthor +from cffconvert.lib.cff_1_x_x.biblatex import BiblatexObjectShared as Shared +from cffconvert.lib.cff_1_x_x.urls.biblatex import BiblatexUrl + + +class BiblatexObject(Shared): + + supported_cff_versions = [ + "1.1.0" + ] + + def add_author(self): + authors_cff = self.cffobj.get("authors", []) + authors_biblatex = [BiblatexAuthor(a).as_string() for a in authors_cff] + authors_biblatex_filtered = [a for a in authors_biblatex if a is not None] + self.author = "author = {" + " and ".join(authors_biblatex_filtered) + "}" + return self + + def add_doi(self): + if "doi" in self.cffobj.keys(): + self.doi = "doi = {" + self.cffobj["doi"] + "}" + if "identifiers" in self.cffobj.keys(): + identifiers = self.cffobj["identifiers"] + for identifier in identifiers: + if identifier["type"] == "doi": + self.doi = "doi = {" + identifier["value"] + "}" + break + return self + + def add_month(self): + if "date-released" in self.cffobj.keys(): + self.month = "month = {" + str(self.cffobj["date-released"].month) + "}" + return self + + def add_url(self): + self.url = BiblatexUrl(self.cffobj).as_string() + return self + + def add_year(self): + if "date-released" in self.cffobj.keys(): + self.year = "year = {" + str(self.cffobj["date-released"].year) + "}" + return self + + def add_date(self): + if "date-released" in self.cffobj.keys(): + self.date = "date = {" + self.cffobj["date-released"].isoformat() + "}" + return self diff --git a/src/cffconvert/lib/cff_1_1_x/citation.py b/src/cffconvert/lib/cff_1_1_x/citation.py index 5e1551094..d555b6ad8 100644 --- a/src/cffconvert/lib/cff_1_1_x/citation.py +++ b/src/cffconvert/lib/cff_1_1_x/citation.py @@ -3,6 +3,7 @@ from ruamel.yaml import SafeConstructor from ruamel.yaml import YAML from cffconvert.lib.cff_1_1_x.apalike import ApalikeObject +from cffconvert.lib.cff_1_1_x.biblatex import BiblatexObject from cffconvert.lib.cff_1_1_x.bibtex import BibtexObject from cffconvert.lib.cff_1_1_x.codemeta import CodemetaObject from cffconvert.lib.cff_1_1_x.endnote import EndnoteObject @@ -55,6 +56,9 @@ def _parse(self): def as_apalike(self): return ApalikeObject(self.cffobj).as_string() + def as_biblatex(self): + return BiblatexObject(self.cffobj).as_string() + def as_bibtex(self): return BibtexObject(self.cffobj).as_string() diff --git a/src/cffconvert/lib/cff_1_2_x/biblatex.py b/src/cffconvert/lib/cff_1_2_x/biblatex.py new file mode 100644 index 000000000..a7a2f4a8c --- /dev/null +++ b/src/cffconvert/lib/cff_1_2_x/biblatex.py @@ -0,0 +1,48 @@ +from cffconvert.lib.cff_1_x_x.authors.biblatex import BiblatexAuthor +from cffconvert.lib.cff_1_x_x.biblatex import BiblatexObjectShared as Shared +from cffconvert.lib.cff_1_x_x.urls.biblatex import BiblatexUrl + + +class BiblatexObject(Shared): + + supported_cff_versions = [ + "1.2.0" + ] + + def add_author(self): + authors_cff = self.cffobj.get("authors", []) + authors_biblatex = [BiblatexAuthor(a).as_string() for a in authors_cff] + authors_biblatex_filtered = [a for a in authors_biblatex if a is not None] + self.author = "author = {" + " and ".join(authors_biblatex_filtered) + "}" + return self + + def add_doi(self): + if "doi" in self.cffobj.keys(): + self.doi = "doi = {" + self.cffobj["doi"] + "}" + if "identifiers" in self.cffobj.keys(): + identifiers = self.cffobj["identifiers"] + for identifier in identifiers: + if identifier["type"] == "doi": + self.doi = "doi = {" + identifier["value"] + "}" + break + return self + + def add_month(self): + if "date-released" in self.cffobj.keys(): + month = self.cffobj["date-released"].split("-")[1].lstrip("0") + self.month = "month = {" + month + "}" + return self + + def add_url(self): + self.url = BiblatexUrl(self.cffobj).as_string() + return self + + def add_year(self): + if "date-released" in self.cffobj.keys(): + self.year = "year = {" + self.cffobj["date-released"][:4] + "}" + return self + + def add_date(self): + if "date-released" in self.cffobj.keys(): + self.date = "date = {" + self.cffobj["date-released"] + "}" + return self diff --git a/src/cffconvert/lib/cff_1_2_x/citation.py b/src/cffconvert/lib/cff_1_2_x/citation.py index 9d46e51be..05e5dcccc 100644 --- a/src/cffconvert/lib/cff_1_2_x/citation.py +++ b/src/cffconvert/lib/cff_1_2_x/citation.py @@ -5,6 +5,7 @@ from ruamel.yaml import SafeConstructor from ruamel.yaml import YAML from cffconvert.lib.cff_1_2_x.apalike import ApalikeObject +from cffconvert.lib.cff_1_2_x.biblatex import BiblatexObject from cffconvert.lib.cff_1_2_x.bibtex import BibtexObject from cffconvert.lib.cff_1_2_x.codemeta import CodemetaObject from cffconvert.lib.cff_1_2_x.endnote import EndnoteObject @@ -57,6 +58,9 @@ def _parse(self): def as_apalike(self): return ApalikeObject(self.cffobj).as_string() + def as_biblatex(self): + return BiblatexObject(self.cffobj).as_string() + def as_bibtex(self): return BibtexObject(self.cffobj).as_string() diff --git a/src/cffconvert/lib/cff_1_3_x/biblatex.py b/src/cffconvert/lib/cff_1_3_x/biblatex.py new file mode 100644 index 000000000..a534e7fe7 --- /dev/null +++ b/src/cffconvert/lib/cff_1_3_x/biblatex.py @@ -0,0 +1,48 @@ +from cffconvert.lib.cff_1_x_x.authors.biblatex import BiblatexAuthor +from cffconvert.lib.cff_1_x_x.biblatex import BiblatexObjectShared as Shared +from cffconvert.lib.cff_1_x_x.urls.biblatex import BiblatexUrl + + +class BiblatexObject(Shared): + + supported_cff_versions = [ + "1.3.0" + ] + + def add_author(self): + authors_cff = self.cffobj.get("authors", []) + authors_biblatex = [BiblatexAuthor(a).as_string() for a in authors_cff] + authors_biblatex_filtered = [a for a in authors_biblatex if a is not None] + self.author = "author = {" + " and ".join(authors_biblatex_filtered) + "}" + return self + + def add_doi(self): + if "doi" in self.cffobj.keys(): + self.doi = "doi = {" + self.cffobj["doi"] + "}" + if "identifiers" in self.cffobj.keys(): + identifiers = self.cffobj["identifiers"] + for identifier in identifiers: + if identifier["type"] == "doi": + self.doi = "doi = {" + identifier["value"] + "}" + break + return self + + def add_month(self): + if "date-released" in self.cffobj.keys(): + month = self.cffobj["date-released"].split("-")[1].lstrip("0") + self.month = "month = {" + month + "}" + return self + + def add_url(self): + self.url = BiblatexUrl(self.cffobj).as_string() + return self + + def add_year(self): + if "date-released" in self.cffobj.keys(): + self.year = "year = {" + self.cffobj["date-released"][:4] + "}" + return self + + def add_date(self): + if "date-released" in self.cffobj.keys(): + self.date = "date = {" + self.cffobj["date-released"] + "}" + return self diff --git a/src/cffconvert/lib/cff_1_3_x/citation.py b/src/cffconvert/lib/cff_1_3_x/citation.py index 34eab2a94..df404a3df 100644 --- a/src/cffconvert/lib/cff_1_3_x/citation.py +++ b/src/cffconvert/lib/cff_1_3_x/citation.py @@ -5,6 +5,7 @@ from ruamel.yaml import SafeConstructor from ruamel.yaml import YAML from cffconvert.lib.cff_1_3_x.apalike import ApalikeObject +from cffconvert.lib.cff_1_3_x.biblatex import BiblatexObject from cffconvert.lib.cff_1_3_x.bibtex import BibtexObject from cffconvert.lib.cff_1_3_x.codemeta import CodemetaObject from cffconvert.lib.cff_1_3_x.endnote import EndnoteObject @@ -57,6 +58,9 @@ def _parse(self): def as_apalike(self): return ApalikeObject(self.cffobj).as_string() + def as_biblatex(self): + return BiblatexObject(self.cffobj).as_string() + def as_bibtex(self): return BibtexObject(self.cffobj).as_string() diff --git a/src/cffconvert/lib/cff_1_x_x/authors/biblatex.py b/src/cffconvert/lib/cff_1_x_x/authors/biblatex.py new file mode 100644 index 000000000..d753d0808 --- /dev/null +++ b/src/cffconvert/lib/cff_1_x_x/authors/biblatex.py @@ -0,0 +1,162 @@ +from cffconvert.lib.cff_1_x_x.authors.base import BaseAuthor + + +# pylint: disable=too-few-public-methods +class BiblatexAuthor(BaseAuthor): + + def __init__(self, author): + super().__init__(author) + self._behaviors = { + "GFANAOE": self._from_given_and_last, + "GFANAO_": self._from_given_and_last, + "GFANA_E": self._from_given_and_last, + "GFANA__": self._from_given_and_last, + "GFAN_OE": self._from_given_and_last, + "GFAN_O_": self._from_given_and_last, + "GFAN__E": self._from_given_and_last, + "GFAN___": self._from_given_and_last, + "GFA_AOE": self._from_given_and_last, + "GFA_AO_": self._from_given_and_last, + "GFA_A_E": self._from_given_and_last, + "GFA_A__": self._from_given_and_last, + "GFA__OE": self._from_given_and_last, + "GFA__O_": self._from_given_and_last, + "GFA___E": self._from_given_and_last, + "GFA____": self._from_given_and_last, + "GF_NAOE": self._from_given_and_last, + "GF_NAO_": self._from_given_and_last, + "GF_NA_E": self._from_given_and_last, + "GF_NA__": self._from_given_and_last, + "GF_N_OE": self._from_given_and_last, + "GF_N_O_": self._from_given_and_last, + "GF_N__E": self._from_given_and_last, + "GF_N___": self._from_given_and_last, + "GF__AOE": self._from_given_and_last, + "GF__AO_": self._from_given_and_last, + "GF__A_E": self._from_given_and_last, + "GF__A__": self._from_given_and_last, + "GF___OE": self._from_given_and_last, + "GF___O_": self._from_given_and_last, + "GF____E": self._from_given_and_last, + "GF_____": self._from_given_and_last, + "G_ANAOE": self._from_given, + "G_ANAO_": self._from_given, + "G_ANA_E": self._from_given, + "G_ANA__": self._from_given, + "G_AN_OE": self._from_given, + "G_AN_O_": self._from_given, + "G_AN__E": self._from_given, + "G_AN___": self._from_given, + "G_A_AOE": self._from_given, + "G_A_AO_": self._from_given, + "G_A_A_E": self._from_given, + "G_A_A__": self._from_given, + "G_A__OE": self._from_given, + "G_A__O_": self._from_given, + "G_A___E": self._from_given, + "G_A____": self._from_given, + "G__NAOE": self._from_given, + "G__NAO_": self._from_given, + "G__NA_E": self._from_given, + "G__NA__": self._from_given, + "G__N_OE": self._from_given, + "G__N_O_": self._from_given, + "G__N__E": self._from_given, + "G__N___": self._from_given, + "G___AOE": self._from_given, + "G___AO_": self._from_given, + "G___A_E": self._from_given, + "G___A__": self._from_given, + "G____OE": self._from_given, + "G____O_": self._from_given, + "G_____E": self._from_given, + "G______": self._from_given, + "_FANAOE": self._from_last, + "_FANAO_": self._from_last, + "_FANA_E": self._from_last, + "_FANA__": self._from_last, + "_FAN_OE": self._from_last, + "_FAN_O_": self._from_last, + "_FAN__E": self._from_last, + "_FAN___": self._from_last, + "_FA_AOE": self._from_last, + "_FA_AO_": self._from_last, + "_FA_A_E": self._from_last, + "_FA_A__": self._from_last, + "_FA__OE": self._from_last, + "_FA__O_": self._from_last, + "_FA___E": self._from_last, + "_FA____": self._from_last, + "_F_NAOE": self._from_last, + "_F_NAO_": self._from_last, + "_F_NA_E": self._from_last, + "_F_NA__": self._from_last, + "_F_N_OE": self._from_last, + "_F_N_O_": self._from_last, + "_F_N__E": self._from_last, + "_F_N___": self._from_last, + "_F__AOE": self._from_last, + "_F__AO_": self._from_last, + "_F__A_E": self._from_last, + "_F__A__": self._from_last, + "_F___OE": self._from_last, + "_F___O_": self._from_last, + "_F____E": self._from_last, + "_F_____": self._from_last, + "__ANAOE": self._from_name, + "__ANAO_": self._from_name, + "__ANA_E": self._from_name, + "__ANA__": self._from_name, + "__AN_OE": self._from_name, + "__AN_O_": self._from_name, + "__AN__E": self._from_name, + "__AN___": self._from_name, + "__A_AOE": self._from_alias, + "__A_AO_": self._from_alias, + "__A_A_E": self._from_alias, + "__A_A__": self._from_alias, + "__A__OE": self._from_alias, + "__A__O_": self._from_alias, + "__A___E": self._from_alias, + "__A____": self._from_alias, + "___NAOE": self._from_name, + "___NAO_": self._from_name, + "___NA_E": self._from_name, + "___NA__": self._from_name, + "___N_OE": self._from_name, + "___N_O_": self._from_name, + "___N__E": self._from_name, + "___N___": self._from_name, + "____AOE": BiblatexAuthor._from_thin_air, + "____AO_": BiblatexAuthor._from_thin_air, + "____A_E": BiblatexAuthor._from_thin_air, + "____A__": BiblatexAuthor._from_thin_air, + "_____OE": BiblatexAuthor._from_thin_air, + "_____O_": BiblatexAuthor._from_thin_air, + "______E": BiblatexAuthor._from_thin_air, + "_______": BiblatexAuthor._from_thin_air + } + + def _from_alias(self): + return "{" + self._author.get("alias") + "}" + + def _from_given_and_last(self): + return self._from_last() + ", " + self._from_given() + + def _from_given(self): + return self._author.get("given-names") + + def _from_last(self): + nameparts = [ + self._author.get("name-particle"), + self._author.get("family-names"), + self._author.get("name-suffix") + ] + return " ".join([n for n in nameparts if n is not None]) + + def _from_name(self): + return "{" + self._author.get("name") + "}" + + def as_string(self): + key = self._get_key() + return self._behaviors[key]() diff --git a/src/cffconvert/lib/cff_1_x_x/biblatex.py b/src/cffconvert/lib/cff_1_x_x/biblatex.py new file mode 100644 index 000000000..a0856d8fd --- /dev/null +++ b/src/cffconvert/lib/cff_1_x_x/biblatex.py @@ -0,0 +1,161 @@ +from abc import abstractmethod + + +class BiblatexObjectShared: + + supported_cff_versions = None + + def __init__(self, cffobj, initialize_empty=False): + self.cffobj = cffobj + self.author = None + self.doi = None + self.month = None + self.title = None + self.url = None + self.year = None + # below are new added fields not supported by bibtex + self.abstract = None + self.date = None + self.repository = None + self.license = None + self.version = None + self.institution = None + self.editor = None + self.swhid = None + self.keywords = None + if initialize_empty: + # clause for testing purposes + pass + else: + self.check_cffobj() + self.add_all() + + def __str__(self): + items = [item for item in [self.author, + self.doi, + self.month, + self.title, + self.url, + self.year, + self.abstract, + self.date, + self.repository, + self.license, + self.version, + self.institution, + self.editor, + self.swhid, + self.keywords] if item is not None] + joined = ",\n".join(items) + return "@software{YourReferenceHere,\n" + joined + "\n}\n" + + def add_all(self): + self.add_author() \ + .add_doi() \ + .add_month() \ + .add_title() \ + .add_url() \ + .add_year() \ + .add_abstract() \ + .add_date() \ + .add_repository() \ + .add_license() \ + .add_version() \ + .add_institution()\ + .add_editor() \ + .add_swhid() \ + .add_keywords() + + return self + + @abstractmethod + def add_author(self): + pass + + @abstractmethod + def add_doi(self): + pass + + @abstractmethod + def add_month(self): + pass + + def add_title(self): + if "title" in self.cffobj.keys(): + self.title = "title = {" + self.cffobj["title"] + "}" + return self + + @abstractmethod + def add_url(self): + pass + + @abstractmethod + def add_year(self): + pass + + def add_abstract(self): + if "abstract" in self.cffobj.keys(): + self.abstract = "abstract = {" + self.cffobj["abstract"] + "}" + return self + + @abstractmethod + def add_date(self): + pass + + def add_repository(self): + if "repository-code" in self.cffobj.keys(): + self.repository = "repository = {" + self.cffobj["repository-code"] + "}" + if "repository" in self.cffobj.keys(): + self.repository = "repository = {" + self.cffobj["repository"] + "}" + return self + + def add_license(self): + if "license" in self.cffobj.keys(): + self.license = "license = {" + self.cffobj["license"] + "}" + return self + + def add_version(self): + if "version" in self.cffobj.keys(): + self.version = "version = {" + self.cffobj["version"] + "}" + return self + + def add_institution(self): + if "contact" in self.cffobj.keys(): + for contact in self.cffobj["contact"]: + if "name" in contact: + self.institution = "institution = {" + contact["name"] + "}" + break + return self + + def add_editor(self): + if "contact" in self.cffobj.keys(): + editors = [i["family-names"] + ", " + i["given-names"] + for i in self.cffobj["contact"] + if "name" not in i and "given-names" in i and "family-names" in i] + if editors: + self.editor = "editor = {" + " and ".join(editors) + "}" + return self + + def add_swhid(self): + if "identifiers" in self.cffobj.keys(): + for identifier in self.cffobj["identifiers"]: + if identifier["type"] == "swh": + self.swhid = "swhid = {" + identifier["value"] + "}" + break + return self + + def add_keywords(self): + if "keywords" in self.cffobj.keys(): + self.keywords = "keywords = {" + ", ".join(self.cffobj["keywords"]) + "}" + return self + + def as_string(self): + return str(self) + + def check_cffobj(self): + if not isinstance(self.cffobj, dict): + raise ValueError("Expected cffobj to be of type 'dict'.") + if "cff-version" not in self.cffobj.keys(): + raise ValueError("Missing key 'cff-version' in CITATION.cff file.") + if self.cffobj["cff-version"] not in self.supported_cff_versions: + raise ValueError(f"cff-version: {self.cffobj['cff-version']} isn't a supported version.") diff --git a/src/cffconvert/lib/cff_1_x_x/urls/biblatex.py b/src/cffconvert/lib/cff_1_x_x/urls/biblatex.py new file mode 100644 index 000000000..d9fc82649 --- /dev/null +++ b/src/cffconvert/lib/cff_1_x_x/urls/biblatex.py @@ -0,0 +1,74 @@ +from cffconvert.lib.cff_1_x_x.urls.base import BaseUrl + + +# pylint: disable=too-few-public-methods +class BiblatexUrl(BaseUrl): + + def __init__(self, cffobj): + super().__init__(cffobj) + self._behaviors = { + "IRACU": self._from_identifiers_url, + "IRAC_": self._from_identifiers_url, + "IRA_U": self._from_identifiers_url, + "IRA__": self._from_identifiers_url, + "IR_CU": self._from_identifiers_url, + "IR_C_": self._from_identifiers_url, + "IR__U": self._from_identifiers_url, + "IR___": self._from_identifiers_url, + "I_ACU": self._from_identifiers_url, + "I_AC_": self._from_identifiers_url, + "I_A_U": self._from_identifiers_url, + "I_A__": self._from_identifiers_url, + "I__CU": self._from_identifiers_url, + "I__C_": self._from_identifiers_url, + "I___U": self._from_identifiers_url, + "I____": self._from_identifiers_url, + "_RACU": self._from_url, + "_RAC_": self._from_repository_code, + "_RA_U": self._from_url, + "_RA__": self._from_repository, + "_R_CU": self._from_url, + "_R_C_": self._from_repository_code, + "_R__U": self._from_url, + "_R___": self._from_repository, + "__ACU": self._from_url, + "__AC_": self._from_repository_code, + "__A_U": self._from_url, + "__A__": self._from_repository_artifact, + "___CU": self._from_url, + "___C_": self._from_repository_code, + "____U": self._from_url, + "_____": BiblatexUrl._from_thin_air + } + + def _from_identifiers_url(self): + urls = self._get_urls_from_identifiers() + if len(urls) > 0: + return f"url = { '{' + urls[0].get('value') + '}' }" + return None + + def _from_repository(self): + return f"url = { '{' + self._cffobj.get('repository') + '}' }" + + def _from_repository_artifact(self): + return f"url = { '{' + self._cffobj.get('repository-artifact') + '}' }" + + def _from_repository_code(self): + return f"url = { '{' + self._cffobj.get('repository-code') + '}' }" + + @staticmethod + def _from_thin_air(): + return None + + def _from_url(self): + return f"url = { '{' + self._cffobj.get('url') + '}' }" + + def as_string(self): + key = "".join([ + self._has_identifiers_url(), + self._has_repository(), + self._has_repository_artifact(), + self._has_repository_code(), + self._has_url() + ]) + return self._behaviors[key]() diff --git a/src/cffconvert/lib/contracts/citation.py b/src/cffconvert/lib/contracts/citation.py index c130805f6..4bc8c2485 100644 --- a/src/cffconvert/lib/contracts/citation.py +++ b/src/cffconvert/lib/contracts/citation.py @@ -16,6 +16,10 @@ def _parse(self): def as_apalike(self): pass + @abstractmethod + def as_biblatex(self): + pass + @abstractmethod def as_bibtex(self): pass diff --git a/tests/cli/cff_1_0_3/biblatex.bib b/tests/cli/cff_1_0_3/biblatex.bib new file mode 100644 index 000000000..b78ee531c --- /dev/null +++ b/tests/cli/cff_1_0_3/biblatex.bib @@ -0,0 +1,12 @@ +@software{YourReferenceHere, +author = {Spaaks, Jurriaan H. and Klaver, Tom}, +doi = {10.5281/zenodo.1162057}, +month = {1}, +title = {cffconvert}, +url = {https://github.com/citation-file-format/cffconvert}, +year = {2018}, +date = {2018-01-16}, +license = {Apache-2.0}, +version = {1.0.0}, +keywords = {citation, bibliography, cff, CITATION.cff} +} diff --git a/tests/cli/cff_1_1_0/biblatex.bib b/tests/cli/cff_1_1_0/biblatex.bib new file mode 100644 index 000000000..b78ee531c --- /dev/null +++ b/tests/cli/cff_1_1_0/biblatex.bib @@ -0,0 +1,12 @@ +@software{YourReferenceHere, +author = {Spaaks, Jurriaan H. and Klaver, Tom}, +doi = {10.5281/zenodo.1162057}, +month = {1}, +title = {cffconvert}, +url = {https://github.com/citation-file-format/cffconvert}, +year = {2018}, +date = {2018-01-16}, +license = {Apache-2.0}, +version = {1.0.0}, +keywords = {citation, bibliography, cff, CITATION.cff} +} diff --git a/tests/cli/cff_1_2_0/biblatex.bib b/tests/cli/cff_1_2_0/biblatex.bib new file mode 100644 index 000000000..8c557aa9d --- /dev/null +++ b/tests/cli/cff_1_2_0/biblatex.bib @@ -0,0 +1,5 @@ +@software{YourReferenceHere, +author = {{Test author}}, +doi = {10.0000/from-doi}, +title = {Test title} +} diff --git a/tests/cli/cff_1_3_0/biblatex.bib b/tests/cli/cff_1_3_0/biblatex.bib new file mode 100644 index 000000000..8c557aa9d --- /dev/null +++ b/tests/cli/cff_1_3_0/biblatex.bib @@ -0,0 +1,5 @@ +@software{YourReferenceHere, +author = {{Test author}}, +doi = {10.0000/from-doi}, +title = {Test title} +} diff --git a/tests/cli/helpers.py b/tests/cli/helpers.py index 5550f723c..23501495c 100644 --- a/tests/cli/helpers.py +++ b/tests/cli/helpers.py @@ -6,6 +6,7 @@ def get_formats(): return [ pytest.param("apalike", "apalike.txt", id="apalike", marks=pytest.mark.apalike), pytest.param("bibtex", "bibtex.bib", id="bibtex", marks=pytest.mark.bibtex), + pytest.param("biblatex", "biblatex.bib", id="biblatex", marks=pytest.mark.biblatex), pytest.param("cff", "CITATION.cff", id="cff"), pytest.param("codemeta", "codemeta.json", id="codemeta", marks=pytest.mark.codemeta), pytest.param("endnote", "endnote.enw", id="endnote", marks=pytest.mark.endnote),