Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

generate citation from csl record #12

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,22 @@ To create a CITATION.cff file of a release, you must supply the doi that is asso

The generated file must be checked for correctness and you are encouraged to enrich it further.

The command above works well for Zenodo doi's, which contain especially rich metadata.
It is also possible to produce a CITATION.cff file from any available information. This feature is experimental:

.. code-block:: bash

doi2cff init --experimental <doi>

# For example
doi2cff init 10.1051/0004-6361/202037850 --experimental


Whenever a new release is made of the software the CITATION.cff must be updated with new doi/version/release date.
This process can be automated by running
This process can be automated by running (only for Zenodo dois)

.. code-block:: bash

doi2cff update <doi>


117 changes: 89 additions & 28 deletions doi2cff/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import sys
import re
from datetime import datetime
from typing import Any, Tuple

import click
from nameparser import HumanName
Expand All @@ -20,6 +21,7 @@ def main():
Current supported DOI types:

* Zenodo upload of a GitHub release (https://guides.github.com/activities/citable-code/)
* Any DOIs with suitable metadata (experimental).

"""
pass
Expand All @@ -29,21 +31,85 @@ def is_software_zenodo(zenodo_record):
return zenodo_record['metadata']['resource_type']['type'] == 'software'


def zenodo_record_to_cff_yaml(zenodo_record: dict, template) -> Tuple[ruamel.yaml.YAML, Any]:
yaml = ruamel.yaml.YAML()
data = yaml.load(template)
data['title'] = zenodo_record['metadata']['title']
data['doi'] = zenodo_record['doi']
tagurl = tagurl_of_zenodo(zenodo_record)
if 'version' in zenodo_record['metadata']:
data['version'] = re.sub('^(v)', '', zenodo_record['metadata']['version'])
else:
data['version'] = tagurl2version(tagurl)
data['license'] = zenodo_record['metadata']['license']['id']
data['date-released'] = datetime.strptime(zenodo_record['metadata']['publication_date'], "%Y-%m-%d").date()
data['repository-code'] = tagurl2repo(tagurl)
data['authors'] = authors_of_zenodo(zenodo_record)
references = references_of_zenodo(zenodo_record)
fixme = 'FIXME generic is too generic, ' \
'see https://citation-file-format.github.io/1.2.0/specifications/#/reference-types for more specific types'
if references:
data['references'] = yaml.seq(references)
for idx, r in enumerate(references):
if r['type'] == 'generic':
data['references'].yaml_add_eol_comment(fixme, idx)

return yaml, data


def csljson_to_cff_yaml(cffjson: dict, template) -> Tuple[ruamel.yaml.YAML, Any]:
yaml = ruamel.yaml.YAML()
data = yaml.load(template)

data['title'] = cffjson['title']
if '\n' in data['title']:
data.yaml_add_eol_comment("FIXME: title contains new line: this is strange", "title")

data['doi'] = cffjson['DOI']

if 'license' in cffjson:
data['license'] = cffjson['license']

data['date-released'] = datetime(*cffjson['published']['date-parts'][0], 1).date()
data['authors'] = authors_of_csl(cffjson)

references = cffjson.get('reference', None)
fixme = 'FIXME generic is too generic, ' \
'see https://citation-file-format.github.io/1.2.0/specifications/#/reference-types for more specific types'
if references:
data['references'] = yaml.seq(references)
for idx, r in enumerate(references):
if r.get('type', 'generic') == 'generic':
data['references'].yaml_add_eol_comment(fixme, idx)

# In CFF 1.2.0 these fields are optional
# https://github.com/citation-file-format/citation-file-format/releases/tag/1.2.0
del data['version']
del data['repository-code']

return yaml, data


@main.command()
@click.argument('doi')
@click.option('--cff_fn',
type=click.File('x'),
default='CITATION.cff',
help='Name of citation formatted output file',
show_default=True)
def init(doi, cff_fn):
@click.option('--experimental/--no-experimental',
is_flag=True,
default=False,
help='experimental parsing of non-zenodo links',
show_default=True)
def init(doi, cff_fn, experimental):
"""Generate CITATION.cff file based on a Zenodo DOI of a Github release.

* DOI, The Digital Object Identifier (DOI) name of a Zenodo upload of a GitHub release
"""
template = '''# YAML 1.2
# Metadata for citation of this software according to the CFF format (https://citation-file-format.github.io/)
cff-version: 1.0.3
cff-version: 1.2.0
message: If you use this software, please cite it using these metadata.
# FIXME title as repository name might not be the best name, please make human readable
title: x
Expand All @@ -56,35 +122,24 @@ def init(doi, cff_fn):
license: x
'''

if not doi_is_from_zenodo(doi):
raise click.UsageError('Unable to process DOI name, only accept DOI name which is a Zenodo upload')
if doi_is_from_zenodo(doi):
zenodo_record = fetch_zenodo_by_doiurl(doi)

zenodo_record = fetch_zenodo_by_doiurl(doi)
if not is_software_zenodo(zenodo_record):
raise click.UsageError('Unable to process DOI name, only accept DOI name '
'which is a Zenodo upload of type software')

if not is_software_zenodo(zenodo_record):
raise click.UsageError('Unable to process DOI name, only accept DOI name which is a Zenodo upload of type software')

yaml = ruamel.yaml.YAML()
data = yaml.load(template)
data['title'] = zenodo_record['metadata']['title']
data['doi'] = zenodo_record['doi']
tagurl = tagurl_of_zenodo(zenodo_record)
if 'version' in zenodo_record['metadata']:
data['version'] = re.sub('^(v)', '', zenodo_record['metadata']['version'])
yaml, data = zenodo_record_to_cff_yaml(zenodo_record, template)
else:
data['version'] = tagurl2version(tagurl)
data['license'] = zenodo_record['metadata']['license']['id']
data['date-released'] = datetime.strptime(zenodo_record['metadata']['publication_date'], "%Y-%m-%d").date()
data['repository-code'] = tagurl2repo(tagurl)
data['authors'] = authors_of_zenodo(zenodo_record)
references = references_of_zenodo(zenodo_record)
fixme = 'FIXME generic is too generic, ' \
'see https://citation-file-format.github.io/1.0.3/specifications/#/reference-types for more specific types'
if references:
data['references'] = yaml.seq(references)
for idx, r in enumerate(references):
if r['type'] == 'generic':
data['references'].yaml_add_eol_comment(fixme, idx)
if experimental:
click.echo("Trying experimental parsing of arbitrary DOI")
csljson = fetch_csljson(doi)
yaml, data = csljson_to_cff_yaml(csljson, template)
else:
raise click.UsageError('Unable to process DOI name, normally we only accept DOI name '
'which is a Zenodo upload'
'You can try experimental parsing of other DOIs '
'(see --experimental option).')

yaml.dump(data, cff_fn)

Expand All @@ -103,6 +158,12 @@ def update(doi, cff_fn):

* DOI, The Digital Object Identifier (DOI) name of a Zenodo upload of a GitHub release
"""

if not doi_is_from_zenodo(doi):
raise click.UsageError('CITATION.cff update is only possible with Zenodo DOI. '
'For non-Zenodo DOIs, please consider recreating the citation, enabling experimental features: '
'`doi2cff init <doi> --experimental`')

update_version(doi, cff_fn)


Expand Down
Loading