Skip to content

Commit 58f9692

Browse files
authored
Merge pull request #4 from CoderChen01/cjj_dev
Complete the config command and json data export
2 parents 8012abf + 37d6523 commit 58f9692

36 files changed

+546
-126
lines changed

opendigger_pycli/cli/__init__.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,8 @@
33
from .commands.config_cmd import config
44
from .commands.display_cmd import display
55
from .commands.export_cmd import export
6-
from .commands.monitor_cmd import monitor
7-
from .commands.report_cmd import report
86

97
opendigger.add_command(config)
108

119
query.add_command(display)
1210
query.add_command(export)
13-
query.add_command(report)
14-
query.add_command(monitor)
Lines changed: 103 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,107 @@
1-
import typing as t # noqa: F401
1+
import typing as t
2+
from dataclasses import fields
23

34
import click
5+
from click.shell_completion import CompletionItem
46

7+
from opendigger_pycli.console import CONSOLE
58

6-
@click.command("config")
7-
def config():
8-
pass
9+
from ..base import pass_environment
10+
from ..config import ALL_CONFIGS
11+
12+
if t.TYPE_CHECKING:
13+
from click.core import Context, Parameter
14+
15+
from opendigger_pycli.datatypes.config import BaseConfig
16+
17+
from ..base import Environment
18+
19+
20+
def config_shell_completion(
21+
ctx: t.Optional["Context"], param: t.Optional["Parameter"], incomplete: str
22+
) -> t.List[CompletionItem]:
23+
incomplete_splited = incomplete.rsplit(".", 1)[0]
24+
if incomplete_splited == "":
25+
return [
26+
CompletionItem(config_dataclass_key) for config_dataclass_key in ALL_CONFIGS
27+
]
28+
is_key = False
29+
last_config_dataclass: t.Optional[BaseConfig] = None
30+
for config_dataclass_key in ALL_CONFIGS:
31+
if incomplete_splited.startswith(config_dataclass_key):
32+
if incomplete_splited != config_dataclass_key:
33+
return [CompletionItem(config_dataclass_key + ".")]
34+
else:
35+
is_key = True
36+
last_config_dataclass = ALL_CONFIGS[config_dataclass_key] # type: ignore
37+
break
38+
else:
39+
continue
40+
41+
if not is_key and last_config_dataclass is None:
42+
return []
43+
44+
if incomplete[:-1] == incomplete_splited:
45+
return [CompletionItem(f"{incomplete_splited}.{field.name}") for field in fields(last_config_dataclass)] # type: ignore
46+
47+
for field in fields(last_config_dataclass): # type: ignore
48+
if incomplete in f"{incomplete_splited}.{field.name}":
49+
return [CompletionItem(f"{incomplete_splited}.{field.name}")]
50+
51+
return []
52+
53+
54+
def parse_config_key(key: str) -> t.Tuple[str, str]:
55+
section_name, config_key = key.rsplit(".", 1)
56+
return section_name, config_key
57+
58+
59+
def check_config_setting(
60+
ctx: "Context", param: "Parameter", values: t.List[t.Tuple[str, str]]
61+
) -> t.List[t.Tuple[str, str]]:
62+
for value in values:
63+
try:
64+
section_name, config_key = parse_config_key(value[0])
65+
except Exception:
66+
raise click.BadParameter(f"{value[0]} is not a valid config key")
67+
else:
68+
if section_name not in ALL_CONFIGS or config_key not in [
69+
field.name for field in fields(ALL_CONFIGS[section_name])
70+
]:
71+
raise click.BadParameter(f"{value[0]} is not a valid config key")
72+
return values
73+
74+
75+
@click.command("config") # type: ignore
76+
@click.option(
77+
"--set",
78+
"-s",
79+
"config_settings",
80+
type=(str, str),
81+
multiple=True,
82+
callback=check_config_setting,
83+
shell_complete=config_shell_completion,
84+
help="Set config value",
85+
required=True,
86+
)
87+
@click.option(
88+
"--persist/--no-persist",
89+
"-p/-n",
90+
is_flag=True,
91+
default=True,
92+
help="Set config value persistently",
93+
)
94+
@pass_environment
95+
def config(
96+
env: "Environment", config_settings: t.List[t.Tuple[str, str]], persist: bool
97+
) -> None:
98+
"""Set config value"""
99+
for config_setting in config_settings:
100+
key, value = config_setting
101+
section_name, key = parse_config_key(key)
102+
env.dlog(f"set config: {section_name}.{key}={value}")
103+
env.set_config(section_name, key, value, is_persist=persist)
104+
env.dlog(f"finished to set config: {section_name}.{key}={value}")
105+
106+
CONSOLE.print("[green]Config set successfully[/]")
107+
CONSOLE.print(env.cli_config)

opendigger_pycli/cli/commands/display_cmd.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from opendigger_pycli.console.print_indicator import SURPPORTED_DISPLAY_FORMAT_TYPE
1414
from opendigger_pycli.results.query import QueryResults
1515

16-
from .base import Environment
16+
from ..base import Environment
1717

1818

1919
@click.command("display", help="Display metrics")
Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,53 @@
11
import typing as t
2+
from pathlib import Path
23

34
import click
45

6+
from opendigger_pycli.exporters import (
7+
SURPPORTED_EXPORT_FORMAT_TYPE,
8+
SURPPORTED_EXPORT_FORMATS,
9+
)
10+
from opendigger_pycli.results.export import ExportResult
511
from opendigger_pycli.utils.decorators import processor
612

713
from ..base import pass_environment
814

915
if t.TYPE_CHECKING:
16+
from opendigger_pycli.results.query import QueryResults
17+
1018
from ..base import Environment
1119

1220

1321
@click.command("export", help="Export metrics")
1422
@click.option(
1523
"--format",
1624
"-f",
17-
"format_name",
18-
type=click.Choice(["csv", "json", "mhtml"]),
25+
type=click.Choice(SURPPORTED_EXPORT_FORMATS),
1926
required=True,
27+
help="Format to export",
28+
)
29+
@click.option(
30+
"--save-dir",
31+
"-s",
32+
"save_dir",
33+
type=click.Path(file_okay=False, resolve_path=True, path_type=Path),
34+
required=True,
35+
help="Directory to save indicators",
36+
)
37+
@click.option(
38+
"--split/--no-split",
39+
"is_split",
40+
default=True,
41+
help="Save indicators in separate files",
2042
)
21-
@click.option("--filename", "-o", "filename", type=str, required=True)
2243
@processor
2344
@pass_environment
2445
def export(
2546
env: "Environment",
26-
format_name: t.Literal["csv", "json", "mhtml"],
27-
filename: click.Path,
47+
results: "QueryResults",
48+
format: SURPPORTED_EXPORT_FORMAT_TYPE,
49+
save_dir: Path,
50+
is_split: bool,
2851
):
29-
pass
52+
ExportResult(results, format, save_dir, is_split).export()
53+
yield from results

opendigger_pycli/cli/commands/monitor_cmd.py

Lines changed: 0 additions & 8 deletions
This file was deleted.

opendigger_pycli/cli/commands/report_cmd.py

Lines changed: 0 additions & 8 deletions
This file was deleted.

opendigger_pycli/cli/config.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ def __init__(self):
2424
def user_config_file_path(self) -> str:
2525
config_dir_str = click.get_app_dir("opendigger-pycli")
2626
config_dir = Path(config_dir_str)
27+
config_dir.mkdir(parents=True, exist_ok=True)
2728
user_config = config_dir / "config.ini"
29+
if not user_config.exists():
30+
user_config.touch()
2831
return str(user_config)
2932

3033
@property
@@ -40,7 +43,9 @@ def __load_config(self):
4043
parser = configparser.RawConfigParser()
4144
parser.read(self.config_file_paths)
4245

43-
for config_dataclass in ALL_CONFIGS:
46+
for config_dataclass_key in ALL_CONFIGS:
47+
config_dataclass = ALL_CONFIGS[config_dataclass_key]
48+
config_dataclass = ALL_CONFIGS[config_dataclass_key]
4449
if not is_dataclass(config_dataclass):
4550
raise TypeError(f"{config_dataclass} is not a dataclass")
4651

@@ -54,8 +59,10 @@ def __load_config(self):
5459

5560
def update_config(self):
5661
parser = configparser.RawConfigParser()
62+
parser.read(self.config_file_paths)
5763

58-
for config_dataclass in ALL_CONFIGS:
64+
for config_dataclass_key in ALL_CONFIGS:
65+
config_dataclass = ALL_CONFIGS[config_dataclass_key]
5966
if not is_dataclass(config_dataclass):
6067
raise TypeError(f"{config_dataclass} is not a dataclass")
6168

@@ -75,7 +82,8 @@ def __rich_console__(
7582
self, console: "Console", options: "ConsoleOptions"
7683
) -> "RenderResult":
7784
yield "[b]OpenDigger Python CLI Configs:[/b]"
78-
for config_dataclass in ALL_CONFIGS:
85+
for config_dataclass_key in ALL_CONFIGS:
86+
config_dataclass = ALL_CONFIGS[config_dataclass_key]
7987
if not is_dataclass(config_dataclass):
8088
raise TypeError(f"{config_dataclass} is not a dataclass")
8189

opendigger_pycli/cli/custom_types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import click
44
from click.shell_completion import CompletionItem
55

6-
from opendigger_pycli.dataloader import (
6+
from opendigger_pycli.dataloaders import (
77
DeveloperNetworkRepoDataloader,
88
ProjectOpenRankNetworkRepoDataloader,
99
RepoNetworkRepoDataloader,

opendigger_pycli/cli/env.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,14 @@ def set_log_level(
107107
self.log("[bold green]verbose mode enabled")
108108

109109
self.load_configs()
110+
111+
def set_config(
112+
self, section_name: str, key: str, value: str, *, is_persist: bool = True
113+
) -> None:
114+
config = getattr(self.cli_config, section_name.replace(".", "_"))
115+
setattr(config, key, value)
116+
if is_persist:
117+
self.cli_config.update_config()
118+
self.dlog(
119+
f"[bold green]set config {section_name}.{key} to {value} successfully"
120+
)

opendigger_pycli/cli/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import click
55

6-
from opendigger_pycli.dataloader import filter_dataloader
6+
from opendigger_pycli.dataloaders import filter_dataloader
77

88
if t.TYPE_CHECKING:
99
from click import Context

0 commit comments

Comments
 (0)