starbash 0.1.8__py3-none-any.whl → 0.1.10__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.
Potentially problematic release.
This version of starbash might be problematic. Click here for more details.
- repo/__init__.py +2 -1
- repo/manager.py +31 -268
- repo/repo.py +294 -0
- starbash/__init__.py +20 -0
- starbash/aliases.py +100 -0
- starbash/analytics.py +4 -0
- starbash/app.py +740 -151
- starbash/commands/__init__.py +0 -17
- starbash/commands/info.py +72 -3
- starbash/commands/process.py +154 -0
- starbash/commands/repo.py +185 -78
- starbash/commands/select.py +135 -44
- starbash/database.py +397 -155
- starbash/defaults/starbash.toml +35 -0
- starbash/main.py +4 -1
- starbash/paths.py +18 -2
- starbash/recipes/master_bias/starbash.toml +32 -19
- starbash/recipes/master_dark/starbash.toml +36 -0
- starbash/recipes/master_flat/starbash.toml +27 -17
- starbash/recipes/osc_dual_duo/starbash.py +1 -5
- starbash/recipes/osc_dual_duo/starbash.toml +8 -4
- starbash/recipes/osc_single_duo/starbash.toml +4 -4
- starbash/recipes/starbash.toml +28 -3
- starbash/selection.py +115 -46
- starbash/templates/repo/master.toml +13 -0
- starbash/templates/repo/processed.toml +10 -0
- starbash/templates/userconfig.toml +1 -1
- starbash/toml.py +29 -0
- starbash/tool.py +199 -67
- {starbash-0.1.8.dist-info → starbash-0.1.10.dist-info}/METADATA +20 -13
- starbash-0.1.10.dist-info/RECORD +40 -0
- starbash-0.1.8.dist-info/RECORD +0 -33
- {starbash-0.1.8.dist-info → starbash-0.1.10.dist-info}/WHEEL +0 -0
- {starbash-0.1.8.dist-info → starbash-0.1.10.dist-info}/entry_points.txt +0 -0
- {starbash-0.1.8.dist-info → starbash-0.1.10.dist-info}/licenses/LICENSE +0 -0
starbash/commands/__init__.py
CHANGED
|
@@ -20,20 +20,3 @@ def format_duration(seconds: int | float) -> str:
|
|
|
20
20
|
hours = int(seconds // 3600)
|
|
21
21
|
minutes = int((seconds % 3600) // 60)
|
|
22
22
|
return f"{hours}h {minutes}m" if minutes else f"{hours}h"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def to_shortdate(date_iso: str) -> str:
|
|
26
|
-
"""Convert ISO UTC datetime string to local short date string (YYYY-MM-DD).
|
|
27
|
-
|
|
28
|
-
Args:
|
|
29
|
-
date_iso: ISO format datetime string (e.g., "2023-10-15T14:30:00Z")
|
|
30
|
-
|
|
31
|
-
Returns:
|
|
32
|
-
Short date string in YYYY-MM-DD format, or the original string if conversion fails
|
|
33
|
-
"""
|
|
34
|
-
try:
|
|
35
|
-
dt_utc = datetime.fromisoformat(date_iso)
|
|
36
|
-
dt_local = dt_utc.astimezone()
|
|
37
|
-
return dt_local.strftime("%Y-%m-%d")
|
|
38
|
-
except (ValueError, TypeError):
|
|
39
|
-
return date_iso
|
starbash/commands/info.py
CHANGED
|
@@ -27,7 +27,7 @@ def dump_column(sb: Starbash, human_name: str, column_name: str) -> None:
|
|
|
27
27
|
sessions = sb.search_session()
|
|
28
28
|
|
|
29
29
|
# Also do a complete unfiltered search so we can compare for the users
|
|
30
|
-
allsessions = sb.db.search_session()
|
|
30
|
+
allsessions = sb.db.search_session(("", []))
|
|
31
31
|
|
|
32
32
|
column_name = get_column_name(column_name)
|
|
33
33
|
found = [session[column_name] for session in sessions if session[column_name]]
|
|
@@ -38,7 +38,7 @@ def dump_column(sb: Starbash, human_name: str, column_name: str) -> None:
|
|
|
38
38
|
all_counts = Counter(allfound)
|
|
39
39
|
|
|
40
40
|
# Sort by telescope name
|
|
41
|
-
|
|
41
|
+
sorted_list = sorted(found_counts.items())
|
|
42
42
|
|
|
43
43
|
# Create and display table
|
|
44
44
|
table = Table(
|
|
@@ -49,7 +49,7 @@ def dump_column(sb: Starbash, human_name: str, column_name: str) -> None:
|
|
|
49
49
|
"# of sessions", style=TABLE_COLUMN_STYLE, no_wrap=True, justify="right"
|
|
50
50
|
)
|
|
51
51
|
|
|
52
|
-
for i, count in
|
|
52
|
+
for i, count in sorted_list:
|
|
53
53
|
table.add_row(i, str(count))
|
|
54
54
|
|
|
55
55
|
console.print(table)
|
|
@@ -76,6 +76,75 @@ def filter():
|
|
|
76
76
|
dump_column(sb, "Filter", Database.FILTER_KEY)
|
|
77
77
|
|
|
78
78
|
|
|
79
|
+
kind_arg = typer.Argument(
|
|
80
|
+
help="Optional image type to filter by (e.g., BIAS, DARK, FLAT, LIGHT)",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@app.command()
|
|
85
|
+
def master(
|
|
86
|
+
kind: Annotated[
|
|
87
|
+
str | None,
|
|
88
|
+
kind_arg,
|
|
89
|
+
] = None,
|
|
90
|
+
):
|
|
91
|
+
"""List all precalculated master images (darks, biases, flats)."""
|
|
92
|
+
with Starbash("info.master") as sb:
|
|
93
|
+
# Get the master repo
|
|
94
|
+
images = sb.get_master_images(kind)
|
|
95
|
+
|
|
96
|
+
if not images:
|
|
97
|
+
kind_msg = f" of type '{kind}'" if kind else ""
|
|
98
|
+
console.print(f"[yellow]No master images{kind_msg} found.[/yellow]")
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
# Create table to display results
|
|
102
|
+
title = f"Master Images ({len(images)} total)"
|
|
103
|
+
if kind:
|
|
104
|
+
title = f"Master {kind} Images ({len(images)} total)"
|
|
105
|
+
table = Table(title=title)
|
|
106
|
+
table.add_column("Date", style=TABLE_COLUMN_STYLE, no_wrap=True)
|
|
107
|
+
table.add_column("Type", style=TABLE_COLUMN_STYLE, no_wrap=True)
|
|
108
|
+
table.add_column("Filename", style=TABLE_VALUE_STYLE, no_wrap=False)
|
|
109
|
+
|
|
110
|
+
# Sort by date, then by type
|
|
111
|
+
sorted_images = sorted(
|
|
112
|
+
images,
|
|
113
|
+
key=lambda img: (
|
|
114
|
+
img.get(Database.DATE_OBS_KEY) or img.get(Database.DATE_KEY) or "",
|
|
115
|
+
img.get(Database.IMAGETYP_KEY) or "",
|
|
116
|
+
),
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
for image in sorted_images:
|
|
120
|
+
date = (
|
|
121
|
+
image.get(Database.DATE_OBS_KEY)
|
|
122
|
+
or image.get(Database.DATE_KEY)
|
|
123
|
+
or "Unknown"
|
|
124
|
+
)
|
|
125
|
+
# Extract just the date part (YYYY-MM-DD) if it's a full ISO timestamp
|
|
126
|
+
if "T" in date:
|
|
127
|
+
date = date.split("T")[0]
|
|
128
|
+
|
|
129
|
+
kind = image.get(Database.IMAGETYP_KEY) or "Unknown"
|
|
130
|
+
filename = image.get("path") or "Unknown"
|
|
131
|
+
|
|
132
|
+
table.add_row(date, kind, filename)
|
|
133
|
+
|
|
134
|
+
console.print(table)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@app.command(hidden=True)
|
|
138
|
+
def masters(
|
|
139
|
+
kind: Annotated[
|
|
140
|
+
str | None,
|
|
141
|
+
kind_arg,
|
|
142
|
+
] = None,
|
|
143
|
+
):
|
|
144
|
+
"""Alias for 'info master' command."""
|
|
145
|
+
master(kind)
|
|
146
|
+
|
|
147
|
+
|
|
79
148
|
@app.callback(invoke_without_command=True)
|
|
80
149
|
def main_callback(ctx: typer.Context):
|
|
81
150
|
"""Show user preferences location and other app info.
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Processing commands for automated image processing workflows."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing_extensions import Annotated
|
|
6
|
+
|
|
7
|
+
from starbash.app import Starbash, copy_images_to_dir
|
|
8
|
+
from starbash import console
|
|
9
|
+
from starbash.commands.select import selection_by_number
|
|
10
|
+
from starbash.database import SessionRow
|
|
11
|
+
|
|
12
|
+
app = typer.Typer()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@app.command()
|
|
16
|
+
def siril(
|
|
17
|
+
session_num: Annotated[
|
|
18
|
+
int,
|
|
19
|
+
typer.Argument(help="Session number to process (from 'select list' output)"),
|
|
20
|
+
],
|
|
21
|
+
destdir: Annotated[
|
|
22
|
+
str,
|
|
23
|
+
typer.Argument(
|
|
24
|
+
help="Destination directory for Siril directory tree and processing"
|
|
25
|
+
),
|
|
26
|
+
],
|
|
27
|
+
run: Annotated[
|
|
28
|
+
bool,
|
|
29
|
+
typer.Option(
|
|
30
|
+
"--run",
|
|
31
|
+
help="Automatically launch Siril GUI after generating directory tree",
|
|
32
|
+
),
|
|
33
|
+
] = False,
|
|
34
|
+
):
|
|
35
|
+
"""Generate Siril directory tree and optionally run Siril GUI.
|
|
36
|
+
|
|
37
|
+
Creates a properly structured directory tree for Siril processing with
|
|
38
|
+
biases/, darks/, flats/, and lights/ subdirectories populated with the
|
|
39
|
+
session's images (via symlinks when possible).
|
|
40
|
+
|
|
41
|
+
If --run is specified, launches the Siril GUI with the generated directory
|
|
42
|
+
structure loaded and ready for processing.
|
|
43
|
+
"""
|
|
44
|
+
with Starbash("process.siril") as sb:
|
|
45
|
+
console.print(
|
|
46
|
+
f"[yellow]Processing session {session_num} for Siril in {destdir}...[/yellow]"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Determine output directory
|
|
50
|
+
output_dir = Path(destdir)
|
|
51
|
+
|
|
52
|
+
# Get the selected session (convert from 1-based to 0-based index)
|
|
53
|
+
session = selection_by_number(sb, session_num)
|
|
54
|
+
|
|
55
|
+
# Get images for this session
|
|
56
|
+
|
|
57
|
+
def session_to_dir(src_session: SessionRow, subdir_name: str):
|
|
58
|
+
"""Copy the images from the specified session to the subdir"""
|
|
59
|
+
img_dir = output_dir / subdir_name
|
|
60
|
+
img_dir.mkdir(parents=True, exist_ok=True)
|
|
61
|
+
images = sb.get_session_images(src_session)
|
|
62
|
+
copy_images_to_dir(images, img_dir)
|
|
63
|
+
|
|
64
|
+
# FIXME - pull this dirname from preferences
|
|
65
|
+
lights = "lights"
|
|
66
|
+
session_to_dir(session, lights)
|
|
67
|
+
|
|
68
|
+
extras = [
|
|
69
|
+
# FIXME search for BIAS/DARK/FLAT etc... using multiple canonical names
|
|
70
|
+
("BIAS", "biases"),
|
|
71
|
+
("DARK", "darks"),
|
|
72
|
+
("FLAT", "flats"),
|
|
73
|
+
]
|
|
74
|
+
for typ, subdir in extras:
|
|
75
|
+
candidates = sb.guess_sessions(session, typ)
|
|
76
|
+
if not candidates:
|
|
77
|
+
console.print(
|
|
78
|
+
f"[yellow]No candidate sessions found for {typ} calibration frames.[/yellow]"
|
|
79
|
+
)
|
|
80
|
+
else:
|
|
81
|
+
session_to_dir(candidates[0], subdir)
|
|
82
|
+
|
|
83
|
+
# FIXME put an starbash.toml repo file in output_dir (with info about what we picked/why)
|
|
84
|
+
# to allow users to override/reprocess with the same settings.
|
|
85
|
+
# Also FIXME, check for the existence of such a file
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@app.command()
|
|
89
|
+
def auto(
|
|
90
|
+
session_num: Annotated[
|
|
91
|
+
int | None,
|
|
92
|
+
typer.Argument(
|
|
93
|
+
help="Session number to process. If not specified, processes all selected sessions."
|
|
94
|
+
),
|
|
95
|
+
] = None,
|
|
96
|
+
):
|
|
97
|
+
"""Automatic processing with sensible defaults.
|
|
98
|
+
|
|
99
|
+
If session number is specified, processes only that session.
|
|
100
|
+
Otherwise, all currently selected sessions will be processed automatically
|
|
101
|
+
using the configured recipes and default settings.
|
|
102
|
+
|
|
103
|
+
This command handles:
|
|
104
|
+
- Automatic master frame selection (bias, dark, flat)
|
|
105
|
+
- Calibration of light frames
|
|
106
|
+
- Registration and stacking
|
|
107
|
+
- Basic post-processing
|
|
108
|
+
|
|
109
|
+
The output will be saved according to the configured recipes.
|
|
110
|
+
"""
|
|
111
|
+
with Starbash("process.auto") as sb:
|
|
112
|
+
if session_num is not None:
|
|
113
|
+
console.print(f"[yellow]Auto-processing session {session_num}...[/yellow]")
|
|
114
|
+
else:
|
|
115
|
+
console.print("[yellow]Auto-processing all selected sessions...[/yellow]")
|
|
116
|
+
|
|
117
|
+
console.print(
|
|
118
|
+
"[red]Still in development - see https://github.com/geeksville/starbash[/red]"
|
|
119
|
+
)
|
|
120
|
+
sb.run_all_stages()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@app.command()
|
|
124
|
+
def masters():
|
|
125
|
+
"""Generate master flats, darks, and biases from selected raw frames.
|
|
126
|
+
|
|
127
|
+
Analyzes the current selection to find all available calibration frames
|
|
128
|
+
(BIAS, DARK, FLAT) and automatically generates master calibration frames
|
|
129
|
+
using stacking recipes.
|
|
130
|
+
|
|
131
|
+
Generated master frames are stored in the configured masters directory
|
|
132
|
+
and will be automatically used for future processing operations.
|
|
133
|
+
"""
|
|
134
|
+
with Starbash("process.masters") as sb:
|
|
135
|
+
console.print(
|
|
136
|
+
"[yellow]Generating master frames from current selection...[/yellow]"
|
|
137
|
+
)
|
|
138
|
+
console.print(
|
|
139
|
+
"[red]Still in development - see https://github.com/geeksville/starbash[/red]"
|
|
140
|
+
)
|
|
141
|
+
sb.run_master_stages()
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@app.callback(invoke_without_command=True)
|
|
145
|
+
def main_callback(ctx: typer.Context):
|
|
146
|
+
"""Process images using automated workflows.
|
|
147
|
+
|
|
148
|
+
These commands handle calibration, registration, stacking, and
|
|
149
|
+
post-processing of astrophotography sessions.
|
|
150
|
+
"""
|
|
151
|
+
if ctx.invoked_subcommand is None:
|
|
152
|
+
# No command provided, show help
|
|
153
|
+
console.print(ctx.get_help())
|
|
154
|
+
raise typer.Exit()
|
starbash/commands/repo.py
CHANGED
|
@@ -1,99 +1,224 @@
|
|
|
1
1
|
import typer
|
|
2
2
|
from typing_extensions import Annotated
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import logging
|
|
3
5
|
|
|
6
|
+
import starbash
|
|
7
|
+
from repo import repo_suffix, Repo
|
|
4
8
|
from starbash.app import Starbash
|
|
5
|
-
from starbash import console
|
|
9
|
+
from starbash import console, log_filter_level
|
|
10
|
+
from starbash.paths import get_user_documents_dir
|
|
11
|
+
from starbash.toml import toml_from_template
|
|
6
12
|
|
|
7
13
|
app = typer.Typer(invoke_without_command=True)
|
|
8
14
|
|
|
9
15
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
16
|
+
def repo_enumeration(sb: Starbash):
|
|
17
|
+
"""return a dict of int (1 based) to Repo instances"""
|
|
18
|
+
verbose = False # assume not verbose for enum picking
|
|
19
|
+
repos = sb.repo_manager.repos if verbose else sb.repo_manager.regular_repos
|
|
20
|
+
|
|
21
|
+
return {i + 1: repo for i, repo in enumerate(repos)}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def complete_repo_by_num(incomplete: str):
|
|
25
|
+
# We need to use stderr_logging to prevent confusing the bash completion parser
|
|
26
|
+
starbash.log_filter_level = (
|
|
27
|
+
logging.ERROR
|
|
28
|
+
) # avoid showing output while doing completion
|
|
29
|
+
with Starbash("repo.complete.num", stderr_logging=True) as sb:
|
|
30
|
+
for num, repo in repo_enumeration(sb).items():
|
|
31
|
+
if str(num).startswith(incomplete):
|
|
32
|
+
yield (str(num), repo.url)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def complete_repo_by_url(incomplete: str):
|
|
36
|
+
# We need to use stderr_logging to prevent confusing the bash completion parser
|
|
37
|
+
starbash.log_filter_level = (
|
|
38
|
+
logging.ERROR
|
|
39
|
+
) # avoid showing output while doing completion
|
|
40
|
+
with Starbash("repo.complete.url", stderr_logging=True) as sb:
|
|
41
|
+
repos = sb.repo_manager.regular_repos
|
|
42
|
+
|
|
43
|
+
for repo in repos:
|
|
44
|
+
if repo.url.startswith(incomplete):
|
|
45
|
+
yield (repo.url, f"kind={repo.kind('input')}")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@app.command()
|
|
49
|
+
def list(
|
|
13
50
|
verbose: bool = typer.Option(
|
|
14
51
|
False, "--verbose", "-v", help="Show all repos including system repos"
|
|
15
52
|
),
|
|
53
|
+
):
|
|
54
|
+
"""
|
|
55
|
+
lists all repositories.
|
|
56
|
+
Use --verbose to show all repos including system/recipe repos.
|
|
57
|
+
"""
|
|
58
|
+
with Starbash("repo.list") as sb:
|
|
59
|
+
repos = sb.repo_manager.repos if verbose else sb.repo_manager.regular_repos
|
|
60
|
+
for i, repo in enumerate(repos):
|
|
61
|
+
kind = repo.kind("input")
|
|
62
|
+
# for unknown repos (probably because we haven't written a starbash.toml file to the root yet),
|
|
63
|
+
# we call them "input" because users will be less confused by that
|
|
64
|
+
|
|
65
|
+
if verbose:
|
|
66
|
+
# No numbers for verbose mode (system repos can't be removed)
|
|
67
|
+
console.print(f"{ repo.url } (kind={ kind })")
|
|
68
|
+
else:
|
|
69
|
+
# Show numbers for user repos (can be removed later)
|
|
70
|
+
console.print(f"{ i + 1:2}: { repo.url } (kind={ kind })")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@app.callback()
|
|
74
|
+
def main(
|
|
75
|
+
ctx: typer.Context,
|
|
16
76
|
):
|
|
17
77
|
"""
|
|
18
78
|
Manage repositories.
|
|
19
79
|
|
|
20
80
|
When called without a subcommand, lists all repositories.
|
|
21
|
-
Use --verbose to show all repos including system/recipe repos.
|
|
22
81
|
"""
|
|
23
82
|
# If no subcommand is invoked, run the list behavior
|
|
24
83
|
if ctx.invoked_subcommand is None:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
kind = repo.kind("input")
|
|
29
|
-
# for unknown repos (probably because we haven't written a starbash.toml file to the root yet),
|
|
30
|
-
# we call them "input" because users will be less confused by that
|
|
31
|
-
|
|
32
|
-
if verbose:
|
|
33
|
-
# No numbers for verbose mode (system repos can't be removed)
|
|
34
|
-
console.print(f"{ repo.url } (kind={ kind })")
|
|
35
|
-
else:
|
|
36
|
-
# Show numbers for user repos (can be removed later)
|
|
37
|
-
console.print(f"{ i + 1:2}: { repo.url } (kind={ kind })")
|
|
84
|
+
# No command provided, show help
|
|
85
|
+
console.print(ctx.get_help())
|
|
86
|
+
raise typer.Exit()
|
|
38
87
|
|
|
39
88
|
|
|
40
89
|
@app.command()
|
|
41
|
-
def add(
|
|
90
|
+
def add(
|
|
91
|
+
path: Annotated[str | None, typer.Argument(help="Path to the respository")] = None,
|
|
92
|
+
master: bool = typer.Option(
|
|
93
|
+
False, "--master", help="Mark this new repository for master files."
|
|
94
|
+
),
|
|
95
|
+
processed: bool = typer.Option(
|
|
96
|
+
False,
|
|
97
|
+
"--processed",
|
|
98
|
+
help="Mark this new repository for processed output files.",
|
|
99
|
+
),
|
|
100
|
+
):
|
|
42
101
|
"""
|
|
43
102
|
Add a repository. path is either a local path or a remote URL.
|
|
44
103
|
"""
|
|
104
|
+
repo_type = None
|
|
105
|
+
if master:
|
|
106
|
+
repo_type = "master"
|
|
107
|
+
elif processed:
|
|
108
|
+
repo_type = "processed"
|
|
109
|
+
|
|
110
|
+
if path is None:
|
|
111
|
+
if repo_type is not None:
|
|
112
|
+
# if we know the repo type we can auto create a default path
|
|
113
|
+
path = str(get_user_documents_dir() / "repos" / repo_type)
|
|
114
|
+
else:
|
|
115
|
+
console.print("[red]Error: path is required for input repositories[/red]")
|
|
116
|
+
raise typer.Exit(1)
|
|
117
|
+
|
|
45
118
|
with Starbash("repo.add") as sb:
|
|
46
|
-
|
|
119
|
+
p = Path(path)
|
|
120
|
+
|
|
121
|
+
repo_toml = p / repo_suffix # the starbash.toml file at the root of the repo
|
|
122
|
+
if repo_toml.exists():
|
|
123
|
+
logging.warning("Using existing repository config file: %s", repo_toml)
|
|
124
|
+
else:
|
|
125
|
+
if repo_type:
|
|
126
|
+
console.print(f"Creating {repo_type} repository: {p}")
|
|
127
|
+
p.mkdir(parents=True, exist_ok=True)
|
|
128
|
+
|
|
129
|
+
toml_from_template(
|
|
130
|
+
f"repo/{repo_type}",
|
|
131
|
+
p / repo_suffix,
|
|
132
|
+
overrides={
|
|
133
|
+
"REPO_TYPE": repo_type,
|
|
134
|
+
"REPO_PATH": str(p),
|
|
135
|
+
"DEFAULT_RELATIVE": "{instrument}/{date}/{imagetyp}/master_{session_config}.fit",
|
|
136
|
+
},
|
|
137
|
+
)
|
|
138
|
+
else:
|
|
139
|
+
# No type specified, therefore (for now) assume we are just using this as an input
|
|
140
|
+
# repo (and it must exist)
|
|
141
|
+
if not p.exists():
|
|
142
|
+
console.print(f"[red]Error: Repo path does not exist: {p}[/red]")
|
|
143
|
+
raise typer.Exit(code=1)
|
|
144
|
+
|
|
145
|
+
console.print(f"Adding repository: {p}")
|
|
146
|
+
|
|
147
|
+
repo = sb.user_repo.add_repo_ref(p)
|
|
47
148
|
if repo:
|
|
48
|
-
console.print(f"Added repository: {path}")
|
|
49
149
|
sb.reindex_repo(repo)
|
|
50
150
|
|
|
51
|
-
# we don't yet write default config files at roots of repos, but it would be easy to add here
|
|
151
|
+
# we don't yet always write default config files at roots of repos, but it would be easy to add here
|
|
52
152
|
# r.write_config()
|
|
53
153
|
sb.user_repo.write_config()
|
|
54
|
-
# FIXME, we also need to index the newly added repo!!!
|
|
55
154
|
|
|
56
155
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
Use 'starbash repo' to see the repository numbers.
|
|
62
|
-
"""
|
|
63
|
-
with Starbash("repo.remove") as sb:
|
|
64
|
-
try:
|
|
65
|
-
# Parse the repo number (1-indexed)
|
|
66
|
-
repo_index = int(reponum) - 1
|
|
156
|
+
def repo_url_to_repo(sb: Starbash, repo_url: str | None) -> Repo | None:
|
|
157
|
+
"""Helper to get a Repo instance from a URL or number"""
|
|
158
|
+
if repo_url is None:
|
|
159
|
+
return None
|
|
67
160
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
console.print(
|
|
73
|
-
f"[red]Error: Repository number {reponum} is out of range. Valid range: 1-{len(regular_repos)}[/red]"
|
|
74
|
-
)
|
|
75
|
-
raise typer.Exit(code=1)
|
|
161
|
+
# try to find by URL
|
|
162
|
+
repo = sb.repo_manager.get_repo_by_url(repo_url)
|
|
163
|
+
if repo is not None:
|
|
164
|
+
return repo
|
|
76
165
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
166
|
+
# Fall back to finding by number
|
|
167
|
+
try:
|
|
168
|
+
# Parse the repo number (1-indexed)
|
|
169
|
+
repo_index = int(repo_url) - 1
|
|
80
170
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
console.print(f"[green]Removed repository: {repo_url}[/green]")
|
|
171
|
+
# Get only the regular (user-visible) repos
|
|
172
|
+
regular_repos = sb.repo_manager.regular_repos
|
|
84
173
|
|
|
85
|
-
|
|
174
|
+
if repo_index < 0 or repo_index >= len(regular_repos):
|
|
86
175
|
console.print(
|
|
87
|
-
f"[red]Error: '{
|
|
176
|
+
f"[red]Error: '{repo_url}' is not a valid repository number. Please enter a repository number or URL.[/red]"
|
|
88
177
|
)
|
|
89
178
|
raise typer.Exit(code=1)
|
|
90
179
|
|
|
180
|
+
return regular_repos[repo_index]
|
|
181
|
+
except ValueError:
|
|
182
|
+
console.print(
|
|
183
|
+
f"[red]Error: '{repo_url}' is not valid. Please enter a repository number or URL.[/red]"
|
|
184
|
+
)
|
|
185
|
+
raise typer.Exit(code=1)
|
|
186
|
+
|
|
91
187
|
|
|
92
188
|
@app.command()
|
|
93
|
-
def
|
|
189
|
+
def remove(
|
|
94
190
|
reponum: Annotated[
|
|
191
|
+
str,
|
|
192
|
+
typer.Argument(
|
|
193
|
+
help="Repository number or URL", autocompletion=complete_repo_by_url
|
|
194
|
+
),
|
|
195
|
+
],
|
|
196
|
+
):
|
|
197
|
+
"""
|
|
198
|
+
Remove a repository by number (from list).
|
|
199
|
+
Use 'starbash repo' to see the repository numbers.
|
|
200
|
+
"""
|
|
201
|
+
with Starbash("repo.remove") as sb:
|
|
202
|
+
# Get the repo to remove
|
|
203
|
+
repo_to_remove = repo_url_to_repo(sb, reponum)
|
|
204
|
+
if repo_to_remove is None:
|
|
205
|
+
console.print(f"[red]Error: You must specify a repository[/red]")
|
|
206
|
+
raise typer.Exit(code=1)
|
|
207
|
+
repo_url = repo_to_remove.url
|
|
208
|
+
|
|
209
|
+
# Remove the repo reference from user config
|
|
210
|
+
sb.remove_repo_ref(repo_url)
|
|
211
|
+
console.print(f"[green]Removed repository: {repo_url}[/green]")
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@app.command()
|
|
215
|
+
def reindex(
|
|
216
|
+
repo_url: Annotated[
|
|
95
217
|
str | None,
|
|
96
|
-
typer.Argument(
|
|
218
|
+
typer.Argument(
|
|
219
|
+
help="The repository URL, if not specified reindex all.",
|
|
220
|
+
autocompletion=complete_repo_by_url,
|
|
221
|
+
),
|
|
97
222
|
] = None,
|
|
98
223
|
force: bool = typer.Option(
|
|
99
224
|
default=False, help="Reread FITS headers, even if they are already indexed."
|
|
@@ -105,35 +230,17 @@ def reindex(
|
|
|
105
230
|
Use 'starbash repo' to see the repository numbers.
|
|
106
231
|
"""
|
|
107
232
|
with Starbash("repo.reindex") as sb:
|
|
108
|
-
|
|
233
|
+
repo_to_reindex = repo_url_to_repo(sb, repo_url)
|
|
234
|
+
|
|
235
|
+
if repo_to_reindex is None:
|
|
109
236
|
sb.reindex_repos(force=force)
|
|
110
237
|
else:
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if repo_index < 0 or repo_index >= len(regular_repos):
|
|
119
|
-
console.print(
|
|
120
|
-
f"[red]Error: Repository number {reponum} is out of range. Valid range: 1-{len(regular_repos)}[/red]"
|
|
121
|
-
)
|
|
122
|
-
raise typer.Exit(code=1)
|
|
123
|
-
|
|
124
|
-
# Get the repo to reindex
|
|
125
|
-
repo_to_reindex = regular_repos[repo_index]
|
|
126
|
-
console.print(f"Reindexing repository: {repo_to_reindex.url}")
|
|
127
|
-
sb.reindex_repo(repo_to_reindex, force=force)
|
|
128
|
-
console.print(
|
|
129
|
-
f"[green]Successfully reindexed repository {reponum}[/green]"
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
except ValueError:
|
|
133
|
-
console.print(
|
|
134
|
-
f"[red]Error: '{reponum}' is not a valid repository number. Please use a number from 'starbash repo'.[/red]"
|
|
135
|
-
)
|
|
136
|
-
raise typer.Exit(code=1)
|
|
238
|
+
# Get the repo to reindex
|
|
239
|
+
console.print(f"Reindexing repository: {repo_to_reindex.url}")
|
|
240
|
+
sb.reindex_repo(repo_to_reindex, force=force)
|
|
241
|
+
console.print(
|
|
242
|
+
f"[green]Successfully reindexed repository {repo_to_reindex}[/green]"
|
|
243
|
+
)
|
|
137
244
|
|
|
138
245
|
|
|
139
246
|
if __name__ == "__main__":
|