# SPDX-FileCopyrightText: 2019 Free Software Foundation Europe e.V. <https://fsfe.org># SPDX-FileCopyrightText: 2023 Nico Rikken <nico.rikken@fsfe.org>## SPDX-License-Identifier: GPL-3.0-or-later"""Functions for downloading license files from spdx/license-list-data."""importerrnoimportloggingimportosimportshutilimportsysimporturllib.requestfromargparseimportArgumentParser,Namespacefromgettextimportgettextas_frompathlibimportPathfromtypingimportIO,Optionalfromurllib.errorimportURLErrorfromurllib.parseimporturljoinfrom._licensesimportALL_NON_DEPRECATED_MAPfrom._utilimport(_LICENSEREF_PATTERN,PathType,StrPath,find_licenses_directory,print_incorrect_spdx_identifier,)from.projectimportProjectfrom.reportimportProjectReport_LOGGER=logging.getLogger(__name__)# All raw text files are available as files underneath this path._SPDX_REPOSITORY_BASE_URL=("https://raw.githubusercontent.com/spdx/license-list-data/master/text/")
[docs]defdownload_license(spdx_identifier:str)->str:"""Download the license text from the SPDX repository. Args: spdx_identifier: SPDX identifier of the license. Raises: URLError: if the license could not be downloaded. Returns: The license text. """# This is fairly naive, but I can't see anything wrong with it.url=urljoin(_SPDX_REPOSITORY_BASE_URL,"".join((spdx_identifier,".txt")))_LOGGER.debug("downloading license from '%s'",url)# TODO: Cache result?withurllib.request.urlopen(url)asresponse:ifresponse.getcode()==200:returnresponse.read().decode("utf-8")raiseURLError("Status code was not 200")
[docs]defput_license_in_file(spdx_identifier:str,destination:StrPath,source:Optional[StrPath]=None,)->None:"""Download a license and put it in the destination file. This function exists solely for convenience. Args: spdx_identifier: SPDX License Identifier of the license. destination: Where to put the license. source: Path to file or directory containing the text for LicenseRef licenses. Raises: URLError: if the license could not be downloaded. FileExistsError: if the license file already exists. FileNotFoundError: if the source could not be found in the directory. """header=""destination=Path(destination)destination.parent.mkdir(exist_ok=True)ifdestination.exists():raiseFileExistsError(errno.EEXIST,os.strerror(errno.EEXIST),str(destination))# LicenseRef- license; don't download anything.if_LICENSEREF_PATTERN.match(spdx_identifier):ifsource:source=Path(source)ifsource.is_dir():source=source/f"{spdx_identifier}.txt"ifnotsource.exists():raiseFileNotFoundError(errno.ENOENT,os.strerror(errno.ENOENT),str(source))shutil.copyfile(source,destination)else:destination.touch()else:text=download_license(spdx_identifier)withdestination.open("w",encoding="utf-8")asfp:fp.write(header)fp.write(text)
[docs]defadd_arguments(parser:ArgumentParser)->None:"""Add arguments to parser."""parser.add_argument("license",action="store",nargs="*",help=_("SPDX License Identifier of license"),)parser.add_argument("--all",action="store_true",help=_("download all missing licenses detected in the project"),)parser.add_argument("--output","-o",dest="file",action="store",type=PathType("w"))parser.add_argument("--source",action="store",type=PathType("r"),help=_("source from which to copy custom LicenseRef- licenses, either"" a directory that contains the file or the file itself"),)
[docs]defrun(args:Namespace,project:Project,out:IO[str]=sys.stdout)->int:"""Download license and place it in the LICENSES/ directory."""def_already_exists(path:StrPath)->None:out.write(_("Error: {spdx_identifier} already exists.").format(spdx_identifier=path))out.write("\n")def_not_found(path:StrPath)->None:out.write(_("Error: {path} does not exist.").format(path=path))def_could_not_download(identifier:str)->None:out.write(_("Error: Failed to download license."))out.write(" ")ifidentifiernotinALL_NON_DEPRECATED_MAP:print_incorrect_spdx_identifier(identifier,out=out)else:out.write(_("Is your internet connection working?"))out.write("\n")def_successfully_downloaded(destination:StrPath)->None:out.write(_("Successfully downloaded {spdx_identifier}.").format(spdx_identifier=destination))out.write("\n")ifargs.all:# TODO: This is fairly inefficient, but gets the job done.report=ProjectReport.generate(project)licenses=report.missing_licensesifargs.file:_LOGGER.warning(_("--output has no effect when used together with --all"))args.file=Noneelifnotargs.license:args.parser.error(_("the following arguments are required: license"))eliflen(args.license)>1andargs.file:args.parser.error(_("cannot use --output with more than one license"))else:licenses=args.licensereturn_code=0forlicinlicenses:ifargs.file:destination=args.fileelse:destination=_path_to_license_file(lic,project.root)try:put_license_in_file(lic,destination=destination,source=args.source)exceptURLError:_could_not_download(lic)return_code=1exceptFileExistsErroraserr:_already_exists(err.filename)return_code=1exceptFileNotFoundErroraserr:_not_found(err.filename)return_code=1else:_successfully_downloaded(destination)returnreturn_code