text: # -*- coding: utf-8 -*-
  from __future__ import print_function
  
  import abc
  import clu.abstract
  import os
  import sys
  
  abstract = abc.abstractmethod
  
  from clu.constants.enums import System, SYSTEM
  # from clu.config.abc import FrozenKeyMap
  from clu.config.base import NamespacedMutableMapping
  from clu.fs.appdirectories import AppDirs
  from clu.fs.filesystem import TemporaryName, Directory
  from clu.fs.misc import extension, filesize
  from clu.fs import pypath
  from clu.predicates import isiterable
  from clu.typology import isvalidpath
  from clu.exporting import Exporter
  
  exporter = Exporter(path=__file__)
  export = exporter.decorator()
  
  @export
  class FileName(clu.abstract.AppName, metaclass=abc.ABCMeta):
      
      @classmethod
      def __init_subclass__(cls, filename=None, **kwargs):
          """ Transform the “filename” class-keyword into “filename” and “filesuffix”
              read-only descriptor values
          """
          super(FileName, cls).__init_subclass__(**kwargs)
          cls.filename = clu.abstract.ValueDescriptor(filename)
          cls.filesuffix = clu.abstract.ValueDescriptor((filename is not None) \
                                                        and extension(filename) \
                                                         or None)
      
      def __init__(self, *args, **kwargs):
          """ Stub __init__(…) method, throwing a lookup error for subclasses
              upon which the “filename” value is None
          """
          if type(self).filename is None:
              raise LookupError("Cannot instantiate a base config class "
                                "(filename is None)")
      
      @staticmethod
      def systems():
          """ Return a set of the valid “clu.constants.enums.System” enum members
              for the current platform
          """
          out = { SYSTEM }
          if SYSTEM is System.DARWIN:
              out |= { System.LINUX2 }
          return out
      
      @staticmethod
      def sys_path_dirs():
          """ Return a tuple of “clu.fs.filesystem.Directory” instances, each
              corresponding to one of the valid entries in the “sys.path” list
          """
          pypath.remove_invalid_paths()
          return tuple(Directory(p) for p in sys.path if os.path.isdir(p))
      
      @classmethod
      def site_dirs(cls):
          """ Return a set of “clu.fs.filesystem.Directory” instances pointing
              to the filesystem locations of site- (aka system-) config directories
          """
          if 'XDG_CONFIG_DIRS' in os.environ:
              xdgs = os.environ.get('XDG_CONFIG_DIRS')
              if xdgs is None:
                  sdirs = set()
              else:
                  dirs = (Directory(xdg) for xdg in xdgs.split(os.pathsep))
                  sdirs = set(d.subdirectory(cls.appname) for d in dirs)
          else:
              sdirs = set()
          for system in cls.systems():
              appdir = AppDirs(appname=cls.appname, system=system)
              sdirs |= { appdir.site_config }
          return sdirs
      
      @classmethod
      def user_dirs(cls):
          """ Return a set of “clu.fs.filesystem.Directory” instances pointing
              to the filesystem locations of user-configuration directories
          """
          if 'XDG_CONFIG_HOME' in os.environ:
              xdg = os.environ.get('XDG_CONFIG_HOME')
              if xdg is None:
                  udirs = set()
              else:
                  udirs = set([Directory(xdg).subdirectory(cls.appname)])
          else:
              udirs = set()
          for system in cls.systems():
              appdir = AppDirs(appname=cls.appname, system=system)
              udirs |= { appdir.user_config }
          return udirs
      
      @classmethod
      def find_file(cls, filename=None, *, extra_site_dirs=None,
                                           extra_user_dirs=None,
                                           search_sys_path=False):
          """ Search available directories for the named file """
          file_name = filename or cls.filename
          site_dirs = cls.site_dirs()
          user_dirs = cls.user_dirs()
          
          if isiterable(extra_site_dirs):
              site_dirs.update(extra_site_dirs)
          if isiterable(extra_user_dirs):
              user_dirs.update(extra_user_dirs)
          
          root_dir = None
          # Search site directories first:
          for site_dir in site_dirs:
              if site_dir.exists:
                  if file_name in site_dir:
                      root_dir = site_dir
                      break
          # Then possibly try “sys.path”:
          if search_sys_path:
              for syspath_dir in cls.sys_path_dirs():
                  if syspath_dir.exists:
                      if file_name in syspath_dir:
                          root_dir = syspath_dir
                          break
          # Then search user directories:
          for user_dir in user_dirs:
              if user_dir.exists:
                  if file_name in user_dir:
                      root_dir = user_dir
                      break
          if root_dir is None:
              raise FileNotFoundError(f"Couldn’t find config file {file_name}")
          return Directory(root_dir.realpath()).subpath(file_name)
  
  @export
  class FileBase(NamespacedMutableMapping, FileName): # type: ignore
      
      """ The FileBase abstract base class furnishes two methods:
          
              • “dump(…)” and
              • “load(…)”;
          
          … and defines two abstract methods that are called by
          the above two definitions:
          
              • “dumps(…)” and
              • “loads(…)”.
          
          … Anyone who has serialized or deserialized anything at
          all with Python – JSON, YAML, TOML, plists, pickles,
          msgpacks, protobufs, bencodes… whatevs – in the last 15
          years, will no doubt recognize these method names and 
          will know immediately what they do. If in case, somehow,
          you don’t know, lookit the docstrings for the methods
          themselves, doggie. Yes.
      """
      
      def __init__(self, filepath=None, *args, **kwargs):
          """ FileBase __init__(…):
              
              Keyword Arguments:
                  • filepath          (default: None)*
                  • filename          (default: None)
                  • extra_site_dirs   (default: None)
                  • extra_user_dirs   (default: None)
                  • search_sys_path   (default: False)
              
              * The “filepath” arg will be ignored if “FileName.find_file(…)”
                returns a valid path to a file
          """
          filename        = kwargs.pop('filename', None)
          extra_site_dirs = kwargs.pop('extra_site_dirs', None)
          extra_user_dirs = kwargs.pop('extra_user_dirs', None)
          search_sys_path = kwargs.pop('search_sys_path', False)
          
          try:
              super(FileBase, self).__init__(*args, **kwargs)
          except TypeError:
              super(FileBase, self).__init__()
          
          if isvalidpath(filepath):
              self.filepath = filepath
          else:
              try:
                  self.filepath = type(self).find_file(filename=filename,
                                                       extra_user_dirs=extra_user_dirs,
                                                       extra_site_dirs=extra_site_dirs,
                                                       search_sys_path=search_sys_path)
              except FileNotFoundError:
                  self.filepath = filename
          
          if isvalidpath(self.filepath):
              self.load()
      
      @property
      def name(self):
          return self.filepath
      
      @property
      def basename(self):
          """ The basename (aka the filename) of the config file path. """
          return os.path.basename(self.name)
      
      @property
      def dirname(self):
          """ The dirname (aka the enclosing directory) of the config file. """
          return self.parent()
      
      @property
      def exists(self):
          return os.path.isfile(self.name)
      
      @property
      def filesize(self):
          """ The filesize for the config file """
          return filesize(self.name)
      
      def split(self):
          """ Return (dirname, basename) e.g. for /yo/dogg/i/heard/youlike.jpg,
              you get back (Directory("/yo/dogg/i/heard"), "youlike.jpg")
          """
          return self.dirname, self.basename
      
      def parent(self):
          """ Sugar for `os.path.abspath(os.path.join(self.name, os.pardir))`
              which, if you are curious, gets you the parent directory of
              the instances’ target filename, wrapped in a Directory
              instance.
          """
          return self.directory(os.path.abspath(
                                os.path.join(self.name,
                                             os.pardir)))
      
      def load(self, filepath=None):
          """ Load from a file, either from a specified “filepath,” or
              from the internally-discovered path to the configuration file
          """
          if filepath is None:
              filepath = self.filepath
          if not isvalidpath(filepath):
              raise FileNotFoundError("No valid filepath available for load()")
          
          with open(filepath, "r") as handle:
              loaded = handle.read()
          
          return self.loads(loaded)
      
      @abstract
      def loads(self, loaded):
          """ Load a datatype-specific namespaced dictionary from
              arbitrary encoded string data
          """
          ...
      
      def dump(self, filepath=None):
          """ Dump to a file, either to the specified “filepath,” or
              to the internally-discovered path to the configuration file
          """
          if filepath is None:
              filepath = self.filepath
          if filepath is None:
              raise FileNotFoundError("No valid filepath available for dump()")
          
          dumped = self.dumps()
          if not dumped:
              return dumped
          
          with TemporaryName(prefix="filebase-dump-",
                             suffix=type(self).filesuffix,
                             randomized=True) as tdmp:
              assert tdmp.write(dumped)
              assert tdmp.copy(filepath)
          
          assert isvalidpath(filepath)
          return dumped
      
      @abstract
      def dumps(self):
          """ Dump a datatype-specific encoded string data out from
              the internal namespaced dictionary
          """
          ...
  
  # Assign the modules’ `__all__` and `__dir__` using the exporter:
  __all__, __dir__ = exporter.all_and_dir()
  