# get a copy of the cffconvert software
git clone https://github.com/scicodes/cffconvert.git
# change directory into cffconvert
cd cffconvert
# create a virtual environment and install cffconvert with dev and testing dependencies
uv venv && source .venv/bin/activate
uv pip install --editable .[dev,testing]There are various sets of dependencies that you may want to install depending on the work you're planning to do.
uv pip install --editable .[dev] # build, pre-commit, pyroma, ruff, setuptools
uv pip install --editable .[gcloud] # flask (for Google Cloud Function deployment)
uv pip install --editable .[testing] # pytest, pytest-covYou can combine these into one command like so, e.g.:
uv pip install --editable .[dev,testing]Alternatively, you can use the Makefile targets:
make dev-install # install with dev + testing deps (editable)
make install # install runtime deps onlyThe official interim source distribution is the Git tag attached to a published GitHub Release.
The current release tag is v2026.09.1.
Publish a GitHub Release for that tag to trigger GHCR; use main only for development, not as a distribution source.
# (from the project root)
# run all tests in Docker (builds a test image automatically)
make test
# or run tests locally (requires dev-install)
make test-local
# run tests for consistent versioning
make test-version
# run pytest on a subset of the files, e.g.
cd tests/lib/cff_1_2_0/ && pytest .Alternatively, use the Makefile:
make test # run full test suite (in Docker)
make test-local # run full test suite on the host (requires dev-install)
make test-version # run version-consistency checks (in Docker)
make test-marker M=bibtex # run tests for a specific marker (in Docker)Tests pertaining to a specific exporter have been marked accordingly with one of the following markers (see also
pytest configuration section in pyproject.toml):
apalikebibtexcodemetaendnoterisschemaorgzenodo
Additionally, there are markers for CLI tests and for library tests:
clilib
You can instruct pytest to run only the tests for one or some of these marked sets. As an example, if you want to run
only the tests related to exporting as Endnote, you should call pytest with the -m flag as follows:
pytest -m endnoteMarkers can also be combined with or and and and not, e.g.
pytest -m 'endnote and lib'
pytest -m 'cli and not zenodo'
pytest -m 'schemaorg or codemeta'
# etcRunning the linters requires that the development tools have been installed:
uv pip install --editable .[dev]Linting the code base using ruff:
ruff check src/cffconvert testsAssessing the package metadata using 'pyroma' (https://github.com/regebro/pyroma):
# from the project root
pyroma .The linting tools are also usable via pre-commit:
# Run all tools
pre-commit run --all-files
# Run a specific tool, see .pre-commit-config.yaml for their IDs
pre-commit run --all-files <ID of the task>Alternatively, the linting tools can be set up to run automatically whenever you issue a git commit command, as
follows:
pre-commit installAlternatively, you can run all linters via the Makefile:
make lint # ruff, pyroma in Docker
make lint-local # ruff, pyroma locally (requires dev-install)
make precommit # pre-commit run --all-filesThere are various source keys in CFF that can be used to convert to a target format. The code uses a pattern of first identifiying what information is present, then summarizing this as a key, then using that key to retrieve a method which is tailored only to that specific combination of source keys. As an example of this mapping, see the setup in https://github.com/SciCodes/cffconvert/blob/main/src/cffconvert/lib/cff_1_x_x/authors/base.py
Source keys:
authors[i].given-namesauthors[i].family-names(including name particle and suffix)authors[i].aliasauthors[i].nameauthors[i].affiliationauthors[i].orcidauthors[i].email
The table below lists how the key name is constructed based what information was provided in the CITATION.cff file:
| key | has given name | has family name | has alias | has name | has affiliation | has orcid | has email |
|---|---|---|---|---|---|---|---|
GFANAOE |
True | True | True | True | True | True | True |
GFANA_E |
True | True | True | True | True | False | True |
GFAN_OE |
True | True | True | True | False | True | True |
GFAN__E |
True | True | True | True | False | False | True |
GFA_AOE |
True | True | True | False | True | True | True |
GFA_A_E |
True | True | True | False | True | False | True |
GFA__OE |
True | True | True | False | False | True | True |
GFA___E |
True | True | True | False | False | False | True |
GF_NAOE |
True | True | False | True | True | True | True |
GF_NA_E |
True | True | False | True | True | False | True |
GF_N_OE |
True | True | False | True | False | True | True |
GF_N__E |
True | True | False | True | False | False | True |
GF__AOE |
True | True | False | False | True | True | True |
GF__A_E |
True | True | False | False | True | False | True |
GF___OE |
True | True | False | False | False | True | True |
GF____E |
True | True | False | False | False | False | True |
G_ANAOE |
True | False | True | True | True | True | True |
G_ANA_E |
True | False | True | True | True | False | True |
G_AN_OE |
True | False | True | True | False | True | True |
G_AN__E |
True | False | True | True | False | False | True |
G_A_AOE |
True | False | True | False | True | True | True |
G_A_A_E |
True | False | True | False | True | False | True |
G_A__OE |
True | False | True | False | False | True | True |
G_A___E |
True | False | True | False | False | False | True |
G__NAOE |
True | False | False | True | True | True | True |
G__NA_E |
True | False | False | True | True | False | True |
G__N_OE |
True | False | False | True | False | True | True |
G__N__E |
True | False | False | True | False | False | True |
G___AOE |
True | False | False | False | True | True | True |
G___A_E |
True | False | False | False | True | False | True |
G____OE |
True | False | False | False | False | True | True |
G_____E |
True | False | False | False | False | False | True |
_FANAOE |
False | True | True | True | True | True | True |
_FANA_E |
False | True | True | True | True | False | True |
_FAN_OE |
False | True | True | True | False | True | True |
_FAN__E |
False | True | True | True | False | False | True |
_FA_AOE |
False | True | True | False | True | True | True |
_FA_A_E |
False | True | True | False | True | False | True |
_FA__OE |
False | True | True | False | False | True | True |
_FA___E |
False | True | True | False | False | False | True |
_F_NAOE |
False | True | False | True | True | True | True |
_F_NA_E |
False | True | False | True | True | False | True |
_F_N_OE |
False | True | False | True | False | True | True |
_F_N__E |
False | True | False | True | False | False | True |
_F__AOE |
False | True | False | False | True | True | True |
_F__A_E |
False | True | False | False | True | False | True |
_F___OE |
False | True | False | False | False | True | True |
_F____E |
False | True | False | False | False | False | True |
__ANAOE |
False | False | True | True | True | True | True |
__ANA_E |
False | False | True | True | True | False | True |
__AN_OE |
False | False | True | True | False | True | True |
__AN__E |
False | False | True | True | False | False | True |
__A_AOE |
False | False | True | False | True | True | True |
__A_A_E |
False | False | True | False | True | False | True |
__A__OE |
False | False | True | False | False | True | True |
__A___E |
False | False | True | False | False | False | True |
___NAOE |
False | False | False | True | True | True | True |
___NA_E |
False | False | False | True | True | False | True |
___N_OE |
False | False | False | True | False | True | True |
___N__E |
False | False | False | True | False | False | True |
____AOE |
False | False | False | False | True | True | True |
____A_E |
False | False | False | False | True | False | True |
_____OE |
False | False | False | False | False | True | True |
______E |
False | False | False | False | False | False | True |
GFANAO_ |
True | True | True | True | True | True | False |
GFANA__ |
True | True | True | True | True | False | False |
GFAN_O_ |
True | True | True | True | False | True | False |
GFAN___ |
True | True | True | True | False | False | False |
GFA_AO_ |
True | True | True | False | True | True | False |
GFA_A__ |
True | True | True | False | True | False | False |
GFA__O_ |
True | True | True | False | False | True | False |
GFA____ |
True | True | True | False | False | False | False |
GF_NAO_ |
True | True | False | True | True | True | False |
GF_NA__ |
True | True | False | True | True | False | False |
GF_N_O_ |
True | True | False | True | False | True | False |
GF_N___ |
True | True | False | True | False | False | False |
GF__AO_ |
True | True | False | False | True | True | False |
GF__A__ |
True | True | False | False | True | False | False |
GF___O_ |
True | True | False | False | False | True | False |
GF_____ |
True | True | False | False | False | False | False |
G_ANAO_ |
True | False | True | True | True | True | False |
G_ANA__ |
True | False | True | True | True | False | False |
G_AN_O_ |
True | False | True | True | False | True | False |
G_AN___ |
True | False | True | True | False | False | False |
G_A_AO_ |
True | False | True | False | True | True | False |
G_A_A__ |
True | False | True | False | True | False | False |
G_A__O_ |
True | False | True | False | False | True | False |
G_A____ |
True | False | True | False | False | False | False |
G__NAO_ |
True | False | False | True | True | True | False |
G__NA__ |
True | False | False | True | True | False | False |
G__N_O_ |
True | False | False | True | False | True | False |
G__N___ |
True | False | False | True | False | False | False |
G___AO_ |
True | False | False | False | True | True | False |
G___A__ |
True | False | False | False | True | False | False |
G____O_ |
True | False | False | False | False | True | False |
G______ |
True | False | False | False | False | False | False |
_FANAO_ |
False | True | True | True | True | True | False |
_FANA__ |
False | True | True | True | True | False | False |
_FAN_O_ |
False | True | True | True | False | True | False |
_FAN___ |
False | True | True | True | False | False | False |
_FA_AO_ |
False | True | True | False | True | True | False |
_FA_A__ |
False | True | True | False | True | False | False |
_FA__O_ |
False | True | True | False | False | True | False |
_FA____ |
False | True | True | False | False | False | False |
_F_NAO_ |
False | True | False | True | True | True | False |
_F_NA__ |
False | True | False | True | True | False | False |
_F_N_O_ |
False | True | False | True | False | True | False |
_F_N___ |
False | True | False | True | False | False | False |
_F__AO_ |
False | True | False | False | True | True | False |
_F__A__ |
False | True | False | False | True | False | False |
_F___O_ |
False | True | False | False | False | True | False |
_F_____ |
False | True | False | False | False | False | False |
__ANAO_ |
False | False | True | True | True | True | False |
__ANA__ |
False | False | True | True | True | False | False |
__AN_O_ |
False | False | True | True | False | True | False |
__AN___ |
False | False | True | True | False | False | False |
__A_AO_ |
False | False | True | False | True | True | False |
__A_A__ |
False | False | True | False | True | False | False |
__A__O_ |
False | False | True | False | False | True | False |
__A____ |
False | False | True | False | False | False | False |
___NAO_ |
False | False | False | True | True | True | False |
___NA__ |
False | False | False | True | True | False | False |
___N_O_ |
False | False | False | True | False | True | False |
___N___ |
False | False | False | True | False | False | False |
____AO_ |
False | False | False | False | True | True | False |
____A__ |
False | False | False | False | True | False | False |
_____O_ |
False | False | False | False | False | True | False |
_______ |
False | False | False | False | False | False | False |
There are various source keys in CFF that can be used to convert to a target format. The code uses a pattern of first identifiying what information is present, then summarizing this as a key, then using that key to retrieve a method which is tailored only to that specific combination of source keys.
Source keys:
doiidentifiers[i].type==doi
The table below lists how the key name is constructed based what information was provided in the CITATION.cff file:
| key | has doi | has identifiers doi |
|---|---|---|
__ |
False | False |
_I |
False | True |
D_ |
True | False |
DI |
True | True |
There are various source keys in CFF that can be used to convert to a target format. The code uses a pattern of first identifiying what information is present, then summarizing this as a key, then using that key to retrieve a method which is tailored only to that specific combination of source keys.
Source keys:
identifiers[i].type==urlrepositoryrepository-artifactrepository-codeurl
The table below lists how the key name is constructed based what information was provided in the CITATION.cff file:
| key | has indentifiers url | has repository | has repository-artifact | has repository-code | has url |
|---|---|---|---|---|---|
IRACU |
True | True | True | True | True |
IRAC_ |
True | True | True | True | False |
IRA_U |
True | True | True | False | True |
IRA__ |
True | True | True | False | False |
IR_CU |
True | True | False | True | True |
IR_C_ |
True | True | False | True | False |
IR__U |
True | True | False | False | True |
IR___ |
True | True | False | False | False |
I_ACU |
True | False | True | True | True |
I_AC_ |
True | False | True | True | False |
I_A_U |
True | False | True | False | True |
I_A__ |
True | False | True | False | False |
I__CU |
True | False | False | True | True |
I__C_ |
True | False | False | True | False |
I___U |
True | False | False | False | True |
I____ |
True | False | False | False | False |
_RACU |
False | True | True | True | True |
_RAC_ |
False | True | True | True | False |
_RA_U |
False | True | True | False | True |
_RA__ |
False | True | True | False | False |
_R_CU |
False | True | False | True | True |
_R_C_ |
False | True | False | True | False |
_R__U |
False | True | False | False | True |
_R___ |
False | True | False | False | False |
__ACU |
False | False | True | True | True |
__AC_ |
False | False | True | True | False |
__A_U |
False | False | True | False | True |
__A__ |
False | False | True | False | False |
___CU |
False | False | False | True | True |
___C_ |
False | False | False | True | False |
____U |
False | False | False | False | True |
_____ |
False | False | False | False | False |
See CONTRIBUTING.md for the full tag-and-push procedure, version checks, GHCR preparation, and the release checklist.
# (builds a self-contained image from the local source tree at version v2026.09.1)
docker build --tag cffconvert:v2026.09.1 .The image installs cffconvert independently and uses /work as its working directory, so no -w flag is needed.
See if the Docker image works as expected:
docker run --rm cffconvert:v2026.09.1 --version
docker run --rm -v "$PWD:/work:ro" cffconvert:v2026.09.1 -f bibtex
# etcPublishing a GitHub Release for the release tag triggers the publish-to-ghcr.yml workflow, which publishes
ghcr.io/scicodes/cffconvert:<release-tag> and uses the ghcr environment as a deployment gate.
The current release tag is v2026.09.1.
No secrets are needed for that environment.
Replace <release-tag> with the published GitHub Release tag in the commands below.
Before the first publish, configure the ghcr environment in GitHub and check the
package settings after upload so the package is linked to SciCodes/cffconvert and public.
The image will only exist after a successful workflow run, so use a local smoke test first:
docker build --tag ghcr.io/scicodes/cffconvert:<release-tag> .
docker run --rm ghcr.io/scicodes/cffconvert:<release-tag> --version
docker run --rm ghcr.io/scicodes/cffconvert:<release-tag> --help