esgvoc 2.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- esgvoc/__init__.py +3 -0
- esgvoc/api/__init__.py +91 -0
- esgvoc/api/data_descriptors/EMD_models/__init__.py +66 -0
- esgvoc/api/data_descriptors/EMD_models/arrangement.py +21 -0
- esgvoc/api/data_descriptors/EMD_models/calendar.py +5 -0
- esgvoc/api/data_descriptors/EMD_models/cell_variable_type.py +20 -0
- esgvoc/api/data_descriptors/EMD_models/component_type.py +5 -0
- esgvoc/api/data_descriptors/EMD_models/coordinate.py +52 -0
- esgvoc/api/data_descriptors/EMD_models/grid_mapping.py +19 -0
- esgvoc/api/data_descriptors/EMD_models/grid_region.py +19 -0
- esgvoc/api/data_descriptors/EMD_models/grid_type.py +19 -0
- esgvoc/api/data_descriptors/EMD_models/horizontal_computational_grid.py +56 -0
- esgvoc/api/data_descriptors/EMD_models/horizontal_grid_cells.py +230 -0
- esgvoc/api/data_descriptors/EMD_models/horizontal_subgrid.py +41 -0
- esgvoc/api/data_descriptors/EMD_models/horizontal_units.py +5 -0
- esgvoc/api/data_descriptors/EMD_models/model.py +139 -0
- esgvoc/api/data_descriptors/EMD_models/model_component.py +115 -0
- esgvoc/api/data_descriptors/EMD_models/reference.py +61 -0
- esgvoc/api/data_descriptors/EMD_models/resolution.py +48 -0
- esgvoc/api/data_descriptors/EMD_models/temporal_refinement.py +19 -0
- esgvoc/api/data_descriptors/EMD_models/truncation_method.py +17 -0
- esgvoc/api/data_descriptors/EMD_models/vertical_computational_grid.py +91 -0
- esgvoc/api/data_descriptors/EMD_models/vertical_coordinate.py +5 -0
- esgvoc/api/data_descriptors/EMD_models/vertical_units.py +19 -0
- esgvoc/api/data_descriptors/__init__.py +159 -0
- esgvoc/api/data_descriptors/activity.py +72 -0
- esgvoc/api/data_descriptors/archive.py +5 -0
- esgvoc/api/data_descriptors/area_label.py +30 -0
- esgvoc/api/data_descriptors/branded_suffix.py +30 -0
- esgvoc/api/data_descriptors/branded_variable.py +21 -0
- esgvoc/api/data_descriptors/citation_url.py +5 -0
- esgvoc/api/data_descriptors/contact.py +5 -0
- esgvoc/api/data_descriptors/conventions.py +28 -0
- esgvoc/api/data_descriptors/creation_date.py +18 -0
- esgvoc/api/data_descriptors/data_descriptor.py +127 -0
- esgvoc/api/data_descriptors/data_specs_version.py +25 -0
- esgvoc/api/data_descriptors/date.py +5 -0
- esgvoc/api/data_descriptors/directory_date.py +22 -0
- esgvoc/api/data_descriptors/drs_specs.py +38 -0
- esgvoc/api/data_descriptors/experiment.py +215 -0
- esgvoc/api/data_descriptors/forcing_index.py +21 -0
- esgvoc/api/data_descriptors/frequency.py +48 -0
- esgvoc/api/data_descriptors/further_info_url.py +5 -0
- esgvoc/api/data_descriptors/grid.py +43 -0
- esgvoc/api/data_descriptors/horizontal_label.py +20 -0
- esgvoc/api/data_descriptors/initialization_index.py +27 -0
- esgvoc/api/data_descriptors/institution.py +80 -0
- esgvoc/api/data_descriptors/known_branded_variable.py +75 -0
- esgvoc/api/data_descriptors/license.py +31 -0
- esgvoc/api/data_descriptors/member_id.py +9 -0
- esgvoc/api/data_descriptors/mip_era.py +26 -0
- esgvoc/api/data_descriptors/model_component.py +32 -0
- esgvoc/api/data_descriptors/models_test/models.py +17 -0
- esgvoc/api/data_descriptors/nominal_resolution.py +50 -0
- esgvoc/api/data_descriptors/obs_type.py +5 -0
- esgvoc/api/data_descriptors/organisation.py +22 -0
- esgvoc/api/data_descriptors/physics_index.py +21 -0
- esgvoc/api/data_descriptors/product.py +16 -0
- esgvoc/api/data_descriptors/publication_status.py +5 -0
- esgvoc/api/data_descriptors/realization_index.py +24 -0
- esgvoc/api/data_descriptors/realm.py +16 -0
- esgvoc/api/data_descriptors/regex.py +5 -0
- esgvoc/api/data_descriptors/region.py +35 -0
- esgvoc/api/data_descriptors/resolution.py +7 -0
- esgvoc/api/data_descriptors/source.py +120 -0
- esgvoc/api/data_descriptors/source_type.py +5 -0
- esgvoc/api/data_descriptors/sub_experiment.py +5 -0
- esgvoc/api/data_descriptors/table.py +28 -0
- esgvoc/api/data_descriptors/temporal_label.py +20 -0
- esgvoc/api/data_descriptors/time_range.py +17 -0
- esgvoc/api/data_descriptors/title.py +5 -0
- esgvoc/api/data_descriptors/tracking_id.py +67 -0
- esgvoc/api/data_descriptors/variable.py +56 -0
- esgvoc/api/data_descriptors/variant_label.py +25 -0
- esgvoc/api/data_descriptors/vertical_label.py +20 -0
- esgvoc/api/project_specs.py +143 -0
- esgvoc/api/projects.py +1253 -0
- esgvoc/api/py.typed +0 -0
- esgvoc/api/pydantic_handler.py +146 -0
- esgvoc/api/report.py +127 -0
- esgvoc/api/search.py +171 -0
- esgvoc/api/universe.py +434 -0
- esgvoc/apps/__init__.py +6 -0
- esgvoc/apps/cmor_tables/__init__.py +7 -0
- esgvoc/apps/cmor_tables/cvs_table.py +948 -0
- esgvoc/apps/drs/__init__.py +0 -0
- esgvoc/apps/drs/constants.py +2 -0
- esgvoc/apps/drs/generator.py +429 -0
- esgvoc/apps/drs/report.py +540 -0
- esgvoc/apps/drs/validator.py +312 -0
- esgvoc/apps/ga/__init__.py +104 -0
- esgvoc/apps/ga/example_usage.py +315 -0
- esgvoc/apps/ga/models/__init__.py +47 -0
- esgvoc/apps/ga/models/netcdf_header.py +306 -0
- esgvoc/apps/ga/models/validator.py +491 -0
- esgvoc/apps/ga/test_ga.py +161 -0
- esgvoc/apps/ga/validator.py +277 -0
- esgvoc/apps/jsg/json_schema_generator.py +341 -0
- esgvoc/apps/jsg/templates/template.jinja +241 -0
- esgvoc/apps/test_cv/README.md +214 -0
- esgvoc/apps/test_cv/__init__.py +0 -0
- esgvoc/apps/test_cv/cv_tester.py +1611 -0
- esgvoc/apps/test_cv/example_usage.py +216 -0
- esgvoc/apps/vr/__init__.py +12 -0
- esgvoc/apps/vr/build_variable_registry.py +71 -0
- esgvoc/apps/vr/example_usage.py +60 -0
- esgvoc/apps/vr/vr_app.py +333 -0
- esgvoc/cli/clean.py +304 -0
- esgvoc/cli/cmor.py +46 -0
- esgvoc/cli/config.py +1300 -0
- esgvoc/cli/drs.py +267 -0
- esgvoc/cli/find.py +138 -0
- esgvoc/cli/get.py +155 -0
- esgvoc/cli/install.py +41 -0
- esgvoc/cli/main.py +60 -0
- esgvoc/cli/offline.py +269 -0
- esgvoc/cli/status.py +79 -0
- esgvoc/cli/test_cv.py +258 -0
- esgvoc/cli/valid.py +147 -0
- esgvoc/core/constants.py +17 -0
- esgvoc/core/convert.py +0 -0
- esgvoc/core/data_handler.py +206 -0
- esgvoc/core/db/__init__.py +3 -0
- esgvoc/core/db/connection.py +40 -0
- esgvoc/core/db/models/mixins.py +25 -0
- esgvoc/core/db/models/project.py +102 -0
- esgvoc/core/db/models/universe.py +98 -0
- esgvoc/core/db/project_ingestion.py +231 -0
- esgvoc/core/db/universe_ingestion.py +172 -0
- esgvoc/core/exceptions.py +33 -0
- esgvoc/core/logging_handler.py +26 -0
- esgvoc/core/repo_fetcher.py +345 -0
- esgvoc/core/service/__init__.py +41 -0
- esgvoc/core/service/configuration/config_manager.py +196 -0
- esgvoc/core/service/configuration/setting.py +363 -0
- esgvoc/core/service/data_merger.py +634 -0
- esgvoc/core/service/esg_voc.py +77 -0
- esgvoc/core/service/resolver_config.py +56 -0
- esgvoc/core/service/state.py +324 -0
- esgvoc/core/service/string_heuristics.py +98 -0
- esgvoc/core/service/term_cache.py +108 -0
- esgvoc/core/service/uri_resolver.py +133 -0
- esgvoc-2.0.2.dist-info/METADATA +82 -0
- esgvoc-2.0.2.dist-info/RECORD +147 -0
- esgvoc-2.0.2.dist-info/WHEEL +4 -0
- esgvoc-2.0.2.dist-info/entry_points.txt +2 -0
- esgvoc-2.0.2.dist-info/licenses/LICENSE.txt +519 -0
esgvoc/cli/config.py
ADDED
|
@@ -0,0 +1,1300 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
|
|
6
|
+
import toml
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.syntax import Syntax
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
|
|
12
|
+
# Import service module but don't initialize it immediately
|
|
13
|
+
import esgvoc.core.service.configuration.config_manager as config_manager_module
|
|
14
|
+
from esgvoc.core.service.configuration.setting import ServiceSettings
|
|
15
|
+
|
|
16
|
+
def get_service():
|
|
17
|
+
"""Get the service module, importing it only when needed."""
|
|
18
|
+
import esgvoc.core.service as service
|
|
19
|
+
return service
|
|
20
|
+
|
|
21
|
+
app = typer.Typer()
|
|
22
|
+
console = Console()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _get_fresh_config(config_manager, config_name: str):
|
|
26
|
+
"""
|
|
27
|
+
Get a fresh configuration, bypassing any potential caching issues.
|
|
28
|
+
"""
|
|
29
|
+
# Force reload from file to ensure we have the latest state
|
|
30
|
+
configs = config_manager.list_configs()
|
|
31
|
+
config_path = configs[config_name]
|
|
32
|
+
|
|
33
|
+
# Load directly from file to avoid any caching
|
|
34
|
+
try:
|
|
35
|
+
data = toml.load(config_path)
|
|
36
|
+
projects = {p["project_name"]: ServiceSettings.ProjectSettings(**p) for p in data.pop("projects", [])}
|
|
37
|
+
from esgvoc.core.service.configuration.setting import UniverseSettings
|
|
38
|
+
|
|
39
|
+
return ServiceSettings(universe=UniverseSettings(**data["universe"]), projects=projects)
|
|
40
|
+
except Exception:
|
|
41
|
+
# Fallback to config manager if direct load fails
|
|
42
|
+
return config_manager.get_config(config_name)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _save_and_reload_config(config_manager, config_name: str, config):
|
|
46
|
+
"""
|
|
47
|
+
Save configuration and ensure proper state reload.
|
|
48
|
+
"""
|
|
49
|
+
config_manager.save_active_config(config)
|
|
50
|
+
|
|
51
|
+
# Reset the state if we modified the active configuration
|
|
52
|
+
if config_name == config_manager.get_active_config_name():
|
|
53
|
+
service.current_state = service.get_state()
|
|
54
|
+
|
|
55
|
+
# Clear any potential caches in the config manager
|
|
56
|
+
if hasattr(config_manager, "_cached_config"):
|
|
57
|
+
config_manager._cached_config = None
|
|
58
|
+
if hasattr(config_manager, "cache"):
|
|
59
|
+
config_manager.cache.clear()
|
|
60
|
+
|
|
61
|
+
"""
|
|
62
|
+
Function to display a rich table in the console.
|
|
63
|
+
|
|
64
|
+
:param table: The table to be displayed
|
|
65
|
+
"""
|
|
66
|
+
console = Console(record=True, width=200)
|
|
67
|
+
console.print(table)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def display(table):
|
|
71
|
+
"""
|
|
72
|
+
Function to display a rich table in the console.
|
|
73
|
+
|
|
74
|
+
:param table: The table to be displayed
|
|
75
|
+
"""
|
|
76
|
+
console = Console(record=True, width=200)
|
|
77
|
+
console.print(table)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@app.command()
|
|
81
|
+
def list():
|
|
82
|
+
"""
|
|
83
|
+
List all available configurations.
|
|
84
|
+
|
|
85
|
+
Displays all available configurations along with the active one.
|
|
86
|
+
"""
|
|
87
|
+
service = get_service()
|
|
88
|
+
config_manager = service.get_config_manager()
|
|
89
|
+
configs = config_manager.list_configs()
|
|
90
|
+
active_config = config_manager.get_active_config_name()
|
|
91
|
+
|
|
92
|
+
table = Table(title="Available Configurations")
|
|
93
|
+
table.add_column("Name", style="cyan")
|
|
94
|
+
table.add_column("Path", style="green")
|
|
95
|
+
table.add_column("Status", style="magenta")
|
|
96
|
+
|
|
97
|
+
for name, path in configs.items():
|
|
98
|
+
status = "🟢 Active" if name == active_config else ""
|
|
99
|
+
table.add_row(name, path, status)
|
|
100
|
+
|
|
101
|
+
display(table)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@app.command()
|
|
105
|
+
def show(
|
|
106
|
+
name: Optional[str] = typer.Argument(
|
|
107
|
+
None, help="Name of the configuration to show. If not provided, shows the active configuration."
|
|
108
|
+
),
|
|
109
|
+
):
|
|
110
|
+
"""
|
|
111
|
+
Show the content of a specific configuration.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
name: Name of the configuration to show. Shows the active configuration if not specified.
|
|
115
|
+
"""
|
|
116
|
+
service = get_service()
|
|
117
|
+
config_manager = service.get_config_manager()
|
|
118
|
+
if name is None:
|
|
119
|
+
name = config_manager.get_active_config_name()
|
|
120
|
+
console.print(f"Showing active configuration: [cyan]{name}[/cyan]")
|
|
121
|
+
|
|
122
|
+
configs = config_manager.list_configs()
|
|
123
|
+
if name not in configs:
|
|
124
|
+
console.print(f"[red]Error: Configuration '{name}' not found.[/red]")
|
|
125
|
+
raise typer.Exit(1)
|
|
126
|
+
|
|
127
|
+
config_path = configs[name]
|
|
128
|
+
try:
|
|
129
|
+
with open(config_path, "r") as f:
|
|
130
|
+
content = f.read()
|
|
131
|
+
|
|
132
|
+
syntax = Syntax(content, "toml", theme="monokai", line_numbers=True)
|
|
133
|
+
console.print(syntax)
|
|
134
|
+
except Exception as e:
|
|
135
|
+
console.print(f"[red]Error reading configuration file: {str(e)}[/red]")
|
|
136
|
+
raise typer.Exit(1)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@app.command()
|
|
140
|
+
def switch(name: str = typer.Argument(..., help="Name of the configuration to switch to.")):
|
|
141
|
+
"""
|
|
142
|
+
Switch to a different configuration.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
name: Name of the configuration to switch to.
|
|
146
|
+
"""
|
|
147
|
+
service = get_service()
|
|
148
|
+
config_manager = service.get_config_manager()
|
|
149
|
+
configs = config_manager.list_configs()
|
|
150
|
+
|
|
151
|
+
if name not in configs:
|
|
152
|
+
console.print(f"[red]Error: Configuration '{name}' not found.[/red]")
|
|
153
|
+
raise typer.Exit(1)
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
config_manager.switch_config(name)
|
|
157
|
+
console.print(f"[green]Successfully switched to configuration: [cyan]{name}[/cyan][/green]")
|
|
158
|
+
|
|
159
|
+
# Reset the state to use the new configuration
|
|
160
|
+
service.current_state = service.get_state()
|
|
161
|
+
except Exception as e:
|
|
162
|
+
console.print(f"[red]Error switching configuration: {str(e)}[/red]")
|
|
163
|
+
raise typer.Exit(1)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@app.command()
|
|
167
|
+
def create(
|
|
168
|
+
name: str = typer.Argument(..., help="Name for the new configuration."),
|
|
169
|
+
base: Optional[str] = typer.Option(
|
|
170
|
+
None, "--base", "-b", help="Base the new configuration on an existing one. Uses the default if not specified."
|
|
171
|
+
),
|
|
172
|
+
switch_to: bool = typer.Option(False, "--switch", "-s", help="Switch to the new configuration after creating it."),
|
|
173
|
+
):
|
|
174
|
+
"""
|
|
175
|
+
Create a new configuration.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
name: Name for the new configuration.
|
|
179
|
+
base: Base the new configuration on an existing one. Uses the default if not specified.
|
|
180
|
+
switch_to: Switch to the new configuration after creating it.
|
|
181
|
+
"""
|
|
182
|
+
service = get_service()
|
|
183
|
+
config_manager = service.get_config_manager()
|
|
184
|
+
configs = config_manager.list_configs()
|
|
185
|
+
|
|
186
|
+
if name in configs:
|
|
187
|
+
console.print(f"[red]Error: Configuration '{name}' already exists.[/red]")
|
|
188
|
+
raise typer.Exit(1)
|
|
189
|
+
|
|
190
|
+
if base and base not in configs:
|
|
191
|
+
console.print(f"[red]Error: Base configuration '{base}' not found.[/red]")
|
|
192
|
+
raise typer.Exit(1)
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
if base:
|
|
196
|
+
# Load the base configuration
|
|
197
|
+
base_config = config_manager.get_config(base)
|
|
198
|
+
config_data = base_config.dump()
|
|
199
|
+
else:
|
|
200
|
+
# Use default settings
|
|
201
|
+
config_data = ServiceSettings._get_default_settings()
|
|
202
|
+
|
|
203
|
+
# Add the new configuration
|
|
204
|
+
config_manager.add_config(name, config_data)
|
|
205
|
+
console.print(f"[green]Successfully created configuration: [cyan]{name}[/cyan][/green]")
|
|
206
|
+
|
|
207
|
+
if switch_to:
|
|
208
|
+
config_manager.switch_config(name)
|
|
209
|
+
console.print(f"[green]Switched to configuration: [cyan]{name}[/cyan][/green]")
|
|
210
|
+
# Reset the state to use the new configuration
|
|
211
|
+
service.current_state = service.get_state()
|
|
212
|
+
|
|
213
|
+
except Exception as e:
|
|
214
|
+
console.print(f"[red]Error creating configuration: {str(e)}[/red]")
|
|
215
|
+
raise typer.Exit(1)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@app.command()
|
|
219
|
+
def remove(name: str = typer.Argument(..., help="Name of the configuration to remove.")):
|
|
220
|
+
"""
|
|
221
|
+
Remove a configuration.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
name: Name of the configuration to remove.
|
|
225
|
+
"""
|
|
226
|
+
service = get_service()
|
|
227
|
+
config_manager = service.get_config_manager()
|
|
228
|
+
configs = config_manager.list_configs()
|
|
229
|
+
|
|
230
|
+
if name not in configs:
|
|
231
|
+
console.print(f"[red]Error: Configuration '{name}' not found.[/red]")
|
|
232
|
+
raise typer.Exit(1)
|
|
233
|
+
|
|
234
|
+
if name == "default":
|
|
235
|
+
console.print("[red]Error: Cannot remove the default configuration.[/red]")
|
|
236
|
+
raise typer.Exit(1)
|
|
237
|
+
|
|
238
|
+
confirm = typer.confirm(f"Are you sure you want to remove configuration '{name}'?")
|
|
239
|
+
if not confirm:
|
|
240
|
+
console.print("Operation cancelled.")
|
|
241
|
+
return
|
|
242
|
+
|
|
243
|
+
try:
|
|
244
|
+
active_config = config_manager.get_active_config_name()
|
|
245
|
+
config_manager.remove_config(name)
|
|
246
|
+
console.print(f"[green]Successfully removed configuration: [cyan]{name}[/cyan][/green]")
|
|
247
|
+
|
|
248
|
+
if active_config == name:
|
|
249
|
+
console.print("[yellow]Active configuration was removed. Switched to default.[/yellow]")
|
|
250
|
+
# Reset the state to use the default configuration
|
|
251
|
+
service.current_state = service.get_state()
|
|
252
|
+
except Exception as e:
|
|
253
|
+
console.print(f"[red]Error removing configuration: {str(e)}[/red]")
|
|
254
|
+
raise typer.Exit(1)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@app.command()
|
|
258
|
+
def edit(
|
|
259
|
+
name: Optional[str] = typer.Argument(
|
|
260
|
+
None, help="Name of the configuration to edit. Edits the active configuration if not specified."
|
|
261
|
+
),
|
|
262
|
+
editor: Optional[str] = typer.Option(
|
|
263
|
+
None, "--editor", "-e", help="Editor to use. Uses the system default if not specified."
|
|
264
|
+
),
|
|
265
|
+
):
|
|
266
|
+
"""
|
|
267
|
+
Edit a configuration using the system's default editor or a specified one.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
name: Name of the configuration to edit. Edits the active configuration if not specified.
|
|
271
|
+
editor: Editor to use. Uses the system default if not specified.
|
|
272
|
+
"""
|
|
273
|
+
service = get_service()
|
|
274
|
+
config_manager = service.get_config_manager()
|
|
275
|
+
if name is None:
|
|
276
|
+
name = config_manager.get_active_config_name()
|
|
277
|
+
console.print(f"Editing active configuration: [cyan]{name}[/cyan]")
|
|
278
|
+
|
|
279
|
+
configs = config_manager.list_configs()
|
|
280
|
+
if name not in configs:
|
|
281
|
+
console.print(f"[red]Error: Configuration '{name}' not found.[/red]")
|
|
282
|
+
raise typer.Exit(1)
|
|
283
|
+
|
|
284
|
+
config_path = configs[name]
|
|
285
|
+
|
|
286
|
+
editor_cmd = editor or os.environ.get("EDITOR", "vim")
|
|
287
|
+
try:
|
|
288
|
+
# Launch the editor properly by using a list of arguments instead of a string
|
|
289
|
+
import subprocess
|
|
290
|
+
|
|
291
|
+
result = subprocess.run([editor_cmd, str(config_path)], check=True)
|
|
292
|
+
if result.returncode == 0:
|
|
293
|
+
console.print(f"[green]Successfully edited configuration: [cyan]{name}[/cyan][/green]")
|
|
294
|
+
|
|
295
|
+
# Reset the state if we edited the active configuration
|
|
296
|
+
if name == config_manager.get_active_config_name():
|
|
297
|
+
service.current_state = service.get_state()
|
|
298
|
+
else:
|
|
299
|
+
console.print("[yellow]Editor exited with an error.[/yellow]")
|
|
300
|
+
except Exception as e:
|
|
301
|
+
console.print(f"[red]Error launching editor: {str(e)}[/red]")
|
|
302
|
+
raise typer.Exit(1)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@app.command()
|
|
306
|
+
def set(
|
|
307
|
+
changes: List[str] = typer.Argument(
|
|
308
|
+
...,
|
|
309
|
+
help="Changes in format 'component:key=value', where component is 'universe' or a project name. Multiple can be specified.",
|
|
310
|
+
),
|
|
311
|
+
config_name: Optional[str] = typer.Option(
|
|
312
|
+
None,
|
|
313
|
+
"--config",
|
|
314
|
+
"-c",
|
|
315
|
+
help="Name of the configuration to modify. Modifies the active configuration if not specified.",
|
|
316
|
+
),
|
|
317
|
+
):
|
|
318
|
+
"""
|
|
319
|
+
Modify configuration settings using a consistent syntax for universe and projects.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
changes: List of changes in format 'component:key=value'. For example:
|
|
323
|
+
'universe:branch=main' - Change the universe branch
|
|
324
|
+
'cmip6:github_repo=https://github.com/new/repo' - Change a project's repository
|
|
325
|
+
config_name: Name of the configuration to modify. Modifies the active configuration if not specified.
|
|
326
|
+
|
|
327
|
+
Examples:
|
|
328
|
+
# Change the universe branch in the active configuration
|
|
329
|
+
esgvoc config set 'universe:branch=esgvoc_dev'
|
|
330
|
+
|
|
331
|
+
# Enable offline mode for universe
|
|
332
|
+
esgvoc config set 'universe:offline_mode=true'
|
|
333
|
+
|
|
334
|
+
# Enable offline mode for a specific project
|
|
335
|
+
esgvoc config set 'cmip6:offline_mode=true'
|
|
336
|
+
|
|
337
|
+
# Change multiple components at once
|
|
338
|
+
esgvoc config set 'universe:branch=esgvoc_dev' 'cmip6:branch=esgvoc_dev'
|
|
339
|
+
|
|
340
|
+
# Change settings in a specific configuration
|
|
341
|
+
esgvoc config set 'universe:local_path=repos/prod/universe' --config prod
|
|
342
|
+
|
|
343
|
+
# Change the GitHub repository URL for a project
|
|
344
|
+
esgvoc config set 'cmip6:github_repo=https://github.com/WCRP-CMIP/CMIP6_CVs_new'
|
|
345
|
+
"""
|
|
346
|
+
service = get_service()
|
|
347
|
+
config_manager = service.get_config_manager()
|
|
348
|
+
if config_name is None:
|
|
349
|
+
config_name = config_manager.get_active_config_name()
|
|
350
|
+
console.print(f"Modifying active configuration: [cyan]{config_name}[/cyan]")
|
|
351
|
+
|
|
352
|
+
configs = config_manager.list_configs()
|
|
353
|
+
if config_name not in configs:
|
|
354
|
+
console.print(f"[red]Error: Configuration '{config_name}' not found.[/red]")
|
|
355
|
+
raise typer.Exit(1)
|
|
356
|
+
|
|
357
|
+
try:
|
|
358
|
+
# Load the configuration
|
|
359
|
+
config = config_manager.get_config(config_name)
|
|
360
|
+
modified = False
|
|
361
|
+
|
|
362
|
+
# Process all changes with the same format
|
|
363
|
+
for change in changes:
|
|
364
|
+
try:
|
|
365
|
+
# Format should be component:setting=value (where component is 'universe' or a project name)
|
|
366
|
+
component_part, setting_part = change.split(":", 1)
|
|
367
|
+
setting_key, setting_value = setting_part.split("=", 1)
|
|
368
|
+
|
|
369
|
+
# Handle universe settings
|
|
370
|
+
if component_part == "universe":
|
|
371
|
+
if setting_key == "github_repo":
|
|
372
|
+
config.universe.github_repo = setting_value
|
|
373
|
+
modified = True
|
|
374
|
+
elif setting_key == "branch":
|
|
375
|
+
config.universe.branch = setting_value
|
|
376
|
+
modified = True
|
|
377
|
+
elif setting_key == "local_path":
|
|
378
|
+
config.universe.local_path = setting_value
|
|
379
|
+
modified = True
|
|
380
|
+
elif setting_key == "db_path":
|
|
381
|
+
config.universe.db_path = setting_value
|
|
382
|
+
modified = True
|
|
383
|
+
elif setting_key == "offline_mode":
|
|
384
|
+
config.universe.offline_mode = setting_value.lower() in ("true", "1", "yes", "on")
|
|
385
|
+
modified = True
|
|
386
|
+
else:
|
|
387
|
+
console.print(f"[yellow]Warning: Unknown universe setting '{setting_key}'. Skipping.[/yellow]")
|
|
388
|
+
continue
|
|
389
|
+
|
|
390
|
+
console.print(f"[green]Updated universe.{setting_key} = {setting_value}[/green]")
|
|
391
|
+
|
|
392
|
+
# Handle project settings using the new update_project method
|
|
393
|
+
elif component_part in config.projects:
|
|
394
|
+
# Use the new update_project method
|
|
395
|
+
if config.update_project(component_part, **{setting_key: setting_value}):
|
|
396
|
+
modified = True
|
|
397
|
+
console.print(f"[green]Updated {component_part}.{setting_key} = {setting_value}[/green]")
|
|
398
|
+
else:
|
|
399
|
+
console.print(f"[yellow]Warning: Unknown project setting '{setting_key}'. Skipping.[/yellow]")
|
|
400
|
+
else:
|
|
401
|
+
console.print(
|
|
402
|
+
f"[yellow]Warning: Component '{component_part}' not found in configuration. Skipping.[/yellow]"
|
|
403
|
+
)
|
|
404
|
+
continue
|
|
405
|
+
|
|
406
|
+
except ValueError:
|
|
407
|
+
console.print(
|
|
408
|
+
f"[yellow]Warning: Invalid change format '{change}'. Should be 'component:key=value'. Skipping.[/yellow]"
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
if modified:
|
|
412
|
+
# Save the modified configuration
|
|
413
|
+
config_manager.save_active_config(config)
|
|
414
|
+
console.print(f"[green]Successfully updated configuration: [cyan]{config_name}[/cyan][/green]")
|
|
415
|
+
|
|
416
|
+
# Reset the state if we modified the active configuration
|
|
417
|
+
if config_name == config_manager.get_active_config_name():
|
|
418
|
+
service.current_state = service.get_state()
|
|
419
|
+
else:
|
|
420
|
+
console.print("[yellow]No changes were made to the configuration.[/yellow]")
|
|
421
|
+
|
|
422
|
+
except Exception as e:
|
|
423
|
+
console.print(f"[red]Error updating configuration: {str(e)}[/red]")
|
|
424
|
+
raise typer.Exit(1)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
# 🔹 NEW: Enhanced project management commands using ServiceSettings methods
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
@app.command()
|
|
431
|
+
def list_available_projects():
|
|
432
|
+
"""
|
|
433
|
+
List all available default projects that can be added.
|
|
434
|
+
"""
|
|
435
|
+
available_projects = ServiceSettings._get_default_project_configs()
|
|
436
|
+
|
|
437
|
+
table = Table(title="Available Default Projects")
|
|
438
|
+
table.add_column("Project Name", style="cyan")
|
|
439
|
+
table.add_column("Repository", style="green")
|
|
440
|
+
table.add_column("Branch", style="yellow")
|
|
441
|
+
|
|
442
|
+
for project_name, config in available_projects.items():
|
|
443
|
+
table.add_row(project_name, config["github_repo"], config["branch"])
|
|
444
|
+
|
|
445
|
+
display(table)
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
@app.command()
|
|
449
|
+
def list_projects(
|
|
450
|
+
config_name: Optional[str] = typer.Option(
|
|
451
|
+
None, "--config", "-c", help="Configuration name. Uses active configuration if not specified."
|
|
452
|
+
),
|
|
453
|
+
):
|
|
454
|
+
"""
|
|
455
|
+
List all projects in a configuration.
|
|
456
|
+
"""
|
|
457
|
+
service = get_service()
|
|
458
|
+
config_manager = service.get_config_manager()
|
|
459
|
+
if config_name is None:
|
|
460
|
+
config_name = config_manager.get_active_config_name()
|
|
461
|
+
console.print(f"Showing projects in active configuration: [cyan]{config_name}[/cyan]")
|
|
462
|
+
|
|
463
|
+
configs = config_manager.list_configs()
|
|
464
|
+
if config_name not in configs:
|
|
465
|
+
console.print(f"[red]Error: Configuration '{config_name}' not found.[/red]")
|
|
466
|
+
raise typer.Exit(1)
|
|
467
|
+
|
|
468
|
+
try:
|
|
469
|
+
config = config_manager.get_config(config_name)
|
|
470
|
+
|
|
471
|
+
if not config.projects:
|
|
472
|
+
console.print(f"[yellow]No projects found in configuration '{config_name}'.[/yellow]")
|
|
473
|
+
return
|
|
474
|
+
|
|
475
|
+
table = Table(title=f"Projects in Configuration: {config_name}")
|
|
476
|
+
table.add_column("Project Name", style="cyan")
|
|
477
|
+
table.add_column("Repository", style="green")
|
|
478
|
+
table.add_column("Branch", style="yellow")
|
|
479
|
+
table.add_column("Local Path", style="blue")
|
|
480
|
+
table.add_column("DB Path", style="magenta")
|
|
481
|
+
|
|
482
|
+
for project_name, project in config.projects.items():
|
|
483
|
+
table.add_row(
|
|
484
|
+
project_name,
|
|
485
|
+
project.github_repo,
|
|
486
|
+
project.branch or "main",
|
|
487
|
+
project.local_path or "N/A",
|
|
488
|
+
project.db_path or "N/A",
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
display(table)
|
|
492
|
+
|
|
493
|
+
except Exception as e:
|
|
494
|
+
console.print(f"[red]Error listing projects: {str(e)}[/red]")
|
|
495
|
+
raise typer.Exit(1)
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
@app.command()
|
|
499
|
+
def add_project(
|
|
500
|
+
project_name: str = typer.Argument(..., help="Name of the project to add."),
|
|
501
|
+
config_name: Optional[str] = typer.Option(
|
|
502
|
+
None, "--config", "-c", help="Configuration name. Uses active configuration if not specified."
|
|
503
|
+
),
|
|
504
|
+
from_default: bool = typer.Option(
|
|
505
|
+
True, "--from-default/--custom", help="Add from default configuration or specify custom settings."
|
|
506
|
+
),
|
|
507
|
+
# Custom project options (only used when --custom is specified)
|
|
508
|
+
github_repo: Optional[str] = typer.Option(
|
|
509
|
+
None, "--repo", "-r", help="GitHub repository URL (for custom projects)."
|
|
510
|
+
),
|
|
511
|
+
branch: Optional[str] = typer.Option("main", "--branch", "-b", help="Branch (for custom projects)."),
|
|
512
|
+
local_path: Optional[str] = typer.Option(None, "--local", "-l", help="Local path (for custom projects)."),
|
|
513
|
+
db_path: Optional[str] = typer.Option(None, "--db", "-d", help="Database path (for custom projects)."),
|
|
514
|
+
):
|
|
515
|
+
"""
|
|
516
|
+
Add a project to a configuration.
|
|
517
|
+
|
|
518
|
+
By default, adds from available default projects. Use --custom to specify custom settings.
|
|
519
|
+
|
|
520
|
+
Examples:
|
|
521
|
+
# Add a default project
|
|
522
|
+
esgvoc add-project input4mip
|
|
523
|
+
|
|
524
|
+
# Add a custom project
|
|
525
|
+
esgvoc add-project my_project --custom --repo https://github.com/me/repo
|
|
526
|
+
"""
|
|
527
|
+
service = get_service()
|
|
528
|
+
config_manager = service.get_config_manager()
|
|
529
|
+
if config_name is None:
|
|
530
|
+
config_name = config_manager.get_active_config_name()
|
|
531
|
+
console.print(f"Modifying active configuration: [cyan]{config_name}[/cyan]")
|
|
532
|
+
|
|
533
|
+
configs = config_manager.list_configs()
|
|
534
|
+
if config_name not in configs:
|
|
535
|
+
console.print(f"[red]Error: Configuration '{config_name}' not found.[/red]")
|
|
536
|
+
raise typer.Exit(1)
|
|
537
|
+
|
|
538
|
+
try:
|
|
539
|
+
# 🔹 FORCE FRESH LOAD: Load configuration directly from file to bypass any caching
|
|
540
|
+
configs = config_manager.list_configs()
|
|
541
|
+
config_path = configs[config_name]
|
|
542
|
+
|
|
543
|
+
# Load fresh configuration from file
|
|
544
|
+
try:
|
|
545
|
+
config = ServiceSettings.load_from_file(config_path)
|
|
546
|
+
console.print(f"[blue]Debug: Loaded fresh config from file[/blue]")
|
|
547
|
+
except Exception as e:
|
|
548
|
+
console.print(f"[yellow]Debug: Failed to load from file ({e}), using config manager[/yellow]")
|
|
549
|
+
config = config_manager.get_config(config_name)
|
|
550
|
+
|
|
551
|
+
# 🔹 DEBUG: Show current projects before adding
|
|
552
|
+
current_projects = []
|
|
553
|
+
if hasattr(config, "projects") and config.projects:
|
|
554
|
+
current_projects = [name for name in config.projects.keys()]
|
|
555
|
+
console.print(f"[blue]Debug: Current projects: {current_projects}[/blue]")
|
|
556
|
+
|
|
557
|
+
if from_default:
|
|
558
|
+
# Add from default configuration
|
|
559
|
+
if config.add_project_from_default(project_name):
|
|
560
|
+
console.print(
|
|
561
|
+
f"[green]Successfully added default project [cyan]{project_name}[/cyan] to configuration [cyan]{config_name}[/cyan][/green]"
|
|
562
|
+
)
|
|
563
|
+
else:
|
|
564
|
+
if config.has_project(project_name):
|
|
565
|
+
console.print(
|
|
566
|
+
f"[red]Error: Project '{project_name}' already exists in configuration '{config_name}'.[/red]"
|
|
567
|
+
)
|
|
568
|
+
else:
|
|
569
|
+
available = config.get_available_default_projects()
|
|
570
|
+
console.print(f"[red]Error: '{project_name}' is not a valid default project.[/red]")
|
|
571
|
+
console.print(f"[yellow]Available default projects: {', '.join(available)}[/yellow]")
|
|
572
|
+
raise typer.Exit(1)
|
|
573
|
+
else:
|
|
574
|
+
# Add custom project
|
|
575
|
+
if not github_repo:
|
|
576
|
+
console.print("[red]Error: --repo is required when adding custom projects.[/red]")
|
|
577
|
+
raise typer.Exit(1)
|
|
578
|
+
|
|
579
|
+
# Set default paths if not provided
|
|
580
|
+
if local_path is None:
|
|
581
|
+
local_path = f"repos/{project_name}"
|
|
582
|
+
if db_path is None:
|
|
583
|
+
db_path = f"dbs/{project_name}.sqlite"
|
|
584
|
+
|
|
585
|
+
custom_config = {
|
|
586
|
+
"project_name": project_name,
|
|
587
|
+
"github_repo": github_repo,
|
|
588
|
+
"branch": branch,
|
|
589
|
+
"local_path": local_path,
|
|
590
|
+
"db_path": db_path,
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if config.add_project_custom(custom_config):
|
|
594
|
+
console.print(
|
|
595
|
+
f"[green]Successfully added custom project [cyan]{project_name}[/cyan] to configuration [cyan]{config_name}[/cyan][/green]"
|
|
596
|
+
)
|
|
597
|
+
else:
|
|
598
|
+
console.print(
|
|
599
|
+
f"[red]Error: Project '{project_name}' already exists in configuration '{config_name}'.[/red]"
|
|
600
|
+
)
|
|
601
|
+
raise typer.Exit(1)
|
|
602
|
+
|
|
603
|
+
# Save the configuration
|
|
604
|
+
config_manager.save_active_config(config)
|
|
605
|
+
|
|
606
|
+
# Reset the state if we modified the active configuration
|
|
607
|
+
if config_name == config_manager.get_active_config_name():
|
|
608
|
+
service.current_state = service.get_state()
|
|
609
|
+
|
|
610
|
+
except ValueError as e:
|
|
611
|
+
console.print(f"[red]Error: {str(e)}[/red]")
|
|
612
|
+
raise typer.Exit(1)
|
|
613
|
+
except Exception as e:
|
|
614
|
+
console.print(f"[red]Error adding project: {str(e)}[/red]")
|
|
615
|
+
raise typer.Exit(1)
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
@app.command()
|
|
619
|
+
def remove_project(
|
|
620
|
+
project_name: str = typer.Argument(..., help="Name of the project to remove."),
|
|
621
|
+
config_name: Optional[str] = typer.Option(
|
|
622
|
+
None, "--config", "-c", help="Configuration name. Uses active configuration if not specified."
|
|
623
|
+
),
|
|
624
|
+
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation prompt."),
|
|
625
|
+
):
|
|
626
|
+
"""
|
|
627
|
+
Remove a project from a configuration.
|
|
628
|
+
"""
|
|
629
|
+
service = get_service()
|
|
630
|
+
config_manager = service.get_config_manager()
|
|
631
|
+
if config_name is None:
|
|
632
|
+
config_name = config_manager.get_active_config_name()
|
|
633
|
+
console.print(f"Modifying active configuration: [cyan]{config_name}[/cyan]")
|
|
634
|
+
|
|
635
|
+
configs = config_manager.list_configs()
|
|
636
|
+
if config_name not in configs:
|
|
637
|
+
console.print(f"[red]Error: Configuration '{config_name}' not found.[/red]")
|
|
638
|
+
raise typer.Exit(1)
|
|
639
|
+
|
|
640
|
+
try:
|
|
641
|
+
# 🔹 FORCE FRESH LOAD for removal too
|
|
642
|
+
configs = config_manager.list_configs()
|
|
643
|
+
config_path = configs[config_name]
|
|
644
|
+
|
|
645
|
+
try:
|
|
646
|
+
config = ServiceSettings.load_from_file(config_path)
|
|
647
|
+
console.print(f"[blue]Debug: Loaded fresh config from file for removal[/blue]")
|
|
648
|
+
except Exception as e:
|
|
649
|
+
console.print(f"[yellow]Debug: Failed to load from file ({e}), using config manager[/yellow]")
|
|
650
|
+
config = config_manager.get_config(config_name)
|
|
651
|
+
|
|
652
|
+
if not config.has_project(project_name):
|
|
653
|
+
console.print(f"[red]Error: Project '{project_name}' not found in configuration '{config_name}'.[/red]")
|
|
654
|
+
raise typer.Exit(1)
|
|
655
|
+
|
|
656
|
+
# Confirm removal unless forced
|
|
657
|
+
if not force:
|
|
658
|
+
confirm = typer.confirm(
|
|
659
|
+
f"Are you sure you want to remove project '{project_name}' from configuration '{config_name}'?"
|
|
660
|
+
)
|
|
661
|
+
if not confirm:
|
|
662
|
+
console.print("Operation cancelled.")
|
|
663
|
+
return
|
|
664
|
+
|
|
665
|
+
# Remove project using the new method
|
|
666
|
+
if config.remove_project(project_name):
|
|
667
|
+
console.print(
|
|
668
|
+
f"[green]Successfully removed project [cyan]{project_name}[/cyan] from configuration [cyan]{config_name}[/cyan][/green]"
|
|
669
|
+
)
|
|
670
|
+
else:
|
|
671
|
+
console.print(f"[red]Error: Failed to remove project '{project_name}'.[/red]")
|
|
672
|
+
raise typer.Exit(1)
|
|
673
|
+
|
|
674
|
+
# Save the configuration
|
|
675
|
+
config_manager.save_active_config(config)
|
|
676
|
+
|
|
677
|
+
# 🔹 DEBUG: Verify the project was actually removed
|
|
678
|
+
remaining_projects = []
|
|
679
|
+
if hasattr(config, "projects") and config.projects:
|
|
680
|
+
remaining_projects = [name for name in config.projects.keys()]
|
|
681
|
+
console.print(f"[blue]Debug: Projects after removal: {remaining_projects}[/blue]")
|
|
682
|
+
|
|
683
|
+
# Reset the state if we modified the active configuration
|
|
684
|
+
if config_name == config_manager.get_active_config_name():
|
|
685
|
+
service.current_state = service.get_state()
|
|
686
|
+
|
|
687
|
+
except Exception as e:
|
|
688
|
+
console.print(f"[red]Error removing project: {str(e)}[/red]")
|
|
689
|
+
raise typer.Exit(1)
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
@app.command()
|
|
693
|
+
def update_project(
|
|
694
|
+
project_name: str = typer.Argument(..., help="Name of the project to update."),
|
|
695
|
+
config_name: Optional[str] = typer.Option(
|
|
696
|
+
None, "--config", "-c", help="Configuration name. Uses active configuration if not specified."
|
|
697
|
+
),
|
|
698
|
+
github_repo: Optional[str] = typer.Option(None, "--repo", "-r", help="New GitHub repository URL."),
|
|
699
|
+
branch: Optional[str] = typer.Option(None, "--branch", "-b", help="New branch."),
|
|
700
|
+
local_path: Optional[str] = typer.Option(None, "--local", "-l", help="New local path."),
|
|
701
|
+
db_path: Optional[str] = typer.Option(None, "--db", "-d", help="New database path."),
|
|
702
|
+
):
|
|
703
|
+
"""
|
|
704
|
+
Update settings for an existing project.
|
|
705
|
+
"""
|
|
706
|
+
service = get_service()
|
|
707
|
+
config_manager = service.get_config_manager()
|
|
708
|
+
if config_name is None:
|
|
709
|
+
config_name = config_manager.get_active_config_name()
|
|
710
|
+
console.print(f"Modifying active configuration: [cyan]{config_name}[/cyan]")
|
|
711
|
+
|
|
712
|
+
configs = config_manager.list_configs()
|
|
713
|
+
if config_name not in configs:
|
|
714
|
+
console.print(f"[red]Error: Configuration '{config_name}' not found.[/red]")
|
|
715
|
+
raise typer.Exit(1)
|
|
716
|
+
|
|
717
|
+
try:
|
|
718
|
+
config = config_manager.get_config(config_name)
|
|
719
|
+
|
|
720
|
+
if not config.has_project(project_name):
|
|
721
|
+
console.print(f"[red]Error: Project '{project_name}' not found in configuration '{config_name}'.[/red]")
|
|
722
|
+
raise typer.Exit(1)
|
|
723
|
+
|
|
724
|
+
# Build update dict with non-None values
|
|
725
|
+
updates = {}
|
|
726
|
+
if github_repo is not None:
|
|
727
|
+
updates["github_repo"] = github_repo
|
|
728
|
+
if branch is not None:
|
|
729
|
+
updates["branch"] = branch
|
|
730
|
+
if local_path is not None:
|
|
731
|
+
updates["local_path"] = local_path
|
|
732
|
+
if db_path is not None:
|
|
733
|
+
updates["db_path"] = db_path
|
|
734
|
+
|
|
735
|
+
if not updates:
|
|
736
|
+
console.print(
|
|
737
|
+
"[yellow]No updates specified. Use --repo, --branch, --local, or --db to specify changes.[/yellow]"
|
|
738
|
+
)
|
|
739
|
+
return
|
|
740
|
+
|
|
741
|
+
# Update project using the new method
|
|
742
|
+
if config.update_project(project_name, **updates):
|
|
743
|
+
console.print(
|
|
744
|
+
f"[green]Successfully updated project [cyan]{project_name}[/cyan] in configuration [cyan]{config_name}[/cyan][/green]"
|
|
745
|
+
)
|
|
746
|
+
for key, value in updates.items():
|
|
747
|
+
console.print(f" [green]{key} = {value}[/green]")
|
|
748
|
+
else:
|
|
749
|
+
console.print(f"[red]Error: Failed to update project '{project_name}'.[/red]")
|
|
750
|
+
raise typer.Exit(1)
|
|
751
|
+
|
|
752
|
+
# Save the configuration
|
|
753
|
+
config_manager.save_active_config(config)
|
|
754
|
+
|
|
755
|
+
# Reset the state if we modified the active configuration
|
|
756
|
+
if config_name == config_manager.get_active_config_name():
|
|
757
|
+
service.current_state = service.get_state()
|
|
758
|
+
|
|
759
|
+
except Exception as e:
|
|
760
|
+
console.print(f"[red]Error updating project: {str(e)}[/red]")
|
|
761
|
+
raise typer.Exit(1)
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
# 🔹 NEW: Simple config management commands
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
@app.command()
|
|
768
|
+
def add(
|
|
769
|
+
project_names: List[str] = typer.Argument(..., help="Names of the projects to add from defaults."),
|
|
770
|
+
config_name: Optional[str] = typer.Option(
|
|
771
|
+
None, "--config", "-c", help="Configuration name. Uses active configuration if not specified."
|
|
772
|
+
),
|
|
773
|
+
):
|
|
774
|
+
"""
|
|
775
|
+
Add one or more default projects to the current configuration and install their CVs.
|
|
776
|
+
|
|
777
|
+
This will:
|
|
778
|
+
1. Add the projects to the configuration using default settings
|
|
779
|
+
2. Download the projects' CVs by running synchronize_all
|
|
780
|
+
|
|
781
|
+
Examples:
|
|
782
|
+
esgvoc config add input4mip
|
|
783
|
+
esgvoc config add input4mip obs4mip cordex-cmip6
|
|
784
|
+
esgvoc config add obs4mip --config my_config
|
|
785
|
+
"""
|
|
786
|
+
service = get_service()
|
|
787
|
+
config_manager = service.get_config_manager()
|
|
788
|
+
if config_name is None:
|
|
789
|
+
config_name = config_manager.get_active_config_name()
|
|
790
|
+
console.print(f"Adding to active configuration: [cyan]{config_name}[/cyan]")
|
|
791
|
+
|
|
792
|
+
configs = config_manager.list_configs()
|
|
793
|
+
if config_name not in configs:
|
|
794
|
+
console.print(f"[red]Error: Configuration '{config_name}' not found.[/red]")
|
|
795
|
+
raise typer.Exit(1)
|
|
796
|
+
|
|
797
|
+
try:
|
|
798
|
+
# Load fresh configuration from file
|
|
799
|
+
configs = config_manager.list_configs()
|
|
800
|
+
config_path = configs[config_name]
|
|
801
|
+
config = ServiceSettings.load_from_file(config_path)
|
|
802
|
+
|
|
803
|
+
added_projects = []
|
|
804
|
+
skipped_projects = []
|
|
805
|
+
invalid_projects = []
|
|
806
|
+
|
|
807
|
+
# Process each project
|
|
808
|
+
for project_name in project_names:
|
|
809
|
+
# Check if project already exists
|
|
810
|
+
if config.has_project(project_name):
|
|
811
|
+
skipped_projects.append(project_name)
|
|
812
|
+
console.print(f"[yellow]⚠ Project '{project_name}' already exists - skipping[/yellow]")
|
|
813
|
+
continue
|
|
814
|
+
|
|
815
|
+
# Add the project from defaults
|
|
816
|
+
try:
|
|
817
|
+
if config.add_project_from_default(project_name):
|
|
818
|
+
added_projects.append(project_name)
|
|
819
|
+
console.print(f"[green]✓ Added project [cyan]{project_name}[/cyan][/green]")
|
|
820
|
+
else:
|
|
821
|
+
invalid_projects.append(project_name)
|
|
822
|
+
console.print(f"[red]✗ Invalid project '{project_name}'[/red]")
|
|
823
|
+
except ValueError as e:
|
|
824
|
+
invalid_projects.append(project_name)
|
|
825
|
+
console.print(f"[red]✗ Invalid project '{project_name}'[/red]")
|
|
826
|
+
|
|
827
|
+
# Show summary of what was processed
|
|
828
|
+
if added_projects:
|
|
829
|
+
console.print(
|
|
830
|
+
f"[green]Successfully added {len(added_projects)} project(s): {', '.join(added_projects)}[/green]"
|
|
831
|
+
)
|
|
832
|
+
if skipped_projects:
|
|
833
|
+
console.print(
|
|
834
|
+
f"[yellow]Skipped {len(skipped_projects)} existing project(s): {', '.join(skipped_projects)}[/yellow]"
|
|
835
|
+
)
|
|
836
|
+
if invalid_projects:
|
|
837
|
+
available = config.get_available_default_projects()
|
|
838
|
+
console.print(f"[red]Invalid project(s): {', '.join(invalid_projects)}[/red]")
|
|
839
|
+
console.print(f"[yellow]Available projects: {', '.join(available)}[/yellow]")
|
|
840
|
+
|
|
841
|
+
# Only proceed if we actually added something
|
|
842
|
+
if added_projects:
|
|
843
|
+
# Save the configuration to the correct file
|
|
844
|
+
if config_name == config_manager.get_active_config_name():
|
|
845
|
+
config_manager.save_active_config(config)
|
|
846
|
+
# Reset the state if we modified the active configuration
|
|
847
|
+
service.current_state = service.get_state()
|
|
848
|
+
else:
|
|
849
|
+
# Save to specific config file
|
|
850
|
+
config_path = configs[config_name]
|
|
851
|
+
config.save_to_file(config_path)
|
|
852
|
+
|
|
853
|
+
# Download the CVs for all added projects
|
|
854
|
+
console.print(f"[blue]Downloading CVs for {len(added_projects)} project(s)...[/blue]")
|
|
855
|
+
service.current_state.synchronize_all()
|
|
856
|
+
console.print(f"[green]✓ Successfully installed CVs for all added projects[/green]")
|
|
857
|
+
elif invalid_projects and not skipped_projects:
|
|
858
|
+
# Exit with error only if we had invalid projects and nothing was skipped
|
|
859
|
+
raise typer.Exit(1)
|
|
860
|
+
|
|
861
|
+
except ValueError as e:
|
|
862
|
+
console.print(f"[red]Error: {str(e)}[/red]")
|
|
863
|
+
raise typer.Exit(1)
|
|
864
|
+
except Exception as e:
|
|
865
|
+
console.print(f"[red]Error adding project: {str(e)}[/red]")
|
|
866
|
+
raise typer.Exit(1)
|
|
867
|
+
|
|
868
|
+
|
|
869
|
+
@app.command()
|
|
870
|
+
def rm(
|
|
871
|
+
project_names: List[str] = typer.Argument(..., help="Names of the projects to remove."),
|
|
872
|
+
config_name: Optional[str] = typer.Option(
|
|
873
|
+
None, "--config", "-c", help="Configuration name. Uses active configuration if not specified."
|
|
874
|
+
),
|
|
875
|
+
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation prompt."),
|
|
876
|
+
keep_files: bool = typer.Option(
|
|
877
|
+
False, "--keep-files", help="Keep local repos and databases (only remove from config)."
|
|
878
|
+
),
|
|
879
|
+
):
|
|
880
|
+
"""
|
|
881
|
+
Remove one or more projects from the configuration and delete their repos/databases.
|
|
882
|
+
|
|
883
|
+
This will:
|
|
884
|
+
1. Remove the projects from the configuration
|
|
885
|
+
2. Delete the local repository directories (unless --keep-files)
|
|
886
|
+
3. Delete the database files (unless --keep-files)
|
|
887
|
+
|
|
888
|
+
Examples:
|
|
889
|
+
esgvoc config rm input4mip
|
|
890
|
+
esgvoc config rm input4mip obs4mip cordex-cmip6
|
|
891
|
+
esgvoc config rm obs4mip --force
|
|
892
|
+
esgvoc config rm cmip6 input4mip --keep-files # Remove from config but keep files
|
|
893
|
+
"""
|
|
894
|
+
service = get_service()
|
|
895
|
+
config_manager = service.get_config_manager()
|
|
896
|
+
if config_name is None:
|
|
897
|
+
config_name = config_manager.get_active_config_name()
|
|
898
|
+
console.print(f"Removing from active configuration: [cyan]{config_name}[/cyan]")
|
|
899
|
+
|
|
900
|
+
configs = config_manager.list_configs()
|
|
901
|
+
if config_name not in configs:
|
|
902
|
+
console.print(f"[red]Error: Configuration '{config_name}' not found.[/red]")
|
|
903
|
+
raise typer.Exit(1)
|
|
904
|
+
|
|
905
|
+
try:
|
|
906
|
+
# Load fresh configuration from file
|
|
907
|
+
configs = config_manager.list_configs()
|
|
908
|
+
config_path = configs[config_name]
|
|
909
|
+
config = ServiceSettings.load_from_file(config_path)
|
|
910
|
+
|
|
911
|
+
# Check which projects exist and collect their details
|
|
912
|
+
valid_projects = []
|
|
913
|
+
invalid_projects = []
|
|
914
|
+
projects_to_remove = {} # project_name -> project_object
|
|
915
|
+
|
|
916
|
+
for project_name in project_names:
|
|
917
|
+
if config.has_project(project_name):
|
|
918
|
+
project = config.get_project(project_name)
|
|
919
|
+
projects_to_remove[project_name] = project
|
|
920
|
+
valid_projects.append(project_name)
|
|
921
|
+
else:
|
|
922
|
+
invalid_projects.append(project_name)
|
|
923
|
+
console.print(f"[red]✗ Project '{project_name}' not found in configuration[/red]")
|
|
924
|
+
|
|
925
|
+
if invalid_projects:
|
|
926
|
+
console.print(f"[red]Invalid project(s): {', '.join(invalid_projects)}[/red]")
|
|
927
|
+
|
|
928
|
+
if not valid_projects:
|
|
929
|
+
console.print("[red]No valid projects to remove.[/red]")
|
|
930
|
+
raise typer.Exit(1)
|
|
931
|
+
|
|
932
|
+
# Show what will be removed and confirm unless forced
|
|
933
|
+
console.print(f"[yellow]Projects to remove: {', '.join(valid_projects)}[/yellow]")
|
|
934
|
+
if not force:
|
|
935
|
+
action_desc = "remove from config only" if keep_files else "remove from config and delete all files"
|
|
936
|
+
project_word = "project" if len(valid_projects) == 1 else "projects"
|
|
937
|
+
confirm = typer.confirm(f"Are you sure you want to {action_desc} for {len(valid_projects)} {project_word}?")
|
|
938
|
+
if not confirm:
|
|
939
|
+
console.print("Operation cancelled.")
|
|
940
|
+
return
|
|
941
|
+
|
|
942
|
+
# Get base directory for file cleanup
|
|
943
|
+
base_dir = config_manager.data_config_dir or str(config_manager.data_dir)
|
|
944
|
+
|
|
945
|
+
removed_projects = []
|
|
946
|
+
# Remove each project
|
|
947
|
+
for project_name in valid_projects:
|
|
948
|
+
project = projects_to_remove[project_name]
|
|
949
|
+
|
|
950
|
+
if config.remove_project(project_name):
|
|
951
|
+
removed_projects.append(project_name)
|
|
952
|
+
console.print(f"[green]✓ Removed [cyan]{project_name}[/cyan] from configuration[/green]")
|
|
953
|
+
|
|
954
|
+
# Clean up filesystem unless --keep-files
|
|
955
|
+
if not keep_files and project:
|
|
956
|
+
# Clean up local repository
|
|
957
|
+
if project.local_path:
|
|
958
|
+
repo_path = Path(base_dir) / project.local_path
|
|
959
|
+
if repo_path.exists():
|
|
960
|
+
shutil.rmtree(repo_path)
|
|
961
|
+
console.print(f"[green] ✓ Deleted repository: {repo_path}[/green]")
|
|
962
|
+
else:
|
|
963
|
+
console.print(f"[yellow] Repository not found: {repo_path}[/yellow]")
|
|
964
|
+
|
|
965
|
+
# Clean up database
|
|
966
|
+
if project.db_path:
|
|
967
|
+
db_path = Path(base_dir) / project.db_path
|
|
968
|
+
if db_path.exists():
|
|
969
|
+
db_path.unlink()
|
|
970
|
+
console.print(f"[green] ✓ Deleted database: {db_path}[/green]")
|
|
971
|
+
else:
|
|
972
|
+
console.print(f"[yellow] Database not found: {db_path}[/yellow]")
|
|
973
|
+
else:
|
|
974
|
+
console.print(f"[red]✗ Failed to remove '{project_name}'[/red]")
|
|
975
|
+
|
|
976
|
+
if removed_projects:
|
|
977
|
+
console.print(
|
|
978
|
+
f"[green]Successfully removed {len(removed_projects)} project(s): {', '.join(removed_projects)}[/green]"
|
|
979
|
+
)
|
|
980
|
+
|
|
981
|
+
# Save the configuration to the correct file
|
|
982
|
+
if config_name == config_manager.get_active_config_name():
|
|
983
|
+
config_manager.save_active_config(config)
|
|
984
|
+
# Reset the state if we modified the active configuration
|
|
985
|
+
service.current_state = service.get_state()
|
|
986
|
+
else:
|
|
987
|
+
# Save to specific config file
|
|
988
|
+
config_path = configs[config_name]
|
|
989
|
+
config.save_to_file(config_path)
|
|
990
|
+
else:
|
|
991
|
+
console.print("[red]No projects were successfully removed.[/red]")
|
|
992
|
+
raise typer.Exit(1)
|
|
993
|
+
|
|
994
|
+
except Exception as e:
|
|
995
|
+
console.print(f"[red]Error removing project: {str(e)}[/red]")
|
|
996
|
+
raise typer.Exit(1)
|
|
997
|
+
|
|
998
|
+
|
|
999
|
+
@app.command()
|
|
1000
|
+
def init(
|
|
1001
|
+
name: str = typer.Argument(..., help="Name for the new empty configuration."),
|
|
1002
|
+
no_switch: bool = typer.Option(
|
|
1003
|
+
False, "--no-switch", help="Don't switch to the new configuration (stays on current)."
|
|
1004
|
+
),
|
|
1005
|
+
):
|
|
1006
|
+
"""
|
|
1007
|
+
Create a new empty configuration with only universe settings (no projects).
|
|
1008
|
+
|
|
1009
|
+
This creates a minimal configuration with just the universe component,
|
|
1010
|
+
allowing you to add projects selectively using 'esgvoc config add'.
|
|
1011
|
+
By default, switches to the new configuration after creation.
|
|
1012
|
+
|
|
1013
|
+
Examples:
|
|
1014
|
+
esgvoc config init minimal
|
|
1015
|
+
esgvoc config init test --no-switch # Create but don't switch
|
|
1016
|
+
"""
|
|
1017
|
+
service = get_service()
|
|
1018
|
+
config_manager = service.get_config_manager()
|
|
1019
|
+
configs = config_manager.list_configs()
|
|
1020
|
+
|
|
1021
|
+
if name in configs:
|
|
1022
|
+
console.print(f"[red]Error: Configuration '{name}' already exists.[/red]")
|
|
1023
|
+
raise typer.Exit(1)
|
|
1024
|
+
|
|
1025
|
+
try:
|
|
1026
|
+
# Create empty configuration with only universe settings
|
|
1027
|
+
default_settings = ServiceSettings._get_default_settings()
|
|
1028
|
+
empty_config_data = {
|
|
1029
|
+
"universe": default_settings["universe"],
|
|
1030
|
+
"projects": [], # No projects - completely empty
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
# Add the new configuration
|
|
1034
|
+
config_manager.add_config(name, empty_config_data)
|
|
1035
|
+
console.print(f"[green]✓ Created empty configuration: [cyan]{name}[/cyan][/green]")
|
|
1036
|
+
|
|
1037
|
+
# Switch to new config by default (unless --no-switch is used)
|
|
1038
|
+
if not no_switch:
|
|
1039
|
+
config_manager.switch_config(name)
|
|
1040
|
+
console.print(f"[green]✓ Switched to configuration: [cyan]{name}[/cyan][/green]")
|
|
1041
|
+
# Reset the state to use the new configuration
|
|
1042
|
+
service.current_state = service.get_state()
|
|
1043
|
+
|
|
1044
|
+
except Exception as e:
|
|
1045
|
+
console.print(f"[red]Error creating configuration: {str(e)}[/red]")
|
|
1046
|
+
raise typer.Exit(1)
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
@app.command()
|
|
1050
|
+
def migrate(
|
|
1051
|
+
config_name: Optional[str] = typer.Option(
|
|
1052
|
+
None, "--config", "-c", help="Configuration name to migrate. Migrates all configs if not specified."
|
|
1053
|
+
),
|
|
1054
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be changed without making changes."),
|
|
1055
|
+
):
|
|
1056
|
+
"""
|
|
1057
|
+
Migrate configuration(s) to convert relative paths to absolute paths.
|
|
1058
|
+
|
|
1059
|
+
This command is needed when upgrading to newer versions that require absolute paths.
|
|
1060
|
+
By default, migrates all configurations. Use --config to migrate only a specific one.
|
|
1061
|
+
|
|
1062
|
+
Examples:
|
|
1063
|
+
esgvoc config migrate # Migrate all configurations
|
|
1064
|
+
esgvoc config migrate --config user_config # Migrate specific configuration
|
|
1065
|
+
esgvoc config migrate --dry-run # Show what would be changed
|
|
1066
|
+
"""
|
|
1067
|
+
import os
|
|
1068
|
+
from pathlib import Path
|
|
1069
|
+
|
|
1070
|
+
# Enable migration mode to allow loading configs with relative paths
|
|
1071
|
+
os.environ['ESGVOC_MIGRATION_MODE'] = '1'
|
|
1072
|
+
|
|
1073
|
+
try:
|
|
1074
|
+
# Use config manager directly to avoid service initialization issues
|
|
1075
|
+
from esgvoc.core.service.configuration.config_manager import ConfigManager
|
|
1076
|
+
config_manager = ConfigManager(ServiceSettings, app_name="esgvoc", app_author="ipsl", default_settings=ServiceSettings._get_default_settings())
|
|
1077
|
+
configs = config_manager.list_configs()
|
|
1078
|
+
|
|
1079
|
+
# Determine which configs to migrate
|
|
1080
|
+
if config_name:
|
|
1081
|
+
if config_name not in configs:
|
|
1082
|
+
console.print(f"[red]Error: Configuration '{config_name}' not found.[/red]")
|
|
1083
|
+
raise typer.Exit(1)
|
|
1084
|
+
configs_to_migrate = {config_name: configs[config_name]}
|
|
1085
|
+
else:
|
|
1086
|
+
configs_to_migrate = configs
|
|
1087
|
+
|
|
1088
|
+
console.print(f"[blue]Migrating {len(configs_to_migrate)} configuration(s)...[/blue]")
|
|
1089
|
+
|
|
1090
|
+
migrated_count = 0
|
|
1091
|
+
for name, config_path in configs_to_migrate.items():
|
|
1092
|
+
console.print(f"\n[cyan]Processing configuration: {name}[/cyan]")
|
|
1093
|
+
|
|
1094
|
+
try:
|
|
1095
|
+
# Load the raw TOML data first to check for relative paths
|
|
1096
|
+
with open(config_path, 'r') as f:
|
|
1097
|
+
raw_data = toml.load(f)
|
|
1098
|
+
|
|
1099
|
+
changes_made = []
|
|
1100
|
+
|
|
1101
|
+
# Check universe paths
|
|
1102
|
+
if 'universe' in raw_data:
|
|
1103
|
+
universe = raw_data['universe']
|
|
1104
|
+
for path_field in ['local_path', 'db_path']:
|
|
1105
|
+
if path_field in universe and universe[path_field]:
|
|
1106
|
+
path_val = universe[path_field]
|
|
1107
|
+
if not Path(path_val).is_absolute():
|
|
1108
|
+
changes_made.append(f"universe.{path_field}: {path_val} -> <absolute>")
|
|
1109
|
+
|
|
1110
|
+
# Check project paths
|
|
1111
|
+
if 'projects' in raw_data:
|
|
1112
|
+
for project in raw_data['projects']:
|
|
1113
|
+
project_name = project.get('project_name', 'unknown')
|
|
1114
|
+
for path_field in ['local_path', 'db_path']:
|
|
1115
|
+
if path_field in project and project[path_field]:
|
|
1116
|
+
path_val = project[path_field]
|
|
1117
|
+
if not Path(path_val).is_absolute():
|
|
1118
|
+
changes_made.append(f"{project_name}.{path_field}: {path_val} -> <absolute>")
|
|
1119
|
+
|
|
1120
|
+
if changes_made:
|
|
1121
|
+
console.print(f"[yellow]Found {len(changes_made)} relative paths to migrate:[/yellow]")
|
|
1122
|
+
for change in changes_made:
|
|
1123
|
+
console.print(f" • {change}")
|
|
1124
|
+
|
|
1125
|
+
if not dry_run:
|
|
1126
|
+
# Load using ServiceSettings which will auto-convert to absolute paths
|
|
1127
|
+
migrated_config = ServiceSettings.load_from_file(config_path)
|
|
1128
|
+
|
|
1129
|
+
# Save back to file (now with absolute paths)
|
|
1130
|
+
migrated_config.save_to_file(config_path)
|
|
1131
|
+
console.print(f"[green]✓ Successfully migrated configuration: {name}[/green]")
|
|
1132
|
+
migrated_count += 1
|
|
1133
|
+
else:
|
|
1134
|
+
console.print(f"[blue] (dry-run: would migrate configuration: {name})[/blue]")
|
|
1135
|
+
migrated_count += 1
|
|
1136
|
+
else:
|
|
1137
|
+
console.print(f"[dim]No relative paths found in {name} - already migrated[/dim]")
|
|
1138
|
+
|
|
1139
|
+
except Exception as e:
|
|
1140
|
+
console.print(f"[red]Error processing {name}: {str(e)}[/red]")
|
|
1141
|
+
continue
|
|
1142
|
+
|
|
1143
|
+
# Summary
|
|
1144
|
+
action = "would be migrated" if dry_run else "migrated"
|
|
1145
|
+
if migrated_count > 0:
|
|
1146
|
+
console.print(f"\n[green]✓ {migrated_count} configuration(s) {action} successfully[/green]")
|
|
1147
|
+
if not dry_run:
|
|
1148
|
+
console.print("[blue]All relative paths have been converted to absolute paths.[/blue]")
|
|
1149
|
+
console.print("[blue]You can now use the configuration system normally.[/blue]")
|
|
1150
|
+
else:
|
|
1151
|
+
console.print(f"\n[blue]No configurations needed migration - all paths are already absolute[/blue]")
|
|
1152
|
+
|
|
1153
|
+
except Exception as e:
|
|
1154
|
+
console.print(f"[red]Error during migration: {str(e)}[/red]")
|
|
1155
|
+
raise typer.Exit(1)
|
|
1156
|
+
finally:
|
|
1157
|
+
# Disable migration mode
|
|
1158
|
+
if 'ESGVOC_MIGRATION_MODE' in os.environ:
|
|
1159
|
+
del os.environ['ESGVOC_MIGRATION_MODE']
|
|
1160
|
+
|
|
1161
|
+
|
|
1162
|
+
@app.command()
|
|
1163
|
+
def offline(
|
|
1164
|
+
enable: Optional[bool] = typer.Option(
|
|
1165
|
+
None, "--enable/--disable", help="Enable or disable offline mode. If not specified, shows current status."
|
|
1166
|
+
),
|
|
1167
|
+
component: Optional[str] = typer.Option(
|
|
1168
|
+
"universe", "--component", "-c", help="Component to modify: 'universe' or project name (default: universe)"
|
|
1169
|
+
),
|
|
1170
|
+
config_name: Optional[str] = typer.Option(
|
|
1171
|
+
None, "--config", help="Configuration name. Uses active configuration if not specified."
|
|
1172
|
+
),
|
|
1173
|
+
):
|
|
1174
|
+
"""
|
|
1175
|
+
Enable, disable, or show offline mode status.
|
|
1176
|
+
|
|
1177
|
+
Examples:
|
|
1178
|
+
esgvoc config offline --enable # Enable offline mode for universe
|
|
1179
|
+
esgvoc config offline --disable # Disable offline mode for universe
|
|
1180
|
+
esgvoc config offline --enable -c cmip6 # Enable offline mode for cmip6 project
|
|
1181
|
+
esgvoc config offline # Show current offline mode status
|
|
1182
|
+
"""
|
|
1183
|
+
service = get_service()
|
|
1184
|
+
config_manager = service.get_config_manager()
|
|
1185
|
+
|
|
1186
|
+
if config_name is None:
|
|
1187
|
+
config_name = config_manager.get_active_config_name()
|
|
1188
|
+
|
|
1189
|
+
configs = config_manager.list_configs()
|
|
1190
|
+
if config_name not in configs:
|
|
1191
|
+
console.print(f"[red]Error: Configuration '{config_name}' not found.[/red]")
|
|
1192
|
+
raise typer.Exit(1)
|
|
1193
|
+
|
|
1194
|
+
try:
|
|
1195
|
+
config = config_manager.get_config(config_name)
|
|
1196
|
+
|
|
1197
|
+
if enable is None:
|
|
1198
|
+
# Show current status
|
|
1199
|
+
if component == "universe":
|
|
1200
|
+
status = "enabled" if config.universe.offline_mode else "disabled"
|
|
1201
|
+
console.print(f"Universe offline mode is [cyan]{status}[/cyan] in configuration '{config_name}'")
|
|
1202
|
+
elif component in config.projects:
|
|
1203
|
+
status = "enabled" if config.projects[component].offline_mode else "disabled"
|
|
1204
|
+
console.print(f"Project '{component}' offline mode is [cyan]{status}[/cyan] in configuration '{config_name}'")
|
|
1205
|
+
else:
|
|
1206
|
+
console.print(f"[red]Error: Component '{component}' not found.[/red]")
|
|
1207
|
+
raise typer.Exit(1)
|
|
1208
|
+
else:
|
|
1209
|
+
# Update offline mode
|
|
1210
|
+
if component == "universe":
|
|
1211
|
+
config.universe.offline_mode = enable
|
|
1212
|
+
status = "enabled" if enable else "disabled"
|
|
1213
|
+
console.print(f"[green]Universe offline mode {status} in configuration '{config_name}'[/green]")
|
|
1214
|
+
elif component in config.projects:
|
|
1215
|
+
config.projects[component].offline_mode = enable
|
|
1216
|
+
status = "enabled" if enable else "disabled"
|
|
1217
|
+
console.print(f"[green]Project '{component}' offline mode {status} in configuration '{config_name}'[/green]")
|
|
1218
|
+
else:
|
|
1219
|
+
console.print(f"[red]Error: Component '{component}' not found.[/red]")
|
|
1220
|
+
raise typer.Exit(1)
|
|
1221
|
+
|
|
1222
|
+
# Save the configuration
|
|
1223
|
+
config_manager.save_active_config(config)
|
|
1224
|
+
|
|
1225
|
+
# Reset the state if we modified the active configuration
|
|
1226
|
+
if config_name == config_manager.get_active_config_name():
|
|
1227
|
+
service.current_state = service.get_state()
|
|
1228
|
+
|
|
1229
|
+
except Exception as e:
|
|
1230
|
+
console.print(f"[red]Error managing offline mode: {str(e)}[/red]")
|
|
1231
|
+
raise typer.Exit(1)
|
|
1232
|
+
|
|
1233
|
+
|
|
1234
|
+
@app.command()
|
|
1235
|
+
def avail(
|
|
1236
|
+
config_name: Optional[str] = typer.Option(
|
|
1237
|
+
None, "--config", "-c", help="Configuration name. Uses active configuration if not specified."
|
|
1238
|
+
),
|
|
1239
|
+
):
|
|
1240
|
+
"""
|
|
1241
|
+
Show a table of all available default projects and their status in the configuration.
|
|
1242
|
+
|
|
1243
|
+
Projects are marked as:
|
|
1244
|
+
- ✓ Active: Project is in the current configuration
|
|
1245
|
+
- ○ Available: Project can be added to the configuration
|
|
1246
|
+
|
|
1247
|
+
Examples:
|
|
1248
|
+
esgvoc config avail
|
|
1249
|
+
esgvoc config avail --config my_config
|
|
1250
|
+
"""
|
|
1251
|
+
service = get_service()
|
|
1252
|
+
config_manager = service.get_config_manager()
|
|
1253
|
+
if config_name is None:
|
|
1254
|
+
config_name = config_manager.get_active_config_name()
|
|
1255
|
+
console.print(f"Showing project availability for: [cyan]{config_name}[/cyan]")
|
|
1256
|
+
|
|
1257
|
+
configs = config_manager.list_configs()
|
|
1258
|
+
if config_name not in configs:
|
|
1259
|
+
console.print(f"[red]Error: Configuration '{config_name}' not found.[/red]")
|
|
1260
|
+
raise typer.Exit(1)
|
|
1261
|
+
|
|
1262
|
+
try:
|
|
1263
|
+
# Load configuration
|
|
1264
|
+
config_path = configs[config_name]
|
|
1265
|
+
config = ServiceSettings.load_from_file(config_path)
|
|
1266
|
+
|
|
1267
|
+
# Get all available default projects
|
|
1268
|
+
available_projects = ServiceSettings._get_default_project_configs()
|
|
1269
|
+
|
|
1270
|
+
table = Table(title=f"Available Projects (Configuration: {config_name})")
|
|
1271
|
+
table.add_column("Status", style="bold")
|
|
1272
|
+
table.add_column("Project Name", style="cyan")
|
|
1273
|
+
table.add_column("Repository", style="green")
|
|
1274
|
+
table.add_column("Branch", style="yellow")
|
|
1275
|
+
|
|
1276
|
+
for project_name, project_config in available_projects.items():
|
|
1277
|
+
# Check if project is in current configuration
|
|
1278
|
+
if config.has_project(project_name):
|
|
1279
|
+
status = "[green]✓ Active[/green]"
|
|
1280
|
+
else:
|
|
1281
|
+
status = "[dim]○ Available[/dim]"
|
|
1282
|
+
|
|
1283
|
+
table.add_row(status, project_name, project_config["github_repo"], project_config["branch"])
|
|
1284
|
+
|
|
1285
|
+
display(table)
|
|
1286
|
+
|
|
1287
|
+
# Show summary
|
|
1288
|
+
active_count = len([p for p in available_projects.keys() if config.has_project(p)])
|
|
1289
|
+
total_count = len(available_projects)
|
|
1290
|
+
console.print(
|
|
1291
|
+
f"\n[blue]Summary: {active_count}/{total_count} projects active in configuration '{config_name}'[/blue]"
|
|
1292
|
+
)
|
|
1293
|
+
|
|
1294
|
+
except Exception as e:
|
|
1295
|
+
console.print(f"[red]Error showing available projects: {str(e)}[/red]")
|
|
1296
|
+
raise typer.Exit(1)
|
|
1297
|
+
|
|
1298
|
+
|
|
1299
|
+
if __name__ == "__main__":
|
|
1300
|
+
app()
|