Skip to content

Commit

Permalink
feat(commands): added a --json option to info and run command
Browse files Browse the repository at this point in the history
  • Loading branch information
noirbizarre committed Apr 26, 2023
1 parent 0cf6578 commit a311faa
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 4 deletions.
1 change: 1 addition & 0 deletions news/1854.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added a `--json` flag to both `run` and `info` command allowing to dump scripts and infos as JSON.
18 changes: 18 additions & 0 deletions src/pdm/cli/commands/info.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import argparse
import json

from rich import print_json

from pdm.cli.commands.base import BaseCommand
from pdm.cli.options import ArgumentGroup, venv_option
from pdm.cli.utils import check_project_file
Expand All @@ -22,6 +24,7 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None:
)
group.add_argument("--packages", action="store_true", help="Show the local packages root")
group.add_argument("--env", action="store_true", help="Show PEP 508 environment markers")
group.add_argument("--json", action="store_true", help="Dump the information in JSON")
group.add_to_parser(parser)

def handle(self, project: Project, options: argparse.Namespace) -> None:
Expand All @@ -38,6 +41,21 @@ def handle(self, project: Project, options: argparse.Namespace) -> None:
project.core.ui.echo(str(packages_path))
elif options.env:
project.core.ui.echo(json.dumps(project.environment.marker_environment, indent=2))
elif options.json:
print_json(
data={
"pdm": {"version": project.core.version},
"python": {
"interpreter": str(interpreter.executable),
"version": interpreter.identifier,
"pypackages": str(packages_path),
"markers": project.environment.marker_environment,
},
"project": {
"root": str(project.root),
},
}
)
else:
for name, value in zip(
[
Expand Down
41 changes: 37 additions & 4 deletions src/pdm/cli/commands/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from types import FrameType
from typing import Any, Callable, Iterator, Mapping, NamedTuple, Sequence, cast

from rich import print_json

from pdm import termui
from pdm.cli.commands.base import BaseCommand
from pdm.cli.hooks import HookManager
Expand Down Expand Up @@ -300,6 +302,27 @@ def show_list(self) -> None:
)
self.project.core.ui.display_columns(result, columns)

def as_json(self) -> dict[str, Any]:
out = {}
for name in sorted(self.project.scripts):
if name == "_":
data = out["_"] = dict(name="_", kind="shared", help="Shared options", **self.global_options)
if isinstance(data.get("env_file"), dict):
data["env_file.override"] = data.pop("env_file").get("override") # type: ignore[attr-defined]
continue
task = self.get_task(name)
assert task is not None
data = out[name] = {
"name": name,
"kind": task.kind,
"help": task.short_description,
"args": task.args,
}
data.update(**task.options)
if isinstance(data.get("env_file"), dict):
data["env_file.override"] = data.pop("env_file").get("override") # type: ignore[attr-defined]
return out


class Command(BaseCommand):
"""Run commands or scripts with local packages loaded"""
Expand All @@ -308,20 +331,28 @@ class Command(BaseCommand):
arguments = [*BaseCommand.arguments, skip_option, venv_option]

def add_arguments(self, parser: argparse.ArgumentParser) -> None:
parser.add_argument(
action = parser.add_mutually_exclusive_group()
action.add_argument(
"-l",
"--list",
action="store_true",
help="Show all available scripts defined in pyproject.toml",
)
parser.add_argument(
action.add_argument(
"-j",
"--json",
action="store_true",
help="Output all scripts infos in JSON",
)
exec = action.add_argument_group("execution", "Execution parameters")
exec.add_argument(
"-s",
"--site-packages",
action="store_true",
help="Load site-packages from the selected interpreter",
)
parser.add_argument("script", nargs="?", help="The command to run")
parser.add_argument(
exec.add_argument("script", nargs="?", help="The command to run")
exec.add_argument(
"args",
nargs=argparse.REMAINDER,
help="Arguments that will be passed to the command",
Expand All @@ -333,6 +364,8 @@ def handle(self, project: Project, options: argparse.Namespace) -> None:
runner = self.runner_cls(project, hooks=hooks)
if options.list:
return runner.show_list()
if options.json:
return print_json(data=runner.as_json())
if options.site_packages:
runner.global_options.update({"site_packages": options.site_packages})
if not options.script:
Expand Down
14 changes: 14 additions & 0 deletions tests/cli/test_others.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
from pathlib import Path

import pytest
Expand Down Expand Up @@ -50,6 +51,19 @@ def test_info_command(project, pdm):
assert result.exit_code == 0


def test_info_command_json(project, pdm):
result = pdm(["info", "--json"], obj=project, strict=True)

data = json.loads(result.outputs)

assert data["pdm"]["version"] == project.core.version
assert data["python"]["version"] == project.environment.interpreter.identifier
assert data["python"]["interpreter"] == str(project.environment.interpreter.executable)
assert isinstance(data["python"]["pypackages"], str)
assert isinstance(data["python"]["markers"], dict)
assert data["project"]["root"] == str(project.root)


def test_info_global_project(pdm, tmp_path):
with cd(tmp_path):
result = pdm(["info", "-g", "--where"])
Expand Down
68 changes: 68 additions & 0 deletions tests/cli/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,74 @@ def test_run_show_list_of_scripts(project, invoke):
assert result_lines[4][1:-1].strip() == "test_shell │ shell │ shell command"


def test_run_json_list_of_scripts(project, invoke):
project.pyproject.settings["scripts"] = {
"_": {"env_file": ".env"},
"test_composite": {"composite": ["test_cmd", "test_script", "test_shell"]},
"test_cmd": "flask db upgrade",
"test_multi": """\
I am a multilines
command
""",
"test_script": {"call": "test_script:main", "help": "call a python function"},
"test_shell": {"shell": "echo $FOO", "help": "shell command"},
"test_env": {"cmd": "true", "env": {"TEST": "value"}},
"test_env_file": {"cmd": "true", "env_file": ".env"},
"test_override": {"cmd": "true", "env_file": {"override": ".env"}},
"test_site_packages": {"cmd": "true", "site_packages": True},
"_private": "true",
}
project.pyproject.write()
result = invoke(["run", "--json"], obj=project, strict=True)

sep = termui.Emoji.ARROW_SEPARATOR
assert json.loads(result.outputs) == {
"_": {"name": "_", "help": "Shared options", "kind": "shared", "env_file": ".env"},
"test_cmd": {"name": "test_cmd", "help": "flask db upgrade", "kind": "cmd", "args": "flask db upgrade"},
"test_composite": {
"name": "test_composite",
"help": f"test_cmd {sep} test_script {sep} test_shell",
"kind": "composite",
"args": ["test_cmd", "test_script", "test_shell"],
},
"test_multi": {
"name": "test_multi",
"help": f"I am a multilines{termui.Emoji.ELLIPSIS}",
"kind": "cmd",
"args": " I am a multilines\n command\n ",
},
"test_script": {
"name": "test_script",
"help": "call a python function",
"kind": "call",
"args": "test_script:main",
},
"test_shell": {"name": "test_shell", "help": "shell command", "kind": "shell", "args": "echo $FOO"},
"test_env": {"name": "test_env", "help": "true", "kind": "cmd", "args": "true", "env": {"TEST": "value"}},
"test_env_file": {"name": "test_env_file", "help": "true", "kind": "cmd", "args": "true", "env_file": ".env"},
"test_override": {
"name": "test_override",
"help": "true",
"kind": "cmd",
"args": "true",
"env_file.override": ".env",
},
"test_site_packages": {
"name": "test_site_packages",
"help": "true",
"kind": "cmd",
"args": "true",
"site_packages": True,
},
"_private": {
"name": "_private",
"help": "true",
"kind": "cmd",
"args": "true",
},
}


@pytest.mark.usefixtures("local_finder")
@pytest.mark.parametrize("explicit_python", [True, False])
def test_run_with_another_project_root(project, invoke, capfd, explicit_python):
Expand Down

0 comments on commit a311faa

Please sign in to comment.