1"""GitLab Overviewer main entry point.
2
3Implements multiple specifications:
4
5* :any:`/specs/spec_renderer_markdown` §1-4 for markdown output
6* :any:`/specs/spec_renderer_quarto` §1-4 for quarto output
7* :any:`/specs/spec_table_sorting` §1-2 for sort key handling
8* :any:`/specs/spec_table_rendering_ui` §1-3 for table configuration
9* :any:`/specs/spec_settings` §1-3 for CLI argument handling
10"""
11
12from __future__ import annotations
13
14import dis
15import sys
16from enum import Enum
17from pathlib import Path
18from typing import Annotated, Optional
19
20import typer # type: ignore
21import yaml
22import argparse
23
24from ..config.settings import Settings
25from ..utils.logging import get_logger
26from ..services import DataCollector
27import gitlab_overviewer.services.sort_utils as sort_utils
28from ..rendering.markdown import MarkdownRenderer
29from ..rendering.quarto import QuartoRenderer
30from ..services.sort_utils import sort_overview
31
32app = typer.Typer(add_completion=False, help="GitLab Overviewer – modern CLI (Typer)")
33
34logger = get_logger(__name__)
35
36
40
41
[docs]
42@app.callback()
43def _common(
44 ctx: typer.Context,
45 sort: Annotated[
46 Optional[str],
47 typer.Option("--sort", help="Sort definition e.g. 'priority:desc'"),
48 ] = None,
49 table_config: Annotated[
50 str,
51 typer.Option(
52 "--table-config", metavar="PATH", help="Path to table_config.yaml"
53 ),
54 ] = "table_config.yaml",
55 log_level: Annotated[
56 str, typer.Option("--log-level", help="Log level (DEBUG, INFO, WARNING, ERROR)")
57 ] = "INFO",
58 output: Annotated[
59 OutputFormat, typer.Option("--output", "-o", case_sensitive=False)
60 ] = OutputFormat.markdown,
61 debug: Annotated[
62 bool, typer.Option("--debug", is_flag=True, help="Enable debug mode")
63 ] = False,
64 display_shared: Annotated[
65 bool,
66 typer.Option(
67 "--display-shared",
68 is_flag=True,
69 help="Includes shared projects in output when true.",
70 ),
71 ] = False,
72 gitlab_host: Annotated[
73 Optional[str],
74 typer.Option(
75 "--gitlab-host",
76 help="Base URL of GitLab instance. Overrides ENV GITLAB_HOST.",
77 ),
78 ] = None,
79 group_api_key: Annotated[
80 Optional[str],
81 typer.Option(
82 "--group-api-key",
83 help="API-Key(s) as JSON-string or simple string. Overrides ENV GROUP_API_KEY.",
84 ),
85 ] = None,
86):
87 """Store common CLI params into context object."""
88
89 # Build Settings instance directly from Typer params
90 settings = Settings(
91 sort=sort,
92 table_config=table_config,
93 log_level=log_level,
94 output=str(output),
95 debug=debug,
96 display_shared=display_shared,
97 gitlab_host=gitlab_host,
98 group_api_key=group_api_key,
99 )
100 Settings.set_singleton(settings)
101 if debug:
102 logger.setLevel("DEBUG")
103 ctx.obj = {
104 "settings": settings,
105 "format": output,
106 "sort": sort,
107 }
108
109
[docs]
110@app.command()
111def run(ctx: typer.Context): # noqa: D401
112 """Run the overview pipeline with the provided options."""
113 settings: Settings = ctx.obj["settings"] # type: ignore[index]
114 logger.info("Running GitLab Overviewer with host %s", settings.gitlab_host)
115
116 # Table config loading (with fallback)
117 table_config_path = settings.table_config or "table_config.yaml"
118 try:
119 with open(table_config_path, "r", encoding="utf-8") as f:
120 table_config = yaml.safe_load(f)
121 except Exception as e:
122 logger.warning("Could not load table_config.yaml: %s", e)
123 table_config = None
124
125 if table_config is None or "columns" not in table_config:
126 logger.warning("No valid table column configuration found, using defaults.")
127 table_config = {
128 "columns": [
129 {"key": "repo", "label": "Repository", "visible": True},
130 {"key": "type", "label": "Typ", "visible": True},
131 {"key": "priority", "label": "Wichtigkeit", "visible": True},
132 {"key": "urgency", "label": "Dringlichkeit", "visible": True},
133 {"key": "date", "label": "Letzte Aktivität", "visible": True},
134 {"key": "supervisors", "label": "Betreuung/Autoren", "visible": True},
135 {"key": "version", "label": "Version", "visible": False},
136 {"key": "status", "label": "Status", "visible": True},
137 {"key": "todos", "label": "Todos", "visible": True},
138 ],
139 }
140
141 if settings.debug:
142 logger.debug("Loaded table column configuration:")
143 logger.debug(yaml.dump(table_config, allow_unicode=True))
144
145 # Data collection
146 collector = DataCollector()
147 overview_rows = collector.collect()
148
149 # Sort the overview data
150 sort_arg = ctx.obj.get("sort")
151 sorted_overviewdata = sort_overview(overview_rows, table_config, sort_arg)
152
153 output_format = ctx.obj["format"]
154 if output_format == OutputFormat.markdown:
155 rendered = MarkdownRenderer(table_config).render(overview_rows)
156 # Write to Overview.md in the project root
157 with open("Overview.md", "w", encoding="utf-8") as f:
158 f.write(rendered)
159 logger.info("Markdown overview written to Overview.md")
160 elif output_format == OutputFormat.quarto:
161 renderer = QuartoRenderer(table_config)
162 renderer.write_files(sorted_overviewdata, base_dir="quarto")
163 logger.info("Quarto .qmd files written to 'quarto/' directory.")
164 else:
165 raise ValueError(f"Unknown output format: {output_format}")
166
167
[docs]
168@app.command()
169def overview(ctx: typer.Context):
170 """Collect and sort overview data using new pipeline."""
171 settings: Settings = ctx.obj["settings"]
172 sort_arg = ctx.obj["sort"]
173 table_config_path = settings.table_config
174 with open(table_config_path, "r", encoding="utf-8") as f:
175 table_config = yaml.safe_load(f)
176 collector = DataCollector()
177 overview_rows = collector.collect()
178
179 # Sort the overview data
180 sort_arg = ctx.obj.get("sort")
181 sorted_overviewdata = sort_overview(overview_rows, table_config, sort_arg)
182
183 output_format = ctx.obj["format"]
184 if output_format == OutputFormat.markdown:
185 rendered = MarkdownRenderer(table_config).render(overview_rows)
186 # Write to Overview.md in the project root
187 with open("Overview.md", "w", encoding="utf-8") as f:
188 f.write(rendered)
189 logger.info("Markdown overview written to Overview.md")
190 elif output_format == OutputFormat.quarto:
191 renderer = QuartoRenderer(table_config)
192 renderer.write_files(sorted_overviewdata, base_dir="quarto")
193 logger.info("Quarto .qmd files written to 'quarto/' directory.")
194 else:
195 raise ValueError(f"Unknown output format: {output_format}")
196
197
198if __name__ == "__main__": # pragma: no cover
199 app()