From: W. Trevor King Date: Sat, 30 Dec 2017 00:04:43 +0000 (-0800) Subject: pull: Update to the SPDX License List 3.0 X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=954a34683ec9b421d0189a10bf3218ba6dc96bd1;p=fsf-license-api.git pull: Update to the SPDX License List 3.0 As released 2017-12-28 [1]. The main change here is that the same license can now have multiple license-list entries, with each entry covering a common grant. I think conflating licenses with grants is unfortunate, and would have preferred continuing to handle version grants with the + license expression operator [2] and possibly a new "-ONLY" operator [3]. But that ship seems to have sailed. This commit converts the SPDX identifiers into lists, so we can list as many identifiers as we want for each FSF license ID. It also adds property docs, so folks understand the significance of the identifier ordering. Even before 3.0, the SPDX used per-grant identifiers for MPL-2.0 and MPL-2.0-no-copyleft-exception [4]. But my reading of [5] gives me the impression that the FSF's GPL-compatibility ruling hinges on section 3.3, which is what the MPL-2.0-no-copyleft-exception is turning off. So I've left mapping at FSF MPL-2.0 -> SPDX MPL-2.0, instead of growing it to [MPL-2.0, MPL-2.0-no-copyleft-exception]. I cheat a bit on the fsf-list-practical link, because the FSF hasn't put an anchor on that header. Instead, I'm using the first entry underneath the header. [1]: https://github.com/spdx/license-list-XML/releases/tag/v3.0 [2]: https://spdx.org/spdx-specification-21-web-version#h.jxpfx0ykyb60 [3]: https://lists.spdx.org/pipermail/spdx-legal/2017-August/002126.html Subject: Re: minutes, summary, next steps Date: Thu, 17 Aug 2017 14:37:22 -0700 Message-ID: <20170817213722.GK23356@valgrind.tremily.us> [4]: https://github.com/spdx/license-list-XML/issues/441 [5]: https://www.gnu.org/licenses/license-list.html#MPL-2.0 --- diff --git a/README.md b/README.md index 4112b30..1365f67 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,36 @@ You can pull an individual license from a few places: * `spdx`, using [the SPDX identifiers][spdx-list]. +## License properties + +Licenses have the following properties: + +* FSF ID: a short slug identifying the license. +* `name`: a short string naming the license. +* `uri`: the FSF's recommendend URI for the license. +* `tags`: an array of FSF categories for the license. + The FSF currently defines the following categories: + + * `gpl-2-compatible` and `gpl-3-compatible`: [licenses that are GPL-compatible][fsf-list-gpl-compatible]. + * `fdl-compatible`: [licenses that are FDL-compatible][fsf-list-fdl-compatible]. + * `libre`: licenses that are either [GPL-compatible][fsf-list-gpl-compatible], [FDL-compatible][fsf-list-fdl-compatible], [or][fsf-list-free-software] [are][fsf-list-free-documentation] [otherwise][fsf-list-practical] [free][fsf-list-font]. + * `viewpoint`: [licenses for works stating a viewpoint][fsf-list-viewpoint]. + * `non-free`: licenses that [are][fsf-list-non-free-software] [non-free][]. +* `identifiers`: an object with mappings to other license lists. + This API currently [attempts](#caveats) to maintain the following mappings: + + * `spdx`: For licenses with SPDX IDs, the `spdx` value will hold an array of [SPDX identifiers][spdx-list]. + Licenses may have multiple SPDX entries when SPDX list defines per-grant IDs that share the same license (e.g. [`GPL-3.0-only`][spdx-gpl-3.0-only] and `GPL-3.0-or-later`][spdx-gpl-3.0-or-later]). + The first entry in the SPDX array is the one that most closely matches the FSF license. + For example, the FSF's [`GNUGPLv3`][fsf-gplv3] text has: + + > However, most software released under GPLv2 allows you to use the terms of later versions of the GPL as well. + + and the GPLv3 text [suggests an “any later version” grant][gplv3], so `GPL-3.0-or-later` is the first SPDX identifier, `GPL-3.0-only` is the second, and the deprecated `GPL-3.0` is the third. + ## Caveats -There are currently two hacks in [the pulling script](pull.py): +There are currently some hacks in [the pulling script](pull.py): * `SPLITS`, which: @@ -47,7 +74,10 @@ There are currently two hacks in [the pulling script](pull.py): Ideally this would be based on [automated license-text comparison][automated-matching], but in order for that to work this API would have to expose the license text that the FSF considered for each ID. Currently, [the FSF's HTML page][fsf-list] links to license source, but not in a consistent enough way for me to extract the text. -Until these hacks are addressed, license IDs and the `identifiers` field should be taken with a grain of salt. +* `TAG_OVERRIDES`, which sets `tags` where the human-readable text on the [FSF's annotated list][fsf-list] has more detail than the easily-machine-readable content. + For example, the FSF currently only distinguishes between `gpl-2-compatible` and `gpl-3-compatible` in text, so licenses that are only compatible with one or the other need tag overrides. + +Until these hacks are addressed, license IDs and the `tags` and `identifiers` field should be taken with a grain of salt. ## Contributing @@ -58,8 +88,21 @@ Until these hacks are addressed, license IDs and the `identifiers` field should [fsf-api]: https://lists.spdx.org/pipermail/spdx-legal/2017-October/002281.html [fsf-freebsd-fdl]: https://www.gnu.org/licenses/license-list.html#FreeBSDDL [fsf-freebsd-gpl]: https://www.gnu.org/licenses/license-list.html#FreeBSD +[fsf-gplv3]: https://www.gnu.org/licenses/license-list.html#GNUGPLv3 [fsf-list]: https://www.gnu.org/licenses/license-list.html +[fsf-list-gpl-compatible]: https://www.gnu.org/licenses/license-list.html#GPLCompatibleLicenses +[fsf-list-fdl-compatible]: https://www.gnu.org/licenses/license-list.html#FDL +[fsf-list-free-software]: https://www.gnu.org/licenses/license-list.html#GPLIncompatibleLicenses +[fsf-list-non-free-software]: https://www.gnu.org/licenses/license-list.html#NonFreeSoftwareLicenses +[fsf-list-free-documentation]: https://www.gnu.org/licenses/license-list.html#FreeDocumentationLicenses +[fsf-list-non-free-documentation]: https://www.gnu.org/licenses/license-list.html#NonFreeDocumentationLicenses +[fsf-list-practical]: https://www.gnu.org/licenses/license-list.html#GPLOther +[fsf-list-font]: https://www.gnu.org/licenses/license-list.html#Fonts +[fsf-list-viewpoint]: https://www.gnu.org/licenses/license-list.html#OpinionLicenses +[gplv3-howto]: https://www.gnu.org/licenses/gpl.html#howto [osi-api-non-canon-2]: https://github.com/OpenSourceOrg/licenses/issues/47 [osi-api-noncanon-1]: https://github.com/OpenSourceOrg/licenses/tree/f7ff223f9694ca0d5114fc82e43c74b5c5087891#is-this-authoritative [osi-api]: https://api.opensource.org/ [spdx-list]: https://spdx.org/licenses/ +[spdx-gpl-3.0-only]: https://spdx.org/licenses/GPL-3.0-only.html +[spdx-gpl-3.0-or-later]: https://spdx.org/licenses/GPL-3.0-or-later.html diff --git a/pull.py b/pull.py index 9f40335..993fff7 100755 --- a/pull.py +++ b/pull.py @@ -92,126 +92,126 @@ TAG_OVERRIDES = { } IDENTIFIERS = { - 'AGPLv1.0': {'spdx': 'AGPL-1.0'}, - 'AGPLv3.0': {'spdx': 'AGPL-3.0'}, - 'AcademicFreeLicense1.1': {'spdx': 'AFL-1.1'}, - 'AcademicFreeLicense1.2': {'spdx': 'AFL-1.2'}, - 'AcademicFreeLicense2.0': {'spdx': 'AFL-2.0'}, - 'AcademicFreeLicense2.1': {'spdx': 'AFL-2.1'}, - 'AcademicFreeLicense3.0': {'spdx': 'AFL-3.0'}, - 'Aladdin': {'spdx': 'Aladdin'}, - 'apache1.1': {'spdx': 'Apache-1.1'}, - 'apache1': {'spdx': 'Apache-1.0'}, - 'apache2': {'spdx': 'Apache-2.0'}, - 'apsl1': {'spdx': 'APSL-1.0'}, - 'apsl2': {'spdx': 'APSL-2.0'}, - 'ArtisticLicense': {'spdx': 'Artistic-1.0'}, - 'ArtisticLicense2': {'spdx': 'Artistic-2.0'}, - 'BerkeleyDB': {'spdx': 'Sleepycat'}, - 'bittorrent': {'spdx': 'BitTorrent-1.1'}, - 'boost': {'spdx': 'BSL-1.0'}, - 'ccby': {'spdx': 'CC-BY-4.0'}, - 'CC-BY-NC-1.0': {'spdx': 'CC-BY-NC-1.0'}, - 'CC-BY-NC-2.0': {'spdx': 'CC-BY-NC-2.0'}, - 'CC-BY-NC-2.5': {'spdx': 'CC-BY-NC-2.5'}, - 'CC-BY-NC-3.0': {'spdx': 'CC-BY-NC-3.0'}, - 'CC-BY-NC-4.0': {'spdx': 'CC-BY-NC-4.0'}, - 'CC-BY-ND-1.0': {'spdx': 'CC-BY-ND-1.0'}, - 'CC-BY-ND-2.0': {'spdx': 'CC-BY-ND-2.0'}, - 'CC-BY-ND-2.5': {'spdx': 'CC-BY-ND-2.5'}, - 'CC-BY-ND-3.0': {'spdx': 'CC-BY-ND-3.0'}, - 'CC-BY-ND-4.0': {'spdx': 'CC-BY-ND-4.0'}, - 'ccbysa': {'spdx': 'CC-BY-SA-4.0'}, - 'CC0': {'spdx': 'CC0-1.0'}, - 'CDDL': {'spdx': 'CDDL-1.0'}, - 'CPAL': {'spdx': 'CPAL-1.0'}, - 'CeCILL': {'spdx': 'CECILL-2.0'}, - 'CeCILL-B': {'spdx': 'CECILL-B'}, - 'CeCILL-C': {'spdx': 'CECILL-C'}, - 'ClarifiedArtistic': {'spdx': 'ClArtistic'}, - 'clearbsd': {'spdx': 'BSD-3-Clause-Clear'}, - 'CommonPublicLicense10': {'spdx': 'CPL-1.0'}, - 'cpol': {'spdx': 'CPOL-1.02'}, - 'Condor': {'spdx': 'Condor-1.1'}, - 'ECL2.0': {'spdx': 'ECL-2.0'}, - 'eCos11': {'spdx': 'RHeCos-1.1'}, - 'eCos2.0': {'spdx': 'GPL-2.0+ WITH eCos-exception-2.0'}, - 'EPL': {'spdx': 'EPL-1.0'}, - 'EPL2': {'spdx': 'EPL-2.0'}, # not in license-list-XML yet - 'EUDataGrid': {'spdx': 'EUDatagrid'}, - 'EUPL': {'spdx': 'EUPL-1.1'}, - 'Eiffel': {'spdx': 'EFL-2.0'}, - 'Expat': {'spdx': 'MIT'}, - 'FDLv1.1': {'spdx': 'GFDL-1.1'}, - 'FDLv1.2': {'spdx': 'GFDL-1.2'}, - 'FDLv1.3': {'spdx': 'GFDL-1.3'}, - 'FreeBSD': {'spdx': 'BSD-2-Clause-FreeBSD'}, - 'freetype': {'spdx': 'FTL'}, - 'GNUAllPermissive': {'spdx': 'FSFAP'}, - 'GNUGPLv3': {'spdx': 'GPL-3.0'}, - 'gnuplot': {'spdx': 'gnuplot'}, - 'GPLv2': {'spdx': 'GPL-2.0'}, - 'HPND': {'spdx': 'HPND'}, - 'IBMPL': {'spdx': 'IPL-1.0'}, - 'iMatix': {'spdx': 'iMatix'}, - 'imlib': {'spdx': 'Imlib2'}, - 'ijg': {'spdx': 'IJG'}, - 'intel': {'spdx': 'Intel'}, - 'IPAFONT': {'spdx': 'IPA'}, - 'ISC': {'spdx': 'ISC'}, - 'JSON': {'spdx': 'JSON'}, - 'LGPLv3': {'spdx': 'LGPL-3.0'}, - 'LGPLv2.1': {'spdx': 'LGPL-2.1'}, - 'LPPL-1.2': {'spdx': 'LPPL-1.2'}, - 'LPPL-1.3a': {'spdx': 'LPPL-1.3a'}, - 'lucent102': {'spdx': 'LPL-1.02'}, - 'ModifiedBSD': {'spdx': 'BSD-3-Clause'}, - 'MPL': {'spdx': 'MPL-1.1'}, - 'MPL-2.0': {'spdx':'MPL-2.0'}, - 'ms-pl': {'spdx': 'MS-PL'}, - 'ms-rl': {'spdx': 'MS-RL'}, - 'NASA': {'spdx': 'NASA-1.3'}, - 'NCSA': {'spdx':'NCSA'}, - 'newOpenLDAP': {'spdx': 'OLDAP-2.7'}, - 'Nokia': {'spdx': 'Nokia'}, - 'NoLicense': {'spdx': 'NONE'}, - 'NOSL': {'spdx': 'NOSL'}, - 'NPL-1.0': {'spdx': 'NPL-1.0'}, - 'NPL-1.1': {'spdx': 'NPL-1.1'}, - 'ODbl': {'spdx': 'ODbL-1.0'}, - 'oldOpenLDAP': {'spdx': 'OLDAP-2.3'}, - 'OpenPublicL': {'spdx': 'OPL-1.0'}, - 'OpenSSL': {'spdx': 'OpenSSL'}, - 'OriginalBSD': {'spdx': 'BSD-4-Clause'}, - 'OSL-1.0': {'spdx': 'OSL-1.0'}, - 'OSL-1.1': {'spdx': 'OSL-1.1'}, - 'OSL-2.0': {'spdx': 'OSL-2.0'}, - 'OSL-2.1': {'spdx': 'OSL-2.1'}, - 'OSL-3.0': {'spdx': 'OSL-3.0'}, - 'PHP-3.01': {'spdx': 'PHP-3.01'}, - 'Python2.0': {'spdx': 'Python-2.0'}, - 'QPL': {'spdx': 'QPL-1.0'}, - 'RPSL': {'spdx': 'RPSL-1.0'}, - 'Ruby': {'spdx': 'Ruby'}, - 'SGIFreeB': {'spdx': 'SGI-B-2.0'}, - 'SILOFL': {'spdx': 'OFL-1.1'}, - 'SPL': {'spdx': 'SPL-1.0'}, - 'StandardMLofNJ': {'spdx': 'SMLNJ'}, - 'Unlicense': {'spdx': 'Unlicense'}, - 'UPL': {'spdx': 'UPL-1.0'}, - 'Vim': {'spdx': 'Vim'}, - 'W3C': {'spdx': 'W3C'}, - 'Watcom': {'spdx': 'Watcom-1.0'}, - 'WTFPL': {'spdx': 'WTFPL'}, - 'X11License': {'spdx': 'X11'}, - 'XFree861.1License': {'spdx': 'XFree86-1.1'}, - 'xinetd': {'spdx': 'xinetd'}, - 'Yahoo': {'spdx': 'YPL-1.1'}, - 'Zend': {'spdx': 'Zend-2.0'}, - 'Zimbra': {'spdx': 'Zimbra-1.3'}, - 'ZLib': {'spdx': 'Zlib'}, - 'Zope2.0': {'spdx': 'ZPL-2.0'}, - 'Zope2.1': {'spdx': 'ZPL-2.1'}, + 'AGPLv1.0': {'spdx': ['AGPL-1.0']}, + 'AGPLv3.0': {'spdx': ['AGPL-3.0-or-later', 'AGPL-3.0-only', 'AGPL-3.0']}, + 'AcademicFreeLicense1.1': {'spdx': ['AFL-1.1']}, + 'AcademicFreeLicense1.2': {'spdx': ['AFL-1.2']}, + 'AcademicFreeLicense2.0': {'spdx': ['AFL-2.0']}, + 'AcademicFreeLicense2.1': {'spdx': ['AFL-2.1']}, + 'AcademicFreeLicense3.0': {'spdx': ['AFL-3.0']}, + 'Aladdin': {'spdx': ['Aladdin']}, + 'apache1.1': {'spdx': ['Apache-1.1']}, + 'apache1': {'spdx': ['Apache-1.0']}, + 'apache2': {'spdx': ['Apache-2.0']}, + 'apsl1': {'spdx': ['APSL-1.0']}, + 'apsl2': {'spdx': ['APSL-2.0']}, + 'ArtisticLicense': {'spdx': ['Artistic-1.0']}, + 'ArtisticLicense2': {'spdx': ['Artistic-2.0']}, + 'BerkeleyDB': {'spdx': ['Sleepycat']}, + 'bittorrent': {'spdx': ['BitTorrent-1.1']}, + 'boost': {'spdx': ['BSL-1.0']}, + 'ccby': {'spdx': ['CC-BY-4.0']}, + 'CC-BY-NC-1.0': {'spdx': ['CC-BY-NC-1.0']}, + 'CC-BY-NC-2.0': {'spdx': ['CC-BY-NC-2.0']}, + 'CC-BY-NC-2.5': {'spdx': ['CC-BY-NC-2.5']}, + 'CC-BY-NC-3.0': {'spdx': ['CC-BY-NC-3.0']}, + 'CC-BY-NC-4.0': {'spdx': ['CC-BY-NC-4.0']}, + 'CC-BY-ND-1.0': {'spdx': ['CC-BY-ND-1.0']}, + 'CC-BY-ND-2.0': {'spdx': ['CC-BY-ND-2.0']}, + 'CC-BY-ND-2.5': {'spdx': ['CC-BY-ND-2.5']}, + 'CC-BY-ND-3.0': {'spdx': ['CC-BY-ND-3.0']}, + 'CC-BY-ND-4.0': {'spdx': ['CC-BY-ND-4.0']}, + 'ccbysa': {'spdx': ['CC-BY-SA-4.0']}, + 'CC0': {'spdx': ['CC0-1.0']}, + 'CDDL': {'spdx': ['CDDL-1.0']}, + 'CPAL': {'spdx': ['CPAL-1.0']}, + 'CeCILL': {'spdx': ['CECILL-2.0']}, + 'CeCILL-B': {'spdx': ['CECILL-B']}, + 'CeCILL-C': {'spdx': ['CECILL-C']}, + 'ClarifiedArtistic': {'spdx': ['ClArtistic']}, + 'clearbsd': {'spdx': ['BSD-3-Clause-Clear']}, + 'CommonPublicLicense10': {'spdx': ['CPL-1.0']}, + 'cpol': {'spdx': ['CPOL-1.02']}, + 'Condor': {'spdx': ['Condor-1.1']}, + 'ECL2.0': {'spdx': ['ECL-2.0']}, + 'eCos11': {'spdx': ['RHeCos-1.1']}, + 'eCos2.0': {'spdx': ['GPL-2.0+ WITH eCos-exception-2.0']}, + 'EPL': {'spdx': ['EPL-1.0']}, + 'EPL2': {'spdx': ['EPL-2.0']}, # not in license-list-XML yet + 'EUDataGrid': {'spdx': ['EUDatagrid']}, + 'EUPL': {'spdx': ['EUPL-1.1']}, + 'Eiffel': {'spdx': ['EFL-2.0']}, + 'Expat': {'spdx': ['MIT']}, + 'FDLv1.1': {'spdx': ['GFDL-1.1-or-later', 'GFDL-1.1-only', 'GFDL-1.1']}, + 'FDLv1.2': {'spdx': ['GFDL-1.2-or-later', 'GFDL-1.2-only', 'GFDL-1.2']}, + 'FDLv1.3': {'spdx': ['GFDL-1.3-or-later', 'GFDL-1.3-only', 'GFDL-1.3']}, + 'FreeBSD': {'spdx': ['BSD-2-Clause-FreeBSD']}, + 'freetype': {'spdx': ['FTL']}, + 'GNUAllPermissive': {'spdx': ['FSFAP']}, + 'GNUGPLv3': {'spdx': ['GPL-3.0-or-later', 'GPL-3.0-only', 'GPL-3.0']}, + 'gnuplot': {'spdx': ['gnuplot']}, + 'GPLv2': {'spdx': ['GPL-2.0-or-later', 'GPL-2.0-only', 'GPL-2.0']}, + 'HPND': {'spdx': ['HPND']}, + 'IBMPL': {'spdx': ['IPL-1.0']}, + 'iMatix': {'spdx': ['iMatix']}, + 'imlib': {'spdx': ['Imlib2']}, + 'ijg': {'spdx': ['IJG']}, + 'intel': {'spdx': ['Intel']}, + 'IPAFONT': {'spdx': ['IPA']}, + 'ISC': {'spdx': ['ISC']}, + 'JSON': {'spdx': ['JSON']}, + 'LGPLv3': {'spdx': ['LGPL-3.0-or-later', 'LGPL-3.0-only', 'LGPL-3.0']}, + 'LGPLv2.1': {'spdx': ['LGPL-2.1-or-later', 'LGPL-2.1-only', 'LGPL-2.1']}, + 'LPPL-1.2': {'spdx': ['LPPL-1.2']}, + 'LPPL-1.3a': {'spdx': ['LPPL-1.3a']}, + 'lucent102': {'spdx': ['LPL-1.02']}, + 'ModifiedBSD': {'spdx': ['BSD-3-Clause']}, + 'MPL': {'spdx': ['MPL-1.1']}, + 'MPL-2.0': {'spdx': ['MPL-2.0']}, + 'ms-pl': {'spdx': ['MS-PL']}, + 'ms-rl': {'spdx': ['MS-RL']}, + 'NASA': {'spdx': ['NASA-1.3']}, + 'NCSA': {'spdx': ['NCSA']}, + 'newOpenLDAP': {'spdx': ['OLDAP-2.7']}, + 'Nokia': {'spdx': ['Nokia']}, + 'NoLicense': {'spdx': ['NONE']}, + 'NOSL': {'spdx': ['NOSL']}, + 'NPL-1.0': {'spdx': ['NPL-1.0']}, + 'NPL-1.1': {'spdx': ['NPL-1.1']}, + 'ODbl': {'spdx': ['ODbL-1.0']}, + 'oldOpenLDAP': {'spdx': ['OLDAP-2.3']}, + 'OpenPublicL': {'spdx': ['OPL-1.0']}, + 'OpenSSL': {'spdx': ['OpenSSL']}, + 'OriginalBSD': {'spdx': ['BSD-4-Clause']}, + 'OSL-1.0': {'spdx': ['OSL-1.0']}, + 'OSL-1.1': {'spdx': ['OSL-1.1']}, + 'OSL-2.0': {'spdx': ['OSL-2.0']}, + 'OSL-2.1': {'spdx': ['OSL-2.1']}, + 'OSL-3.0': {'spdx': ['OSL-3.0']}, + 'PHP-3.01': {'spdx': ['PHP-3.01']}, + 'Python2.0': {'spdx': ['Python-2.0']}, + 'QPL': {'spdx': ['QPL-1.0']}, + 'RPSL': {'spdx': ['RPSL-1.0']}, + 'Ruby': {'spdx': ['Ruby']}, + 'SGIFreeB': {'spdx': ['SGI-B-2.0']}, + 'SILOFL': {'spdx': ['OFL-1.1']}, + 'SPL': {'spdx': ['SPL-1.0']}, + 'StandardMLofNJ': {'spdx': ['SMLNJ']}, + 'Unlicense': {'spdx': ['Unlicense']}, + 'UPL': {'spdx': ['UPL-1.0']}, + 'Vim': {'spdx': ['Vim']}, + 'W3C': {'spdx': ['W3C']}, + 'Watcom': {'spdx': ['Watcom-1.0']}, + 'WTFPL': {'spdx': ['WTFPL']}, + 'X11License': {'spdx': ['X11']}, + 'XFree861.1License': {'spdx': ['XFree86-1.1']}, + 'xinetd': {'spdx': ['xinetd']}, + 'Yahoo': {'spdx': ['YPL-1.1']}, + 'Zend': {'spdx': ['Zend-2.0']}, + 'Zimbra': {'spdx': ['Zimbra-1.3']}, + 'ZLib': {'spdx': ['Zlib']}, + 'Zope2.0': {'spdx': ['ZPL-2.0']}, + 'Zope2.1': {'spdx': ['ZPL-2.1']}, } @@ -287,11 +287,14 @@ def save(licenses, dir=os.curdir): with open(license_path, 'w') as f: json.dump(obj=license, fp=f, indent=2, sort_keys=True) f.write('\n') - for scheme, identifier in license.get('identifiers', {}).items(): + for scheme, identifiers in license.get('identifiers', {}).items(): scheme_dir = os.path.join(dir, scheme) os.makedirs(scheme_dir, exist_ok=True) - id_path = os.path.join(scheme_dir, '{}.json'.format(identifier)) - os.link(license_path, id_path) + if isinstance(identifiers, str): + identifiers = [identifiers] + for identifier in identifiers: + id_path = os.path.join(scheme_dir, '{}.json'.format(identifier)) + os.link(license_path, id_path) with open(os.path.join(dir, 'licenses-full.json'), 'w') as f: json.dump(obj=full_index, fp=f, indent=2, sort_keys=True) f.write('\n')