content: #
  # Copyright (c) 2017 Hewlett Packard Enterprise
  #
  # SPDX-License-Identifier: Apache-2.0
  """
  ================
  Custom Formatter
  ================
  
  This formatter outputs the issues in custom machine-readable format.
  
  default template: ``{abspath}:{line}: {test_id}[bandit]: {severity}: {msg}``
  
  :Example:
  
  .. code-block:: none
  
      /usr/lib/python3.6/site-packages/openlp/core/utils/__init__.py:\
  405: B310[bandit]: MEDIUM: Audit url open for permitted schemes. \
  Allowing use of file:/ or custom schemes is often unexpected.
  
  .. versionadded:: 1.5.0
  
  .. versionchanged:: 1.7.3
      New field `CWE` added to output
  
  """
  import logging
  import os
  import re
  import string
  import sys
  
  from bandit.core import test_properties
  
  LOG = logging.getLogger(__name__)
  
  
  class SafeMapper(dict):
      """Safe mapper to handle format key errors"""
  
      @classmethod  # To prevent PEP8 warnings in the test suite
      def __missing__(cls, key):
          return "{%s}" % key
  
  
  @test_properties.accepts_baseline
  def report(manager, fileobj, sev_level, conf_level, template=None):
      """Prints issues in custom format
  
      :param manager: the bandit manager object
      :param fileobj: The output file object, which may be sys.stdout
      :param sev_level: Filtering severity level
      :param conf_level: Filtering confidence level
      :param template: Output template with non-terminal tags <N>
                      (default: '{abspath}:{line}:
                      {test_id}[bandit]: {severity}: {msg}')
      """
  
      machine_output = {"results": [], "errors": []}
      for fname, reason in manager.get_skipped():
          machine_output["errors"].append({"filename": fname, "reason": reason})
  
      results = manager.get_issue_list(
          sev_level=sev_level, conf_level=conf_level
      )
  
      msg_template = template
      if template is None:
          msg_template = "{abspath}:{line}: {test_id}[bandit]: {severity}: {msg}"
  
      # Dictionary of non-terminal tags that will be expanded
      tag_mapper = {
          "abspath": lambda issue: os.path.abspath(issue.fname),
          "relpath": lambda issue: os.path.relpath(issue.fname),
          "line": lambda issue: issue.lineno,
          "col": lambda issue: issue.col_offset,
          "end_col": lambda issue: issue.end_col_offset,
          "test_id": lambda issue: issue.test_id,
          "severity": lambda issue: issue.severity,
          "msg": lambda issue: issue.text,
          "confidence": lambda issue: issue.confidence,
          "range": lambda issue: issue.linerange,
          "cwe": lambda issue: issue.cwe,
      }
  
      # Create dictionary with tag sets to speed up search for similar tags
      tag_sim_dict = {tag: set(tag) for tag, _ in tag_mapper.items()}
  
      # Parse the format_string template and check the validity of tags
      try:
          parsed_template_orig = list(string.Formatter().parse(msg_template))
          # of type (literal_text, field_name, fmt_spec, conversion)
  
          # Check the format validity only, ignore keys
          string.Formatter().vformat(msg_template, (), SafeMapper(line=0))
      except ValueError as e:
          LOG.error("Template is not in valid format: %s", e.args[0])
          sys.exit(2)
  
      tag_set = {t[1] for t in parsed_template_orig if t[1] is not None}
      if not tag_set:
          LOG.error("No tags were found in the template. Are you missing '{}'?")
          sys.exit(2)
  
      def get_similar_tag(tag):
          similarity_list = [
              (len(set(tag) & t_set), t) for t, t_set in tag_sim_dict.items()
          ]
          return sorted(similarity_list)[-1][1]
  
      tag_blacklist = []
      for tag in tag_set:
          # check if the tag is in dictionary
          if tag not in tag_mapper:
              similar_tag = get_similar_tag(tag)
              LOG.warning(
                  "Tag '%s' was not recognized and will be skipped, "
                  "did you mean to use '%s'?",
                  tag,
                  similar_tag,
              )
              tag_blacklist += [tag]
  
      # Compose the message template back with the valid values only
      msg_parsed_template_list = []
      for literal_text, field_name, fmt_spec, conversion in parsed_template_orig:
          if literal_text:
              # if there is '{' or '}', double it to prevent expansion
              literal_text = re.sub("{", "{{", literal_text)
              literal_text = re.sub("}", "}}", literal_text)
              msg_parsed_template_list.append(literal_text)
  
          if field_name is not None:
              if field_name in tag_blacklist:
                  msg_parsed_template_list.append(field_name)
                  continue
              # Append the fmt_spec part
              params = [field_name, fmt_spec, conversion]
              markers = ["", ":", "!"]
              msg_parsed_template_list.append(
                  ["{"]
                  + [f"{m + p}" if p else "" for m, p in zip(markers, params)]
                  + ["}"]
              )
  
      msg_parsed_template = (
          "".join([item for lst in msg_parsed_template_list for item in lst])
          + "\n"
      )
      with fileobj:
          for defect in results:
              evaluated_tags = SafeMapper(
                  (k, v(defect)) for k, v in tag_mapper.items()
              )
              output = msg_parsed_template.format(**evaluated_tags)
  
              fileobj.write(output)
  
      if fileobj.name != sys.stdout.name:
          LOG.info("Result written to file: %s", fileobj.name)
  