text: #
  # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
  # SPDX-License-Identifier: Apache-2.0
  #
  """Mbed Program abstraction layer."""
  import logging
  
  from pathlib import Path
  from typing import Dict
  from urllib.parse import urlparse
  
  from mbed_tools.project.exceptions import ProgramNotFound, ExistingProgram, MbedOSNotFound
  from mbed_tools.project._internal.project_data import (
      MbedProgramFiles,
      MbedOS,
      MBED_OS_REFERENCE_FILE_NAME,
      MBED_OS_DIR_NAME,
  )
  
  logger = logging.getLogger(__name__)
  
  
  class MbedProgram:
      """Represents an Mbed program.
  
      An `MbedProgram` consists of:
          * A copy of, or reference to, `MbedOS`
          * A set of `MbedProgramFiles`
          * A collection of references to external libraries, defined in .lib files located in the program source tree
      """
  
      def __init__(self, program_files: MbedProgramFiles, mbed_os: MbedOS) -> None:
          """Initialise the program attributes.
  
          Args:
              program_files: Object holding paths to a set of files that define an Mbed program.
              mbed_os: An instance of `MbedOS` holding paths to locations in the local copy of the Mbed OS source.
          """
          self.files = program_files
          self.root = self.files.mbed_os_ref.parent
          self.mbed_os = mbed_os
  
      @classmethod
      def from_new(cls, dir_path: Path) -> "MbedProgram":
          """Create an MbedProgram from an empty directory.
  
          Creates the directory if it doesn't exist.
  
          Args:
              dir_path: Directory in which to create the program.
  
          Raises:
              ExistingProgram: An existing program was found in the path.
          """
          if _tree_contains_program(dir_path):
              raise ExistingProgram(
                  f"An existing Mbed program was found in the directory tree {dir_path}. It is not possible to nest Mbed "
                  "programs. Please ensure there is no mbed-os.lib file in the cwd hierarchy."
              )
  
          logger.info(f"Creating Mbed program at path '{dir_path.resolve()}'")
          dir_path.mkdir(exist_ok=True)
          program_files = MbedProgramFiles.from_new(dir_path)
          logger.info(f"Creating git repository for the Mbed program '{dir_path}'")
          mbed_os = MbedOS.from_new(dir_path / MBED_OS_DIR_NAME)
          return cls(program_files, mbed_os)
  
      @classmethod
      def from_existing(
          cls, dir_path: Path, build_subdir: Path, mbed_os_path: Path = None, check_mbed_os: bool = True,
      ) -> "MbedProgram":
          """Create an MbedProgram from an existing program directory.
  
          Args:
              dir_path: Directory containing an Mbed program.
              build_subdir: The subdirectory for the CMake build tree.
              mbed_os_path: Directory containing Mbed OS.
              check_mbed_os: If True causes an exception to be raised if the Mbed OS source directory does not
                             exist.
  
          Raises:
              ProgramNotFound: An existing program was not found in the path.
          """
          if mbed_os_path is None:
              program_root = _find_program_root(dir_path)
              if program_root is None:
                  program_root = Path('.')
              mbed_os_path = program_root / MBED_OS_DIR_NAME
          else:
              program_root = dir_path
  
          logger.info(f"Found existing Mbed program at path '{program_root}'")
          program = MbedProgramFiles.from_existing(program_root, build_subdir)
  
          try:
              mbed_os = MbedOS.from_existing(mbed_os_path, check_mbed_os)
          except ValueError as mbed_os_err:
              raise MbedOSNotFound(
                  f"Mbed OS was not found due to the following error: {mbed_os_err}"
                  "\nYou may need to resolve the mbed-os.lib reference. You can do this by performing a `deploy`."
              )
  
          return cls(program, mbed_os)
  
  
  def parse_url(name_or_url: str) -> Dict[str, str]:
      """Create a valid github/armmbed url from a program name.
  
      Args:
          url: The URL, or a program name to turn into an URL.
  
      Returns:
          Dictionary containing the remote url and the destination path for the clone.
      """
      url_obj = urlparse(name_or_url)
      if url_obj.hostname:
          url = url_obj.geturl()
      elif ":" in name_or_url.split("/", maxsplit=1)[0]:
          # If non-standard and no slashes before first colon, git will recognize as scp ssh syntax
          url = name_or_url
      else:
          url = f"https://github.com/armmbed/{url_obj.path}"
      # We need to create a valid directory name from the url path section.
      return {"url": url, "dst_path": url_obj.path.rsplit("/", maxsplit=1)[-1].replace("/", "")}
  
  
  def _tree_contains_program(path: Path) -> bool:
      """Check if the current path or its ancestors contain an mbed-os.lib file.
  
      Args:
          path: The starting path for the search. The search walks up the tree from this path.
  
      Returns:
          `True` if an mbed-os.lib file is located between `path` and filesystem root.
          `False` if no mbed-os.lib file was found.
      """
      try:
          _find_program_root(path)
          return True
      except ProgramNotFound:
          return False
  
  
  def _find_program_root(cwd: Path) -> Path:
      """Walk up the directory tree, looking for an mbed-os.lib file.
  
      Programs contain an mbed-os.lib file at the root of the source tree.
  
      Args:
          cwd: The directory path to search for a program.
  
      Raises:
          ProgramNotFound: No mbed-os.lib file found in the path.
  
      Returns:
          Path containing the mbed-os.lib file.
      """
      potential_root = cwd.absolute().resolve()
      while str(potential_root) != str(potential_root.anchor):
          logger.debug(f"Searching for mbed-os.lib file at path {potential_root}")
          root_file = potential_root / MBED_OS_REFERENCE_FILE_NAME
          if root_file.exists() and root_file.is_file():
              logger.debug(f"mbed-os.lib file found at {potential_root}")
              return potential_root
  
          potential_root = potential_root.parent
  
      logger.debug("No mbed-os.lib file found.")
      return None
  