text: # -*- coding: utf-8 -*-
  from __future__ import absolute_import, print_function
  
  import copy
  import itertools
  import os
  from json import JSONDecodeError
  from pathlib import Path
  
  import attr
  import plette.lockfiles
  
  from ..exceptions import LockfileCorruptException, MissingParameter, PipfileNotFound
  from ..utils import is_editable, is_vcs, merge_items
  from .project import ProjectFile
  from .requirements import Requirement
  from .utils import optional_instance_of
  
  DEFAULT_NEWLINES = "\n"
  
  
  def preferred_newlines(f):
      if isinstance(f.newlines, str):
          return f.newlines
      return DEFAULT_NEWLINES
  
  
  is_lockfile = optional_instance_of(plette.lockfiles.Lockfile)
  is_projectfile = optional_instance_of(ProjectFile)
  
  
  @attr.s(slots=True)
  class Lockfile(object):
      path = attr.ib(validator=optional_instance_of(Path), type=Path)
      _requirements = attr.ib(default=attr.Factory(list), type=list)
      _dev_requirements = attr.ib(default=attr.Factory(list), type=list)
      projectfile = attr.ib(validator=is_projectfile, type=ProjectFile)
      _lockfile = attr.ib(validator=is_lockfile, type=plette.lockfiles.Lockfile)
      newlines = attr.ib(default=DEFAULT_NEWLINES, type=str)
  
      @path.default
      def _get_path(self):
          return Path(os.curdir).joinpath("Pipfile.lock").absolute()
  
      @projectfile.default
      def _get_projectfile(self):
          return self.load_projectfile(self.path)
  
      @_lockfile.default
      def _get_lockfile(self):
          return self.projectfile.model
  
      @property
      def lockfile(self):
          return self._lockfile
  
      @property
      def section_keys(self):
          return ["default", "develop"]
  
      @property
      def extended_keys(self):
          return [k for k in itertools.product(self.section_keys, ["", "vcs", "editable"])]
  
      def get(self, k):
          return self.__getitem__(k)
  
      def __contains__(self, k):
          check_lockfile = k in self.extended_keys or self.lockfile.__contains__(k)
          if check_lockfile:
              return True
          return super(Lockfile, self).__contains__(k)
  
      def __setitem__(self, k, v):
          lockfile = self._lockfile
          lockfile.__setitem__(k, v)
  
      def __getitem__(self, k, *args, **kwargs):
          retval = None
          lockfile = self._lockfile
          section = None
          pkg_type = None
          try:
              retval = lockfile[k]
          except KeyError:
              if "-" in k:
                  section, _, pkg_type = k.rpartition("-")
                  vals = getattr(lockfile.get(section, {}), "_data", {})
                  if pkg_type == "vcs":
                      retval = {k: v for k, v in vals.items() if is_vcs(v)}
                  elif pkg_type == "editable":
                      retval = {k: v for k, v in vals.items() if is_editable(v)}
              if retval is None:
                  raise
          else:
              retval = getattr(retval, "_data", retval)
          return retval
  
      def __getattr__(self, k, *args, **kwargs):
          retval = None
          lockfile = super(Lockfile, self).__getattribute__("_lockfile")
          try:
              return super(Lockfile, self).__getattribute__(k)
          except AttributeError:
              retval = getattr(lockfile, k, None)
          if retval is not None:
              return retval
          return super(Lockfile, self).__getattribute__(k, *args, **kwargs)
  
      def get_deps(self, dev=False, only=True):
          deps = {}
          if dev:
              deps.update(self.develop._data)
              if only:
                  return deps
          deps = merge_items([deps, self.default._data])
          return deps
  
      @classmethod
      def read_projectfile(cls, path):
          """Read the specified project file and provide an interface for
          writing/updating.
  
          :param str path: Path to the target file.
          :return: A project file with the model and location for interaction
          :rtype: :class:`~requirementslib.models.project.ProjectFile`
          """
  
          pf = ProjectFile.read(path, plette.lockfiles.Lockfile, invalid_ok=True)
          return pf
  
      @classmethod
      def lockfile_from_pipfile(cls, pipfile_path):
          from .pipfile import Pipfile
  
          if os.path.isfile(pipfile_path):
              if not os.path.isabs(pipfile_path):
                  pipfile_path = os.path.abspath(pipfile_path)
              pipfile = Pipfile.load(os.path.dirname(pipfile_path))
              return plette.lockfiles.Lockfile.with_meta_from(pipfile._pipfile)
          raise PipfileNotFound(pipfile_path)
  
      @classmethod
      def load_projectfile(cls, path, create=True, data=None):
          """Given a path, load or create the necessary lockfile.
  
          :param str path: Path to the project root or lockfile
          :param bool create: Whether to create the lockfile if not found, defaults to True
          :raises OSError: Thrown if the project root directory doesn't exist
          :raises FileNotFoundError: Thrown if the lockfile doesn't exist and ``create=False``
          :return: A project file instance for the supplied project
          :rtype: :class:`~requirementslib.models.project.ProjectFile`
          """
  
          if not path:
              path = os.curdir
          path = Path(path).absolute()
          project_path = path if path.is_dir() else path.parent
          lockfile_path = path if path.is_file() else project_path / "Pipfile.lock"
          if not project_path.exists():
              raise OSError("Project does not exist: %s" % project_path.as_posix())
          elif not lockfile_path.exists() and not create:
              raise FileNotFoundError(
                  "Lockfile does not exist: %s" % lockfile_path.as_posix()
              )
          projectfile = cls.read_projectfile(lockfile_path.as_posix())
          if not lockfile_path.exists():
              if not data:
                  path_str = lockfile_path.as_posix()
                  if path_str[-5:] == ".lock":
                      pipfile = Path(path_str[:-5])
                  else:
                      pipfile = project_path.joinpath("Pipfile")
                  lf = cls.lockfile_from_pipfile(pipfile)
              else:
                  lf = plette.lockfiles.Lockfile(data)
              projectfile.model = lf
          return projectfile
  
      @classmethod
      def from_data(cls, path, data, meta_from_project=True):
          """Create a new lockfile instance from a dictionary.
  
          :param str path: Path to the project root.
          :param dict data: Data to load into the lockfile.
          :param bool meta_from_project: Attempt to populate the meta section from the
              project root, default True.
          """
  
          if path is None:
              raise MissingParameter("path")
          if data is None:
              raise MissingParameter("data")
          if not isinstance(data, dict):
              raise TypeError("Expecting a dictionary for parameter 'data'")
          path = os.path.abspath(str(path))
          if os.path.isdir(path):
              project_path = path
          elif not os.path.isdir(path) and os.path.isdir(os.path.dirname(path)):
              project_path = os.path.dirname(path)
          pipfile_path = os.path.join(project_path, "Pipfile")
          lockfile_path = os.path.join(project_path, "Pipfile.lock")
          if meta_from_project:
              lockfile = cls.lockfile_from_pipfile(pipfile_path)
              lockfile.update(data)
          else:
              lockfile = plette.lockfiles.Lockfile(data)
          projectfile = ProjectFile(
              line_ending=DEFAULT_NEWLINES, location=lockfile_path, model=lockfile
          )
          return cls(
              projectfile=projectfile,
              lockfile=lockfile,
              newlines=projectfile.line_ending,
              path=Path(projectfile.location),
          )
  
      @classmethod
      def load(cls, path, create=True):
          """Create a new lockfile instance.
  
          :param project_path: Path to  project root or lockfile
          :type project_path: str or :class:`pathlib.Path`
          :param str lockfile_name: Name of the lockfile in the project root directory
          :param pipfile_path: Path to the project pipfile
          :type pipfile_path: :class:`pathlib.Path`
          :returns: A new lockfile representing the supplied project paths
          :rtype: :class:`~requirementslib.models.lockfile.Lockfile`
          """
  
          try:
              projectfile = cls.load_projectfile(path, create=create)
          except JSONDecodeError:
              path = os.path.abspath(path)
              path = Path(
                  os.path.join(path, "Pipfile.lock") if os.path.isdir(path) else path
              )
              formatted_path = path.as_posix()
              backup_path = "%s.bak" % formatted_path
              LockfileCorruptException.show(formatted_path, backup_path=backup_path)
              path.rename(backup_path)
              cls.load(formatted_path, create=True)
          lockfile_path = Path(projectfile.location)
          creation_args = {
              "projectfile": projectfile,
              "lockfile": projectfile.model,
              "newlines": projectfile.line_ending,
              "path": lockfile_path,
          }
          return cls(**creation_args)
  
      @classmethod
      def create(cls, path, create=True):
          return cls.load(path, create=create)
  
      @property
      def develop(self):
          return self._lockfile.develop
  
      @property
      def default(self):
          return self._lockfile.default
  
      def get_requirements(self, dev=True, only=False):
          """Produces a generator which generates requirements from the desired
          section.
  
          :param bool dev: Indicates whether to use dev requirements, defaults to False
          :return: Requirements from the relevant the relevant pipfile
          :rtype: :class:`~requirementslib.models.requirements.Requirement`
          """
  
          deps = self.get_deps(dev=dev, only=only)
          for k, v in deps.items():
              yield Requirement.from_pipfile(k, v)
  
      @property
      def dev_requirements(self):
          if not self._dev_requirements:
              self._dev_requirements = list(self.get_requirements(dev=True, only=True))
          return self._dev_requirements
  
      @property
      def requirements(self):
          if not self._requirements:
              self._requirements = list(self.get_requirements(dev=False, only=True))
          return self._requirements
  
      @property
      def dev_requirements_list(self):
          return [{name: entry._data} for name, entry in self._lockfile.develop.items()]
  
      @property
      def requirements_list(self):
          return [{name: entry._data} for name, entry in self._lockfile.default.items()]
  
      def write(self):
          self.projectfile.model = copy.deepcopy(self._lockfile)
          self.projectfile.write()
  
      def as_requirements(self, include_hashes=False, dev=False):
          """Returns a list of requirements in pip-style format."""
          lines = []
          section = self.dev_requirements if dev else self.requirements
          for req in section:
              kwargs = {"include_hashes": include_hashes}
              if req.editable:
                  kwargs["include_markers"] = False
              r = req.as_line(**kwargs)
              lines.append(r.strip())
          return lines
  