text: # -*- test-case-name: twisted.logger.test.test_flatten -*-
  # Copyright (c) Twisted Matrix Laboratories.
  # See LICENSE for details.
  
  """
  Code related to "flattening" events; that is, extracting a description of all
  relevant fields from the format string and persisting them for later
  examination.
  """
  
  from string import Formatter
  from collections import defaultdict
  
  
  aFormatter = Formatter()
  
  
  
  class KeyFlattener:
      """
      A L{KeyFlattener} computes keys for the things within curly braces in
      PEP-3101-style format strings as parsed by L{string.Formatter.parse}.
      """
  
      def __init__(self):
          """
          Initialize a L{KeyFlattener}.
          """
          self.keys = defaultdict(lambda: 0)
  
  
      def flatKey(self, fieldName, formatSpec, conversion):
          """
          Compute a string key for a given field/format/conversion.
  
          @param fieldName: A format field name.
          @type fieldName: L{str}
  
          @param formatSpec: A format spec.
          @type formatSpec: L{str}
  
          @param conversion: A format field conversion type.
          @type conversion: L{str}
  
          @return: A key specific to the given field, format and conversion, as
              well as the occurrence of that combination within this
              L{KeyFlattener}'s lifetime.
          @rtype: L{str}
          """
          result = (
              "{fieldName}!{conversion}:{formatSpec}"
              .format(
                  fieldName=fieldName,
                  formatSpec=(formatSpec or ""),
                  conversion=(conversion or ""),
              )
          )
          self.keys[result] += 1
          n = self.keys[result]
          if n != 1:
              result += "/" + str(self.keys[result])
          return result
  
  
  
  def flattenEvent(event):
      """
      Flatten the given event by pre-associating format fields with specific
      objects and callable results in a L{dict} put into the C{"log_flattened"}
      key in the event.
  
      @param event: A logging event.
      @type event: L{dict}
      """
      if event.get("log_format", None) is None:
          return
  
      if "log_flattened" in event:
          fields = event["log_flattened"]
      else:
          fields = {}
  
      keyFlattener = KeyFlattener()
  
      for (literalText, fieldName, formatSpec, conversion) in (
          aFormatter.parse(event["log_format"])
      ):
          if fieldName is None:
              continue
  
          if conversion != "r":
              conversion = "s"
  
          flattenedKey = keyFlattener.flatKey(fieldName, formatSpec, conversion)
          structuredKey = keyFlattener.flatKey(fieldName, formatSpec, "")
  
          if flattenedKey in fields:
              # We've already seen and handled this key
              continue
  
          if fieldName.endswith(u"()"):
              fieldName = fieldName[:-2]
              callit = True
          else:
              callit = False
  
          field = aFormatter.get_field(fieldName, (), event)
          fieldValue = field[0]
  
          if conversion == "r":
              conversionFunction = repr
          else:  # Above: if conversion is not "r", it's "s"
              conversionFunction = str
  
          if callit:
              fieldValue = fieldValue()
  
          flattenedValue = conversionFunction(fieldValue)
          fields[flattenedKey] = flattenedValue
          fields[structuredKey] = fieldValue
  
      if fields:
          event["log_flattened"] = fields
  
  
  
  def extractField(field, event):
      """
      Extract a given format field from the given event.
  
      @param field: A string describing a format field or log key.  This is the
          text that would normally fall between a pair of curly braces in a
          format string: for example, C{"key[2].attribute"}.  If a conversion is
          specified (the thing after the C{"!"} character in a format field) then
          the result will always be L{unicode}.
      @type field: L{str} (native string)
  
      @param event: A log event.
      @type event: L{dict}
  
      @return: A value extracted from the field.
      @rtype: L{object}
  
      @raise KeyError: if the field is not found in the given event.
      """
      keyFlattener = KeyFlattener()
      [[literalText, fieldName, formatSpec, conversion]] = aFormatter.parse(
          "{" + field + "}"
      )
      key = keyFlattener.flatKey(fieldName, formatSpec, conversion)
      if "log_flattened" not in event:
          flattenEvent(event)
      return event["log_flattened"][key]
  
  
  
  def flatFormat(event):
      """
      Format an event which has been flattened with L{flattenEvent}.
  
      @param event: A logging event.
      @type event: L{dict}
  
      @return: A formatted string.
      @rtype: L{unicode}
      """
      fieldValues = event["log_flattened"]
      s = []
      keyFlattener = KeyFlattener()
      formatFields = aFormatter.parse(event["log_format"])
      for literalText, fieldName, formatSpec, conversion in formatFields:
          s.append(literalText)
          if fieldName is not None:
              key = keyFlattener.flatKey(
                      fieldName, formatSpec, conversion or "s")
              s.append(str(fieldValues[key]))
      return u"".join(s)
  