blob: 4219c6b8155213b63433064e4221c70adf22f0f3 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright (c) 2020 Yash Varshney
# Copyright (c) 2023 spdx contributors
# SPDX-License-Identifier: Apache-2.0
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
import sys
from json import JSONDecodeError
from xml.parsers.expat import ExpatError
from xml.sax import SAXParseException
import click
from beartype.typing import List
from yaml.scanner import ScannerError
from spdx_tools.spdx.graph_generation import export_graph_from_document
from spdx_tools.spdx.model import Document
from spdx_tools.spdx.parser.error import SPDXParsingError
from spdx_tools.spdx.parser.parse_anything import parse_file
from spdx_tools.spdx.validation.document_validator import validate_full_spdx_document
from spdx_tools.spdx.validation.validation_message import ValidationMessage
from spdx_tools.spdx.writer.tagvalue import tagvalue_writer
from spdx_tools.spdx.writer.write_anything import write_file
@click.command()
@click.option("--infile", "-i", required=True, help="The file containing the document to be validated or converted.")
@click.option(
"--outfile",
"-o",
help="The file to write the converted document to (write a dash for output to stdout or omit for no conversion). "
"If you add the option --graph to the command the generated graph will be written to this file.",
)
@click.option(
"--version",
help='The SPDX version to be used during parsing and validation ("SPDX-2.2" or "SPDX-2.3"). '
"Will be read from the document if not provided.",
default=None,
)
@click.option("--novalidation", is_flag=True, help="Don't validate the provided document.")
@click.option(
"--graph",
is_flag=True,
default=False,
help="Generate a relationship graph from the input file. "
"The generated graph is saved to the file specified with --outfile. "
"Note: You need to install the optional dependencies 'networkx' and 'pygraphviz' for this feature.",
)
def main(infile: str, outfile: str, version: str, novalidation: bool, graph: bool):
"""
CLI-tool for validating SPDX documents and converting between RDF, TAG-VALUE, JSON, YAML and XML formats.
Formats are determined by the file endings.
To use, run: 'pyspdxtools --infile <input file name> --outfile <output file name>'
"""
try:
document: Document = parse_file(infile)
if not novalidation:
if not version:
version = document.creation_info.spdx_version
if version not in ["SPDX-2.2", "SPDX-2.3"]:
logging.error(f"This tool only supports SPDX versions SPDX-2.2 and SPDX-2.3, but got: {version}")
sys.exit(1)
validation_messages: List[ValidationMessage] = validate_full_spdx_document(document, version)
if validation_messages:
log_string = "\n".join(
["The document is invalid. The following issues have been found:"]
+ [message.validation_message for message in validation_messages]
)
logging.error(log_string)
sys.exit(1)
else:
logging.info("The document is valid.")
if outfile == "-":
tagvalue_writer.write_document(document, sys.stdout)
elif graph:
try:
export_graph_from_document(document, outfile)
except ImportError:
logging.error(
"To be able to draw a relationship graph of the parsed document "
"you need to install 'networkx' and 'pygraphviz'. Run 'pip install \".[graph_generation]\"'."
)
sys.exit(1)
elif outfile:
write_file(document, outfile, validate=False)
except NotImplementedError as err:
logging.error(
err.args[0]
+ "\nPlease note that this project is currently undergoing a major refactoring and therefore missing "
"a few features which will be added in time (refer to https://github.com/spdx/tools-python/issues "
"for insights into the current status).\n"
"In the meantime, please use the current PyPI release version."
)
sys.exit(1)
except SPDXParsingError as err:
log_string = "\n".join(
["There have been issues while parsing the provided document:"]
+ [message for message in err.get_messages()]
)
logging.error(log_string)
sys.exit(1)
except JSONDecodeError as err:
logging.error(f"Invalid JSON provided: {err.args[0]}")
sys.exit(1)
except ScannerError as err:
logging.error("Invalid YAML provided: " + "\n".join([str(arg) for arg in err.args]))
sys.exit(1)
except ExpatError as err:
logging.error(f"Invalid XML provided: {err.args[0]}")
sys.exit(1)
except SAXParseException as err:
logging.error(f"Invalid RDF-XML provided: {str(err)}")
sys.exit(1)
except FileNotFoundError as err:
logging.error(f"{err.strerror}: {err.filename}")
sys.exit(1)
if __name__ == "__main__":
main()