Example #1
0
    def _configure_io(self, io: IO) -> None:
        # We need to check if the command being run
        # is the "run" command.
        definition = self.definition
        with suppress(CleoException):
            io.input.bind(definition)

        name = io.input.first_argument
        if name == "run":
            from poetry.console.io.inputs.run_argv_input import RunArgvInput

            input = cast(ArgvInput, io.input)
            run_input = RunArgvInput([self._name or ""] + input._tokens)
            # For the run command reset the definition
            # with only the set options (i.e. the options given before the command)
            for option_name, value in input.options.items():
                if value:
                    option = definition.option(option_name)
                    run_input.add_parameter_option("--" + option.name)
                    if option.shortcut:
                        shortcuts = re.split(r"\|-?", option.shortcut.lstrip("-"))
                        shortcuts = [s for s in shortcuts if s]
                        for shortcut in shortcuts:
                            run_input.add_parameter_option("-" + shortcut.lstrip("-"))

            with suppress(CleoException):
                run_input.bind(definition)

            for option_name, value in input.options.items():
                if value:
                    run_input.set_option(option_name, value)

            io.set_input(run_input)

        super()._configure_io(io)
Example #2
0
    def display_package_tree(self, io: IO, package: Package,
                             installed_repo: Repository) -> None:
        io.write(f"<c1>{package.pretty_name}</c1>")
        description = ""
        if package.description:
            description = " " + package.description

        io.write_line(f" <b>{package.pretty_version}</b>{description}")

        dependencies = package.requires
        dependencies = sorted(dependencies, key=lambda x: x.name)
        tree_bar = "├"
        total = len(dependencies)
        for i, dependency in enumerate(dependencies, 1):
            if i == total:
                tree_bar = "└"

            level = 1
            color = self.colors[level]
            info = (f"{tree_bar}── <{color}>{dependency.name}</{color}>"
                    f" {dependency.pretty_constraint}")
            self._write_tree_line(io, info)

            tree_bar = tree_bar.replace("└", " ")
            packages_in_tree = [package.name, dependency.name]

            self._display_tree(io, dependency, installed_repo,
                               packages_in_tree, tree_bar, level + 1)
Example #3
0
    def _write_error(self, io: IO, error: Exception) -> None:
        """
        Outputs an error message.
        """
        message = "<error>{}</error>".format(str(error))

        io.write_error_line(message)
Example #4
0
    def _write_prompt(self, io: IO) -> None:
        """
        Outputs the question prompt.
        """
        message = self._question

        io.write_error("<question>{}</question> ".format(message))
Example #5
0
    def _download(self, destination: pathlib.Path,
                  io: cleo_io.IO) -> pathlib.Path:
        req = requests.get(self.url, stream=True)
        length = int(req.headers.get("content-length", 0))

        progress = progress_bar.ProgressBar(io, max=length)
        io.write_line(f"Downloading <info>{self.url}</>")
        if req.status_code < 200 or req.status_code >= 300:
            raise RuntimeError(f"download failed: {req.status_code}")

        progress.start(length)

        try:
            with open(destination, "wb") as f:
                for chunk in req.iter_content(chunk_size=4096):
                    if chunk:
                        progress.advance(len(chunk))
                        f.write(chunk)
        except BaseException:
            if destination.exists():
                destination.unlink()
        finally:
            progress.finish()
            io.write_line("")

        try:
            self.verify(destination)
        except Exception:
            destination.unlink()
            raise

        return destination
Example #6
0
    def _write_tree_line(self, io: IO, line: str) -> None:
        if not io.output.supports_utf8():
            line = line.replace("└", "`-")
            line = line.replace("├", "|-")
            line = line.replace("──", "-")
            line = line.replace("│", "|")

        io.write_line(line)
    def _write_prompt(self, io: IO) -> None:
        message = self._question

        message = "<question>{} (yes/no)</> [<comment>{}</>] ".format(
            message, "yes" if self._default else "no"
        )

        io.write_error(message)
Example #8
0
    def handle(self) -> int:
        from pathlib import Path

        from cleo.io.inputs.string_input import StringInput
        from cleo.io.io import IO

        from poetry.factory import Factory
        from poetry.utils.env import EnvManager

        plugins = self.argument("plugins")

        system_env = EnvManager.get_system_env(naive=True)
        env_dir = Path(os.getenv("POETRY_HOME") or system_env.path)

        # From this point forward, all the logic will be deferred to
        # the remove command, by using the global `pyproject.toml` file.
        application = cast(Application, self.application)
        remove_command: RemoveCommand = cast(RemoveCommand,
                                             application.find("remove"))
        # We won't go through the event dispatching done by the application
        # so we need to configure the command manually
        remove_command.set_poetry(Factory().create_poetry(env_dir))
        remove_command.set_env(system_env)
        application._configure_installer(remove_command, self._io)

        argv = ["remove"] + plugins
        if self.option("dry-run"):
            argv.append("--dry-run")

        return remove_command.run(
            IO(
                StringInput(" ".join(argv)),
                self._io.output,
                self._io.error_output,
            ))
Example #9
0
    def run(self, io: IO) -> int:
        self.merge_application_definition()

        try:
            io.input.bind(self.definition)
        except CleoException:
            if not self._ignore_validation_errors:
                raise

        self.initialize(io)

        if io.is_interactive():
            self.interact(io)

        if io.input.has_argument(
                "command") and io.input.argument("command") is None:
            io.input.set_argument("command", self.name)

        io.input.validate()

        status_code = self.execute(io)

        if status_code is None:
            status_code = 0

        return status_code
Example #10
0
    def download(self, io: cleo_io.IO) -> pathlib.Path:
        destination_dir = cache.cachedir() / "distfiles"
        if not destination_dir.exists():
            destination_dir.mkdir()

        destination = destination_dir / self.name
        if destination.exists():
            try:
                self.verify(destination)
            except Exception:
                io.write_line(
                    f"<warning>Cached {self.name} exists, but does pass "
                    f"verification.  Downloading anew.")
            else:
                return destination

        return self._download(destination, io)
Example #11
0
    def _read_from_input(self, io: IO) -> str:
        """
        Read user input.
        """
        ret = io.read_line(4096)

        if not ret:
            raise RuntimeError("Aborted")

        return ret.strip()
Example #12
0
    def deactivate(self, io: IO) -> None:
        venv_path = self._poetry.config.get("virtualenvs.path")
        if venv_path is None:
            venv_path = Path(CACHE_DIR) / "virtualenvs"
        else:
            venv_path = Path(venv_path)

        name = self._poetry.package.name
        name = self.generate_env_name(name, str(self._poetry.file.parent))

        envs_file = TOMLFile(venv_path / self.ENVS_FILE)
        if envs_file.exists():
            envs = envs_file.read()
            env = envs.get(name)
            if env is not None:
                io.write_line(
                    "Deactivating virtualenv: <comment>{}</comment>".format(
                        venv_path / (name + "-py{}".format(env["minor"]))))
                del envs[name]

                envs_file.write(envs)
Example #13
0
    def configure_sources(cls, poetry: Poetry, sources: list[dict[str, str]],
                          config: Config, io: IO) -> None:
        for source in sources:
            repository = cls.create_legacy_repository(source, config)
            is_default = bool(source.get("default", False))
            is_secondary = bool(source.get("secondary", False))
            if io.is_debug():
                message = f"Adding repository {repository.name} ({repository.url})"
                if is_default:
                    message += " and setting it as the default one"
                elif is_secondary:
                    message += " and setting it as secondary"

                io.write_line(message)

            poetry.pool.add_repository(repository,
                                       is_default,
                                       secondary=is_secondary)

        # Put PyPI last to prefer private repositories
        # unless we have no default source AND no primary sources
        # (default = false, secondary = false)
        if poetry.pool.has_default():
            if io.is_debug():
                io.write_line("Deactivating the PyPI repository")
        else:
            from poetry.repositories.pypi_repository import PyPiRepository

            default = not poetry.pool.has_primary_repositories()
            poetry.pool.add_repository(PyPiRepository(), default, not default)
Example #14
0
    def _write_prompt(self, io: IO) -> None:
        """
        Outputs the question prompt.
        """
        message = self._question
        default = self._default

        if default is None:
            message = "<question>{}</question>: ".format(message)
        elif self._multi_select:
            choices = self._choices
            default = default.split(",")

            for i, value in enumerate(default):
                default[i] = choices[int(value.strip())]

            message = "<question>{}</question> [<comment>{}</comment>]:".format(
                message, ", ".join(default))
        else:
            choices = self._choices
            message = "<question>{}</question> [<comment>{}</comment>]:".format(
                message, choices[int(default)])

        if len(self._choices) > 1:
            width = max(
                *map(len, [str(k) for k, _ in enumerate(self._choices)]))
        else:
            width = 1

        messages = [message]
        for key, value in enumerate(self._choices):
            messages.append(" [<comment>{:{}}</>] {}".format(
                key, width, value))

        io.write_error_line("\n".join(messages))

        message = self._prompt

        io.write_error(message)
Example #15
0
    def handle(self) -> int:
        self.line_error(self.help)

        application = cast(Application, self.application)
        command: SelfShowPluginsCommand = cast(
            SelfShowPluginsCommand, application.find("self show plugins"))

        exit_code: int = command.run(
            IO(
                StringInput(""),
                self.io.output,
                self.io.error_output,
            ))
        return exit_code
Example #16
0
    def ask(self, io: IO) -> str:
        """
        Asks the question to the user.
        """
        if not io.is_interactive():
            return self.default

        if not self._validator:
            return self._do_ask(io)

        def interviewer():
            return self._do_ask(io)

        return self._validate_attempts(interviewer, io)
Example #17
0
    def handle(self) -> int:
        self.line_error(self.deprecation)

        application = self.get_application()
        command: SelfAddCommand = application.find("self add")
        application.configure_installer_for_command(command, self.io)

        argv: list[str] = ["add", *self.argument("plugins")]

        if self.option("--dry-run"):
            argv.append("--dry-run")

        exit_code: int = command.run(
            IO(
                StringInput(" ".join(argv)),
                self.io.output,
                self.io.error_output,
            ))
        return exit_code
Example #18
0
    def handle(self) -> int:
        self.line_error(self.help)

        application = cast(Application, self.application)
        command: SelfRemoveCommand = cast(SelfRemoveCommand,
                                          application.find("self remove"))
        application.configure_installer_for_command(command, self.io)

        argv: list[str] = ["remove", *self.argument("plugins")]

        if self.option("--dry-run"):
            argv.append("--dry-run")

        exit_code: int = command.run(
            IO(
                StringInput(" ".join(argv)),
                self.io.output,
                self.io.error_output,
            ))
        return exit_code
Example #19
0
    def _system_project_handle(self) -> int:
        self.write("<info>Updating Poetry version ...</info>\n\n")
        application = self.get_application()
        add_command: AddCommand = application.find("add")
        add_command.set_env(self.env)
        application.configure_installer_for_command(add_command, self.io)

        argv = ["add", f"poetry@{self.argument('version')}"]

        if self.option("dry-run"):
            argv.append("--dry-run")

        if self.option("preview"):
            argv.append("--allow-prereleases")

        exit_code: int = add_command.run(
            IO(
                StringInput(" ".join(argv)),
                self.io.output,
                self.io.error_output,
            ))
        return exit_code
Example #20
0
    def create_venv(
        self,
        io: IO,
        name: Optional[str] = None,
        executable: Optional[str] = None,
        force: bool = False,
    ) -> Union["SystemEnv", "VirtualEnv"]:
        if self._env is not None and not force:
            return self._env

        cwd = self._poetry.file.parent
        env = self.get(reload=True)

        if not env.is_sane():
            force = True

        if env.is_venv() and not force:
            # Already inside a virtualenv.
            return env

        create_venv = self._poetry.config.get("virtualenvs.create")
        root_venv = self._poetry.config.get("virtualenvs.in-project")

        venv_path = self._poetry.config.get("virtualenvs.path")
        if root_venv:
            venv_path = cwd / ".venv"
        elif venv_path is None:
            venv_path = Path(CACHE_DIR) / "virtualenvs"
        else:
            venv_path = Path(venv_path)

        if not name:
            name = self._poetry.package.name

        python_patch = ".".join([str(v) for v in sys.version_info[:3]])
        python_minor = ".".join([str(v) for v in sys.version_info[:2]])
        if executable:
            python_patch = decode(
                subprocess.check_output(
                    list_to_shell_command([
                        executable,
                        "-c",
                        "\"import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))\"",
                    ]),
                    shell=True,
                ).strip())
            python_minor = ".".join(python_patch.split(".")[:2])

        supported_python = self._poetry.package.python_constraint
        if not supported_python.allows(Version.parse(python_patch)):
            # The currently activated or chosen Python version
            # is not compatible with the Python constraint specified
            # for the project.
            # If an executable has been specified, we stop there
            # and notify the user of the incompatibility.
            # Otherwise, we try to find a compatible Python version.
            if executable:
                raise NoCompatiblePythonVersionFound(
                    self._poetry.package.python_versions, python_patch)

            io.write_line(
                "<warning>The currently activated Python version {} "
                "is not supported by the project ({}).\n"
                "Trying to find and use a compatible version.</warning> ".
                format(python_patch, self._poetry.package.python_versions))

            for python_to_try in reversed(
                    sorted(
                        self._poetry.package.AVAILABLE_PYTHONS,
                        key=lambda v: (v.startswith("3"), -len(v), v),
                    )):
                if len(python_to_try) == 1:
                    if not parse_constraint("^{}.0".format(
                            python_to_try)).allows_any(supported_python):
                        continue
                elif not supported_python.allows_all(
                        parse_constraint(python_to_try + ".*")):
                    continue

                python = "python" + python_to_try

                if io.is_debug():
                    io.write_line("<debug>Trying {}</debug>".format(python))

                try:
                    python_patch = decode(
                        subprocess.check_output(
                            list_to_shell_command([
                                python,
                                "-c",
                                "\"import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))\"",
                            ]),
                            stderr=subprocess.STDOUT,
                            shell=True,
                        ).strip())
                except CalledProcessError:
                    continue

                if not python_patch:
                    continue

                if supported_python.allows(Version.parse(python_patch)):
                    io.write_line("Using <c1>{}</c1> ({})".format(
                        python, python_patch))
                    executable = python
                    python_minor = ".".join(python_patch.split(".")[:2])
                    break

            if not executable:
                raise NoCompatiblePythonVersionFound(
                    self._poetry.package.python_versions)

        if root_venv:
            venv = venv_path
        else:
            name = self.generate_env_name(name, str(cwd))
            name = "{}-py{}".format(name, python_minor.strip())
            venv = venv_path / name

        if not venv.exists():
            if create_venv is False:
                io.write_line("<fg=black;bg=yellow>"
                              "Skipping virtualenv creation, "
                              "as specified in config file."
                              "</>")

                return SystemEnv(Path(sys.prefix))

            io.write_line("Creating virtualenv <c1>{}</> in {}".format(
                name, str(venv_path)))

            self.build_venv(
                venv,
                executable=executable,
                flags=self._poetry.config.get("virtualenvs.options"),
            )
        else:
            if force:
                if not env.is_sane():
                    io.write_line(
                        "<warning>The virtual environment found in {} seems to be broken.</warning>"
                        .format(env.path))
                io.write_line("Recreating virtualenv <c1>{}</> in {}".format(
                    name, str(venv)))
                self.remove_venv(venv)
                self.build_venv(
                    venv,
                    executable=executable,
                    flags=self._poetry.config.get("virtualenvs.options"),
                )
            elif io.is_very_verbose():
                io.write_line(
                    "Virtualenv <c1>{}</> already exists.".format(name))

        # venv detection:
        # stdlib venv may symlink sys.executable, so we can't use realpath.
        # but others can symlink *to* the venv Python,
        # so we can't just use sys.executable.
        # So we just check every item in the symlink tree (generally <= 3)
        p = os.path.normcase(sys.executable)
        paths = [p]
        while os.path.islink(p):
            p = os.path.normcase(
                os.path.join(os.path.dirname(p), os.readlink(p)))
            paths.append(p)

        p_venv = os.path.normcase(str(venv))
        if any(p.startswith(p_venv) for p in paths):
            # Running properly in the virtualenv, don't need to do anything
            return SystemEnv(Path(sys.prefix), self.get_base_prefix())

        return VirtualEnv(venv)
Example #21
0
    def _autocomplete(self, io: IO) -> str:
        """
        Autocomplete a question.
        """
        autocomplete = self._autocomplete_values

        ret = ""

        i = 0
        ofs = -1
        matches = [x for x in autocomplete]
        num_matches = len(matches)

        stty_mode = subprocess.check_output(["stty",
                                             "-g"]).decode().rstrip("\n")

        # Disable icanon (so we can read each keypress) and echo (we'll do echoing here instead)
        subprocess.check_output(["stty", "-icanon", "-echo"])

        # Add highlighted text style
        style = Style(options=["reverse"])
        io.error_output.formatter.set_style("hl", style)

        # Read a keypress
        while True:
            c = io.read(1)

            # Backspace character
            if c == "\177":
                if num_matches == 0 and i != 0:
                    i -= 1
                    # Move cursor backwards
                    io.write_error("\033[1D")

                if i == 0:
                    ofs = -1
                    matches = [x for x in autocomplete]
                    num_matches = len(matches)
                else:
                    num_matches = 0

                # Pop the last character off the end of our string
                ret = ret[:i]
            # Did we read an escape sequence
            elif c == "\033":
                c += io.read(2)

                # A = Up Arrow. B = Down Arrow
                if c[2] == "A" or c[2] == "B":
                    if c[2] == "A" and ofs == -1:
                        ofs = 0

                    if num_matches == 0:
                        continue

                    ofs += -1 if c[2] == "A" else 1
                    ofs = (num_matches + ofs) % num_matches
            elif ord(c) < 32:
                if c == "\t" or c == "\n":
                    if num_matches > 0 and ofs != -1:
                        ret = matches[ofs]
                        # Echo out remaining chars for current match
                        io.write_error(ret[i:])
                        i = len(ret)

                    if c == "\n":
                        io.write_error(c)
                        break

                    num_matches = 0

                continue
            else:
                io.write_error(c)
                ret += c
                i += 1

                num_matches = 0
                ofs = 0

                for value in autocomplete:
                    # If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
                    if value.startswith(ret) and i != len(value):
                        num_matches += 1
                        matches[num_matches - 1] = value

            # Erase characters from cursor to end of line
            io.write_error("\033[K")

            if num_matches > 0 and ofs != -1:
                # Save cursor position
                io.write_error("\0337")
                # Write highlighted text
                io.write_error("<hl>" + matches[ofs][i:] + "</hl>")
                # Restore cursor position
                io.write_error("\0338")

        subprocess.call(["stty", "{}".format(stty_mode)])

        return ret
Example #22
0
    def handle(self) -> int:
        from pathlib import Path

        import tomlkit

        from cleo.io.inputs.string_input import StringInput
        from cleo.io.io import IO
        from poetry.core.pyproject.toml import PyProjectTOML
        from poetry.core.semver.helpers import parse_constraint

        from poetry.factory import Factory
        from poetry.packages.project_package import ProjectPackage
        from poetry.repositories.installed_repository import InstalledRepository
        from poetry.utils.env import EnvManager

        plugins = self.argument("plugins")

        # Plugins should be installed in the system env to be globally available
        system_env = EnvManager.get_system_env(naive=True)

        env_dir = Path(os.getenv("POETRY_HOME") or system_env.path)

        # We check for the plugins existence first.
        if env_dir.joinpath("pyproject.toml").exists():
            pyproject = tomlkit.loads(
                env_dir.joinpath("pyproject.toml").read_text(encoding="utf-8"))
            poetry_content = pyproject["tool"]["poetry"]
            existing_packages = self.get_existing_packages_from_input(
                plugins, poetry_content, "dependencies")

            if existing_packages:
                self.notify_about_existing_packages(existing_packages)

            plugins = [
                plugin for plugin in plugins if plugin not in existing_packages
            ]

        if not plugins:
            return 0

        plugins = self._determine_requirements(plugins)

        # We retrieve the packages installed in the system environment.
        # We assume that this environment will be a self contained virtual environment
        # built by the official installer or by pipx.
        # If not, it might lead to side effects since other installed packages
        # might not be required by Poetry but still taken into account when resolving dependencies.
        installed_repository = InstalledRepository.load(system_env,
                                                        with_dependencies=True)

        root_package = None
        for package in installed_repository.packages:
            if package.name == "poetry":
                root_package = ProjectPackage(package.name, package.version)
                for dependency in package.requires:
                    root_package.add_dependency(dependency)

                break

        root_package.python_versions = ".".join(
            str(v) for v in system_env.version_info[:3])
        # We create a `pyproject.toml` file based on all the information
        # we have about the current environment.
        if not env_dir.joinpath("pyproject.toml").exists():
            Factory.create_pyproject_from_package(root_package, env_dir)

        # We add the plugins to the dependencies section of the previously
        # created `pyproject.toml` file
        pyproject = PyProjectTOML(env_dir.joinpath("pyproject.toml"))
        poetry_content = pyproject.poetry_config
        poetry_dependency_section = poetry_content["dependencies"]
        plugin_names = []
        for plugin in plugins:
            if "version" in plugin:
                # Validate version constraint
                parse_constraint(plugin["version"])

            constraint = tomlkit.inline_table()
            for name, value in plugin.items():
                if name == "name":
                    continue

                constraint[name] = value

            if len(constraint) == 1 and "version" in constraint:
                constraint = constraint["version"]

            poetry_dependency_section[plugin["name"]] = constraint
            plugin_names.append(plugin["name"])

        pyproject.save()

        # From this point forward, all the logic will be deferred to
        # the update command, by using the previously created `pyproject.toml`
        # file.
        application = cast(Application, self.application)
        update_command: UpdateCommand = cast(UpdateCommand,
                                             application.find("update"))
        # We won't go through the event dispatching done by the application
        # so we need to configure the command manually
        update_command.set_poetry(Factory().create_poetry(env_dir))
        update_command.set_env(system_env)
        application._configure_installer(update_command, self._io)

        argv = ["update"] + plugin_names
        if self.option("dry-run"):
            argv.append("--dry-run")

        return update_command.run(
            IO(
                StringInput(" ".join(argv)),
                self._io.output,
                self._io.error_output,
            ))
Example #23
0
 def interact(self, io: IO) -> None:
     io.write_line("interact called")
Example #24
0
 def activate(self, poetry: Poetry, io: IO) -> None:
     io.write_line("Setting readmes")
     poetry.package.readmes = ("README.md", )
Example #25
0
 def activate(self, poetry: Poetry, io: IO) -> None:
     io.write_line("Updating version")
     poetry.package.set_version("9.9.9")