text: #!/usr/bin/env python
  # -*- coding: utf-8 -*-
  """
  API functions that can be used by external software
  """
  
  try:
      from collections import OrderedDict
  except ImportError:  # pragma: no-cover
      from ordereddict import OrderedDict  # pylint:disable=import-error
  
  import os
  import traceback
  
  import six
  from rebulk.introspector import introspect
  
  from .__version__ import __version__
  from .options import parse_options, load_config, merge_options
  from .rules import rebulk_builder
  
  
  class GuessitException(Exception):
      """
      Exception raised when guessit fails to perform a guess because of an internal error.
      """
  
      def __init__(self, string, options):
          super(GuessitException, self).__init__("An internal error has occured in guessit.\n"
                                                 "===================== Guessit Exception Report =====================\n"
                                                 "version=%s\n"
                                                 "string=%s\n"
                                                 "options=%s\n"
                                                 "--------------------------------------------------------------------\n"
                                                 "%s"
                                                 "--------------------------------------------------------------------\n"
                                                 "Please report at "
                                                 "https://github.com/guessit-io/guessit/issues.\n"
                                                 "====================================================================" %
                                                 (__version__, str(string), str(options), traceback.format_exc()))
  
          self.string = string
          self.options = options
  
  
  def configure(options=None, rules_builder=rebulk_builder, force=False):
      """
      Load configuration files and initialize rebulk rules if required.
  
      :param options:
      :type options: dict
      :param rules_builder:
      :type rules_builder:
      :param force:
      :type force: bool
      :return:
      """
      default_api.configure(options, rules_builder=rules_builder, force=force)
  
  
  def guessit(string, options=None):
      """
      Retrieves all matches from string as a dict
      :param string: the filename or release name
      :type string: str
      :param options:
      :type options: str|dict
      :return:
      :rtype:
      """
      return default_api.guessit(string, options)
  
  
  def properties(options=None):
      """
      Retrieves all properties with possible values that can be guessed
      :param options:
      :type options: str|dict
      :return:
      :rtype:
      """
      return default_api.properties(options)
  
  
  class GuessItApi(object):
      """
      An api class that can be configured with custom Rebulk configuration.
      """
  
      def __init__(self):
          """Default constructor."""
          self.rebulk = None
          self.config = None
          self.load_config_options = None
          self.advanced_config = None
  
      @classmethod
      def _fix_encoding(cls, value):
          if isinstance(value, list):
              return [cls._fix_encoding(item) for item in value]
          if isinstance(value, dict):
              return {cls._fix_encoding(k): cls._fix_encoding(v) for k, v in value.items()}
          if six.PY2 and isinstance(value, six.text_type):
              return value.encode('utf-8')
          if six.PY3 and isinstance(value, six.binary_type):
              return value.decode('ascii')
          return value
  
      @classmethod
      def _has_same_properties(cls, dic1, dic2, values):
          for value in values:
              if dic1.get(value) != dic2.get(value):
                  return False
          return True
  
      def configure(self, options=None, rules_builder=rebulk_builder, force=False, sanitize_options=True):
          """
          Load configuration files and initialize rebulk rules if required.
  
          :param options:
          :type options: str|dict
          :param rules_builder:
          :type rules_builder:
          :param force:
          :type force: bool
          :return:
          :rtype: dict
          """
          if sanitize_options:
              options = parse_options(options, True)
              options = self._fix_encoding(options)
  
          if self.config is None or self.load_config_options is None or force or \
                  not self._has_same_properties(self.load_config_options,
                                                options,
                                                ['config', 'no_user_config', 'no_default_config']):
              config = load_config(options)
              config = self._fix_encoding(config)
              self.load_config_options = options
          else:
              config = self.config
  
          advanced_config = merge_options(config.get('advanced_config'), options.get('advanced_config'))
  
          should_build_rebulk = force or not self.rebulk or not self.advanced_config or \
                                self.advanced_config != advanced_config
  
          if should_build_rebulk:
              self.advanced_config = advanced_config
              self.rebulk = rules_builder(advanced_config)
  
          self.config = config
          return self.config
  
      def guessit(self, string, options=None):  # pylint: disable=too-many-branches
          """
          Retrieves all matches from string as a dict
          :param string: the filename or release name
          :type string: str|Path
          :param options:
          :type options: str|dict
          :return:
          :rtype:
          """
          try:
              from pathlib import Path
              if isinstance(string, Path):
                  try:
                      # Handle path-like object
                      string = os.fspath(string)
                  except AttributeError:
                      string = str(string)
          except ImportError:
              pass
  
          try:
              options = parse_options(options, True)
              options = self._fix_encoding(options)
              config = self.configure(options, sanitize_options=False)
              options = merge_options(config, options)
              result_decode = False
              result_encode = False
  
              if six.PY2:
                  if isinstance(string, six.text_type):
                      string = string.encode("utf-8")
                      result_decode = True
                  elif isinstance(string, six.binary_type):
                      string = six.binary_type(string)
              if six.PY3:
                  if isinstance(string, six.binary_type):
                      string = string.decode('ascii')
                      result_encode = True
                  elif isinstance(string, six.text_type):
                      string = six.text_type(string)
  
              matches = self.rebulk.matches(string, options)
              if result_decode:
                  for match in matches:
                      if isinstance(match.value, six.binary_type):
                          match.value = match.value.decode("utf-8")
              if result_encode:
                  for match in matches:
                      if isinstance(match.value, six.text_type):
                          match.value = match.value.encode("ascii")
              return matches.to_dict(options.get('advanced', False), options.get('single_value', False),
                                     options.get('enforce_list', False))
          except:
              raise GuessitException(string, options)
  
      def properties(self, options=None):
          """
          Grab properties and values that can be generated.
          :param options:
          :type options:
          :return:
          :rtype:
          """
          options = parse_options(options, True)
          options = self._fix_encoding(options)
          config = self.configure(options, sanitize_options=False)
          options = merge_options(config, options)
          unordered = introspect(self.rebulk, options).properties
          ordered = OrderedDict()
          for k in sorted(unordered.keys(), key=six.text_type):
              ordered[k] = list(sorted(unordered[k], key=six.text_type))
          if hasattr(self.rebulk, 'customize_properties'):
              ordered = self.rebulk.customize_properties(ordered)
          return ordered
  
  
  default_api = GuessItApi()
  