text: """
  Extension that adjust project file tree to include a namespace package.
  
  This extension adds a **namespace** option to
  :obj:`~pyscaffold.api.create_project` and provides correct values for the
  options **root_pkg** and **namespace_pkg** to the following functions in the
  action list.
  """
  
  import os
  from pathlib import Path
  from typing import List, cast
  
  from ..actions import Action, ActionParams, ScaffoldOpts, Structure
  from ..exceptions import InvalidIdentifier
  from ..file_system import chdir, move
  from ..identification import is_valid_identifier
  from ..log import logger
  from ..operations import remove
  from . import Extension, store_with
  
  
  class Namespace(Extension):
      """Add a namespace (container package) to the generated package."""
  
      def augment_cli(self, parser):
          """Add an option to parser that enables the namespace extension.
  
          Args:
              parser (argparse.ArgumentParser): CLI parser object
          """
          parser.add_argument(
              self.flag,
              dest=self.name,
              default=None,
              action=store_with(self),
              metavar="NS1[.NS2]",
              help="put your project inside a namespace package "
              "(default: use no namespace)",
          )
          return self
  
      def activate(self, actions: List[Action]) -> List[Action]:
          """Register an action responsible for adding namespace to the package.
  
          Args:
              actions: list of actions to perform
  
          Returns:
              list: updated list of actions
          """
          actions = self.register(
              actions, enforce_namespace_options, after="get_default_options"
          )
  
          actions = self.register(actions, add_namespace, before="version_migration")
  
          return self.register(actions, move_old_package, after="create_structure")
  
  
  def prepare_namespace(namespace_str: str) -> List[str]:
      """Check the validity of namespace_str and split it up into a list
  
      Args:
          namespace_str: namespace, e.g. "com.blue_yonder"
  
      Returns:
          list of namespaces, e.g. ["com", "com.blue_yonder"]
  
      Raises:
            :obj:`InvalidIdentifier` : raised if namespace is not valid
      """
      namespaces = namespace_str.split(".") if namespace_str else list()
      for namespace in namespaces:
          if not is_valid_identifier(namespace):
              raise InvalidIdentifier(f"{namespace} is not a valid namespace package.")
      return [".".join(namespaces[: i + 1]) for i in range(len(namespaces))]
  
  
  def enforce_namespace_options(struct: Structure, opts: ScaffoldOpts) -> ActionParams:
      """Make sure options reflect the namespace usage."""
      opts.setdefault("namespace", None)
  
      if opts["namespace"]:
          opts["ns_list"] = prepare_namespace(opts["namespace"])
          opts["root_pkg"] = opts["ns_list"][0]
          opts["qual_pkg"] = ".".join([opts["ns_list"][-1], opts["package"]])
  
      return struct, opts
  
  
  def add_namespace(struct: Structure, opts: ScaffoldOpts) -> ActionParams:
      """Prepend the namespace to a given file structure
  
      Args:
          struct: directory structure as dictionary of dictionaries
          opts: options of the project
  
      Returns:
          Directory structure as dictionary of dictionaries and input options
      """
      if not opts["namespace"]:
          return struct, opts
  
      namespace = opts["ns_list"][-1].split(".")
      base_struct = struct
      struct = cast(Structure, base_struct["src"])  # recursive types not supported yet
      pkg_struct = cast(Structure, struct[opts["package"]])
      del struct[opts["package"]]
      for sub_package in namespace:
          struct[sub_package] = {"__init__.py": ("", remove)}  # convert to PEP420
          struct = cast(Structure, struct[sub_package])
      struct[opts["package"]] = pkg_struct
  
      return base_struct, opts
  
  
  def move_old_package(struct: Structure, opts: ScaffoldOpts) -> ActionParams:
      """Move old package that may be eventually created without namespace
  
      Args:
          struct (dict): directory structure as dictionary of dictionaries
          opts (dict): options of the project
  
      Returns:
          tuple(dict, dict):
              directory structure as dictionary of dictionaries and input options
      """
      project_path = Path(opts.get("project_path", "."))
      with chdir(project_path, log=True, **opts):
          old_path = Path("src", opts["package"])
          namespace_path = opts["qual_pkg"].replace(".", os.sep)
          target = Path("src", namespace_path)
  
          old_exists = opts["pretend"] or old_path.is_dir()
          #  ^  When pretending, pretend also an old folder exists
          #     to show a worst case scenario log to the user...
  
          if old_exists and opts["qual_pkg"] != opts["package"]:
              if not opts["pretend"]:
                  logger.warning(
                      "\nA folder %r exists in the project directory, and it "
                      "is likely to have been generated by a PyScaffold "
                      "extension or manually by one of the current project "
                      "authors.\n"
                      "Moving it to %r, since a namespace option was passed.\n"
                      "Please make sure to edit all the files that depend on  "
                      "this package to ensure the correct location.\n",
                      opts["package"],
                      namespace_path,
                  )
  
              move(old_path, target=target, log=True, pretend=opts["pretend"])
  
      return struct, opts
  