Skip to content

Commit

Permalink
Add json output to list and list-depends
Browse files Browse the repository at this point in the history
  • Loading branch information
sbidoul committed Aug 15, 2021
1 parent ef51949 commit 78c536f
Show file tree
Hide file tree
Showing 10 changed files with 157 additions and 18 deletions.
8 changes: 6 additions & 2 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ $ manifestoo list [OPTIONS]

**Options**:

* `--separator TEXT`: Separator charater to use (by default, print one item per line).
* `--format [names|json]`: Output format [default: names]
* `--separator TEXT`: Separator character to use for the 'names' format (by
default, print one item per line).
* `--help`: Show this message and exit.

## `manifestoo list-depends`
Expand All @@ -105,7 +107,9 @@ $ manifestoo list-depends [OPTIONS]

**Options**:

* `--separator TEXT`: Separator charater to use (by default, print one item per line).
* `--format [names|json]`: Output format [default: names]
* `--separator TEXT`: Separator character to use for the 'names' format (by
default, print one item per line).
* `--transitive`: Print all transitive dependencies.
* `--include-selected`: Print the selected addons along with their dependencies.
* `--ignore-missing`: Do not fail if dependencies are not found in addons path. This only applies to top level (selected) addons and transitive dependencies.
Expand Down
1 change: 1 addition & 0 deletions news/11.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add rich `json` output format to `list` and `list-depends` commands.
25 changes: 24 additions & 1 deletion src/manifestoo/addon.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
import sys
from pathlib import Path

from .manifest import InvalidManifest, Manifest
from .manifest import InvalidManifest, Manifest, ManifestDict

if sys.version_info >= (3, 8):
from typing import TypedDict

class AddonDict(TypedDict):
manifest: ManifestDict
manifest_path: str
path: str


else:
from typing import Any, Dict

AddonDict = Dict[str, Any]


class AddonNotFound(Exception):
Expand Down Expand Up @@ -52,3 +67,11 @@ def from_addon_dir(
except InvalidManifest as e:
raise AddonNotFoundInvalidManifest(str(e)) from e
return cls(manifest)

def as_dict(self) -> AddonDict:
"""Convert to a dictionary suitable for json output."""
return dict(
manifest=self.manifest.manifest_dict,
manifest_path=str(self.manifest_path),
path=str(self.path),
)
5 changes: 3 additions & 2 deletions src/manifestoo/commands/tree.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Dict, List, Optional, Set
from typing import Dict, List, Optional, Set, cast

import typer

Expand Down Expand Up @@ -57,7 +57,8 @@ def _print(indent: List[str], node: Node) -> None:

def sversion(self, odoo_series: OdooSeries) -> Optional[str]:
if not self.addon:
return typer.style("✘ not installed", fg=typer.colors.RED)
# typer.style (from click, actually) miss a type annotation
return cast(str, typer.style("✘ not installed", fg=typer.colors.RED))
elif is_core_ce_addon(self.addon_name, odoo_series):
return f"{odoo_series}+{OdooEdition.CE}"
elif is_core_ee_addon(self.addon_name, odoo_series):
Expand Down
46 changes: 36 additions & 10 deletions src/manifestoo/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from enum import Enum
from pathlib import Path
from typing import List, Optional

Expand All @@ -13,7 +14,7 @@
from .core_addons import get_core_addons
from .odoo_series import OdooSeries, detect_from_addons_set
from .options import MainOptions
from .utils import ensure_odoo_series, not_implemented, print_list
from .utils import ensure_odoo_series, not_implemented, print_addons_as_json, print_list
from .version import __version__

app = typer.Typer()
Expand Down Expand Up @@ -185,26 +186,48 @@ def callback(
ctx.obj = main_options


class Format(str, Enum):
names = "names"
json = "json"


@app.command()
def list(
ctx: typer.Context,
format: Format = typer.Option(
Format.names,
help="Output format",
),
separator: Optional[str] = typer.Option(
None,
help="Separator charater to use (by default, print one item per line).",
help=(
"Separator character to use for the 'names' format "
"(by default, print one item per line)."
),
),
) -> None:
"""Print the selected addons."""
main_options: MainOptions = ctx.obj
result = list_command(main_options.addons_selection)
print_list(result, separator or main_options.separator or "\n")
names = list_command(main_options.addons_selection)
if format == Format.names:
print_list(names, separator or main_options.separator or "\n")
else:
print_addons_as_json(names, main_options.addons_set)


@app.command()
def list_depends(
ctx: typer.Context,
format: Format = typer.Option(
Format.names,
help="Output format",
),
separator: Optional[str] = typer.Option(
None,
help="Separator charater to use (by default, print one item per line).",
help=(
"Separator character to use for the 'names' format "
"(by default, print one item per line)."
),
),
transitive: bool = typer.Option(
False,
Expand Down Expand Up @@ -238,7 +261,7 @@ def list_depends(
main_options: MainOptions = ctx.obj
if as_pip_requirements:
not_implemented("--as-pip-requirement")
result, missing = list_depends_command(
names, missing = list_depends_command(
main_options.addons_selection,
main_options.addons_set,
transitive,
Expand All @@ -247,10 +270,13 @@ def list_depends(
if missing and not ignore_missing:
echo.error("not found in addons path: " + ",".join(sorted(missing)))
raise typer.Abort()
print_list(
result,
separator or main_options.separator or "\n",
)
if format == Format.names:
print_list(
names,
separator or main_options.separator or "\n",
)
else:
print_addons_as_json(names, main_options.addons_set)


@app.command()
Expand Down
5 changes: 4 additions & 1 deletion src/manifestoo/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,11 @@ class InvalidManifest(Exception):
pass


ManifestDict = Dict[Any, Any]


class Manifest:
def __init__(self, manifest_path: Path, manifest_dict: Dict[Any, Any]) -> None:
def __init__(self, manifest_path: Path, manifest_dict: ManifestDict) -> None:
self.manifest_path = manifest_path
self.manifest_dict = manifest_dict

Expand Down
20 changes: 19 additions & 1 deletion src/manifestoo/utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import json
import sys
from typing import Iterable, List, Optional
from typing import Any, Dict, Iterable, List, Optional

import typer

from . import echo
from .addon import AddonDict
from .addons_set import AddonsSet
from .odoo_series import OdooSeries


Expand All @@ -29,6 +32,21 @@ def print_list(lst: Iterable[str], separator: str) -> None:
sys.stdout.write("\n")


def print_json(obj: Any) -> None:
json.dump(obj, sys.stdout)
sys.stdout.write("\n")


def print_addons_as_json(names: Iterable[str], addons_set: AddonsSet) -> None:
d: Dict[str, Optional[AddonDict]] = {}
for name in names:
if name in addons_set:
d[name] = addons_set[name].as_dict()
else:
d[name] = None
print_json(d)


def notice_or_abort(msg: str, abort: bool) -> None:
if abort:
echo.error(msg)
Expand Down
25 changes: 25 additions & 0 deletions tests/test_cmd_list.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import json

from typer.testing import CliRunner

from manifestoo.commands.list import list_command
Expand Down Expand Up @@ -26,3 +28,26 @@ def test_integration(tmp_path):
assert not result.exception
assert result.exit_code == 0, result.stderr
assert result.stdout == "a\nb\n"


def test_integration_json(tmp_path):
addons = {
"a": {"name": "A"},
"b": {},
}
populate_addons_dir(tmp_path, addons)
runner = CliRunner(mix_stderr=False)
result = runner.invoke(
app,
[f"--select-addons-dir={tmp_path}", "list", "--format=json"],
catch_exceptions=False,
)
assert not result.exception
assert result.exit_code == 0, result.stderr
json_output = json.loads(result.stdout)
assert "a" in json_output
assert "b" in json_output
assert "manifest" in json_output["a"]
assert "manifest_path" in json_output["a"]
assert "path" in json_output["a"]
assert json_output["a"]["manifest"] == addons["a"]
27 changes: 27 additions & 0 deletions tests/test_cmd_list_depends.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import json

from typer.testing import CliRunner

from manifestoo.commands.list_depends import list_depends_command
Expand Down Expand Up @@ -159,3 +161,28 @@ def test_integration(tmp_path):
assert not result.exception
assert result.exit_code == 0, result.stderr
assert result.stdout == "b\n"


def test_integration_json(tmp_path):
addons = {
"a": {"depends": ["b"]},
"b": {"name": "B"},
}
populate_addons_dir(tmp_path, addons)
runner = CliRunner(mix_stderr=False)
result = runner.invoke(
app,
[
f"--addons-path={tmp_path}",
"--select-include",
"a",
"list-depends",
"--format=json",
],
catch_exceptions=False,
)
assert not result.exception
assert result.exit_code == 0, result.stderr
json_output = json.loads(result.stdout)
assert len(json_output) == 1
assert json_output["b"]["manifest"] == addons["b"]
13 changes: 12 additions & 1 deletion tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import pytest
import typer

from manifestoo.utils import comma_split, not_implemented, notice_or_abort, print_list
from manifestoo.utils import (
comma_split,
not_implemented,
notice_or_abort,
print_json,
print_list,
)


@pytest.mark.parametrize(
Expand All @@ -24,6 +30,11 @@ def test_print_list(capsys):
assert capsys.readouterr().out == "b,a\n"


def test_print_json(capsys):
print_json(["b", "a"])
assert capsys.readouterr().out == '["b", "a"]\n'


def test_print_empty_list(capsys):
print_list([], ",")
assert capsys.readouterr().out == ""
Expand Down

0 comments on commit 78c536f

Please sign in to comment.