Source code for statick_tool.plugins.tool.val_parser

"""Apply VAL Parser tool and gather results.

Tool is from King's College London. Tool authors are:

- Maria Fox and Derek Long - PDDL2.2 and VAL
- Richard Howey - PDDL2.2 and VAL and Continuous Effects, derived predicates,
  timed initial literals and LaTeX report in VAL
- Stephen Cresswell - PDDL2.2 Parser

https://github.com/KCL-Planning/VAL/tree/master/applications#parser
"""

import argparse
import logging
import re
import subprocess
from typing import Match, Optional, Pattern

from statick_tool.issue import Issue
from statick_tool.package import Package
from statick_tool.tool_plugin import ToolPlugin


[docs] class ValParserToolPlugin(ToolPlugin): """Apply Parser tool and gather results."""
[docs] def get_name(self) -> str: """Get name of tool. Returns: Name of the tool. """ return "val_parser"
[docs] def gather_args(self, args: argparse.Namespace) -> None: """Gather arguments. Args: args: Flags for this plugin will be added to these existing arguments. """ args.add_argument( "--val-parser-bin", dest="val_parser_bin", type=str, help="VAL Parser binary path", )
[docs] def get_binary( # pylint: disable=unused-argument self, level: Optional[str] = None, package: Optional[Package] = None ) -> str: """Get tool binary name. Args: level: The level to process (optional). package: The package to process (optional). Returns: Name of the tool binary. """ binary = "Parser" if ( self.plugin_context is not None and self.plugin_context.args.val_parser_bin is not None ): binary = self.plugin_context.args.val_parser_bin return binary
[docs] def scan(self, package: Package, level: str) -> Optional[list[Issue]]: """Run tool and gather output. Args: package: The package to process. level: The level to process. Returns: List of issues or None. """ if "pddl_domain_src" not in package or not package["pddl_domain_src"]: return [] flags: list[str] = [] flags += self.get_user_flags(level) parser_bin = self.get_binary() try: subproc_args: list[str] = ( [parser_bin] + flags + package["pddl_domain_src"] + package["pddl_problem_src"] ) output = subprocess.check_output( subproc_args, stderr=subprocess.STDOUT, universal_newlines=True ) except subprocess.CalledProcessError as ex: output = ex.output if ex.returncode != 255: logging.warning("VAL Parser failed! Returncode = %d", ex.returncode) logging.warning("%s exception: %s", self.get_name(), ex.output) return None except OSError as ex: logging.warning("Couldn't find %s! (%s)", parser_bin, ex) return None logging.debug("%s", output) if self.plugin_context and self.plugin_context.args.output_directory: with open(self.get_name() + ".log", "w", encoding="utf-8") as fid: fid.write(output) issues: list[Issue] = self.parse_tool_output(output) return issues
[docs] def parse_tool_output(self, output: str) -> list[Issue]: """Parse tool output and report issues. Args: output: Output string. Returns: List of issues. """ tool_re: str = r"(.+):\s(.+):\s(.+):\s(.+):\s(.+)\s(.+)" parse: Pattern[str] = re.compile(tool_re) issues: list[Issue] = [] issue_found = False for line in output.splitlines(): # If we find this pattern then all following lines will have an issue per # line. That's when we start parsing. if line.startswith("Errors:") and "warnings:" in line: issue_found = True continue if issue_found: match: Optional[Match[str]] = parse.match(line) if match: severity = 1 if match.group(4) == "Warning": severity = 3 elif match.group(4) == "Error": severity = 5 issue = Issue( match.group(1), int(match.group(3)), self.get_name(), "PDDL", severity, match.group(5) + " " + match.group(6), None, ) issues.append(issue) return issues