starbash 0.1.8__tar.gz → 0.1.10__tar.gz
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.
- {starbash-0.1.8 → starbash-0.1.10}/PKG-INFO +20 -13
- {starbash-0.1.8 → starbash-0.1.10}/README.md +20 -13
- {starbash-0.1.8 → starbash-0.1.10}/pyproject.toml +1 -1
- {starbash-0.1.8 → starbash-0.1.10}/src/repo/__init__.py +2 -1
- starbash-0.1.10/src/repo/manager.py +144 -0
- starbash-0.1.8/src/repo/manager.py → starbash-0.1.10/src/repo/repo.py +29 -116
- {starbash-0.1.8 → starbash-0.1.10}/src/starbash/__init__.py +20 -0
- starbash-0.1.10/src/starbash/aliases.py +100 -0
- {starbash-0.1.8 → starbash-0.1.10}/src/starbash/analytics.py +4 -0
- starbash-0.1.10/src/starbash/app.py +1053 -0
- {starbash-0.1.8 → starbash-0.1.10}/src/starbash/commands/__init__.py +0 -17
- {starbash-0.1.8 → starbash-0.1.10}/src/starbash/commands/info.py +72 -3
- starbash-0.1.10/src/starbash/commands/process.py +154 -0
- starbash-0.1.10/src/starbash/commands/repo.py +247 -0
- {starbash-0.1.8 → starbash-0.1.10}/src/starbash/commands/select.py +135 -44
- starbash-0.1.10/src/starbash/database.py +759 -0
- {starbash-0.1.8 → starbash-0.1.10}/src/starbash/defaults/starbash.toml +35 -0
- {starbash-0.1.8 → starbash-0.1.10}/src/starbash/main.py +4 -1
- {starbash-0.1.8 → starbash-0.1.10}/src/starbash/paths.py +18 -2
- starbash-0.1.10/src/starbash/recipes/master_bias/starbash.toml +68 -0
- starbash-0.1.10/src/starbash/recipes/master_dark/starbash.toml +36 -0
- starbash-0.1.10/src/starbash/recipes/master_flat/starbash.toml +56 -0
- {starbash-0.1.8 → starbash-0.1.10}/src/starbash/recipes/osc_dual_duo/starbash.py +1 -5
- {starbash-0.1.8 → starbash-0.1.10}/src/starbash/recipes/osc_dual_duo/starbash.toml +8 -4
- {starbash-0.1.8 → starbash-0.1.10}/src/starbash/recipes/osc_single_duo/starbash.toml +4 -4
- {starbash-0.1.8 → starbash-0.1.10}/src/starbash/recipes/starbash.toml +28 -3
- {starbash-0.1.8 → starbash-0.1.10}/src/starbash/selection.py +115 -46
- starbash-0.1.10/src/starbash/templates/repo/master.toml +13 -0
- starbash-0.1.10/src/starbash/templates/repo/processed.toml +10 -0
- {starbash-0.1.8 → starbash-0.1.10}/src/starbash/templates/userconfig.toml +1 -1
- starbash-0.1.10/src/starbash/toml.py +29 -0
- starbash-0.1.10/src/starbash/tool.py +392 -0
- starbash-0.1.8/src/starbash/app.py +0 -464
- starbash-0.1.8/src/starbash/commands/repo.py +0 -140
- starbash-0.1.8/src/starbash/database.py +0 -517
- starbash-0.1.8/src/starbash/recipes/master_bias/starbash.toml +0 -55
- starbash-0.1.8/src/starbash/recipes/master_flat/starbash.toml +0 -46
- starbash-0.1.8/src/starbash/tool.py +0 -260
- {starbash-0.1.8 → starbash-0.1.10}/LICENSE +0 -0
- {starbash-0.1.8 → starbash-0.1.10}/src/starbash/commands/user.py +0 -0
- {starbash-0.1.8 → starbash-0.1.10}/src/starbash/defaults/__init__.py +0 -0
- {starbash-0.1.8 → starbash-0.1.10}/src/starbash/recipes/README.md +0 -0
- {starbash-0.1.8 → starbash-0.1.10}/src/starbash/recipes/__init__.py +0 -0
- {starbash-0.1.8 → starbash-0.1.10}/src/starbash/templates/__init__.py +0 -0
- {starbash-0.1.8 → starbash-0.1.10}/src/starbash/url.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: starbash
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.10
|
|
4
4
|
Summary: A tool for automating/standardizing/sharing astrophotography workflows.
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Author: Kevin Hester
|
|
@@ -20,7 +20,7 @@ Requires-Dist: tomlkit (>=0.13.3,<0.14.0)
|
|
|
20
20
|
Requires-Dist: typer (>=0.20.0,<0.21.0)
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
|
|
23
|
-
# Starbash
|
|
23
|
+
# [Starbash](https://github.com/geeksville/starbash)
|
|
24
24
|
|
|
25
25
|
<img src="https://raw.githubusercontent.com/geeksville/starbash/refs/heads/main/img/icon.png" alt="Starbash: Astrophotography workflows simplified" width="30%" align="right" style="margin-bottom: 20px;">
|
|
26
26
|
|
|
@@ -33,6 +33,7 @@ A tool for automating/standardizing/sharing astrophotography workflows.
|
|
|
33
33
|
* Automatic - with sensible defaults, that you can change as needed.
|
|
34
34
|
* Easy - provides a 'seestar like' starting-point for autoprocessing all your sessions (by default).
|
|
35
35
|
* Fast - even with large image repositories. Automatic master bias and flat generation and reasonable defaults
|
|
36
|
+
* Multi-session - by default. So your workflows can stack from multiple nights (and still use the correct flats etc...)
|
|
36
37
|
* Sharable - you can share/use recipes for image preprocessing flows.
|
|
37
38
|
|
|
38
39
|
(This project is currently 'alpha' and missing recipes for some workflows, but adding new recipes is easy and we're happy to help. Please file a github issue if your images are not auto-processed and we'll work out a fix.)
|
|
@@ -67,7 +68,7 @@ See the current [TODO](TODO.md) file for work items. I'll be looking for pre-al
|
|
|
67
68
|
|
|
68
69
|
Currently the easiest way to install this command-line based tool is to install is via [pipx](https://pipx.pypa.io/stable/). If you don't already have pipx and you have python installed, you can auto install it by running "pip install --user pipx." If you don't have python installed see the pipx link for pipx installers for any OS.
|
|
69
70
|
|
|
70
|
-
Once pipx is installed just run the following **two** commands (the sb --install-completion will make TAB auto-complete automatically complete sb options (for most platforms)):
|
|
71
|
+
Once pipx is installed just run the following **two** commands (the sb --install-completion will make TAB auto-complete automatically complete sb options (for most platforms)). Installing auto-complete is **highly** recommended because it makes entering starbash commands fast - by pressing the TAB key:
|
|
71
72
|
|
|
72
73
|
```
|
|
73
74
|
➜ pipx install starbash
|
|
@@ -88,10 +89,10 @@ FIXME - add getting started instructions (possibly with a screenshare video)
|
|
|
88
89
|
## Supported commands
|
|
89
90
|
|
|
90
91
|
### Repository Management
|
|
91
|
-
- `sb repo [--verbose]` - List installed repos (use `-v` for details)
|
|
92
|
-
- `sb repo add
|
|
93
|
-
- `sb repo remove <
|
|
94
|
-
- `sb repo reindex [--force]
|
|
92
|
+
- `sb repo list [--verbose]` - List installed repos (use `-v` for details)
|
|
93
|
+
- `sb repo add [--master] [filepath|URL]` - Add a repository, optionally specifying the type
|
|
94
|
+
- `sb repo remove <REPOURL>` - Remove the indicated repo from the repo list
|
|
95
|
+
- `sb repo reindex [--force] <REPOURL>` - Reindex the specified repo (or all repos if none specified)
|
|
95
96
|
|
|
96
97
|
### User Preferences
|
|
97
98
|
- `sb user name "Your Name"` - Set name for attribution in generated images
|
|
@@ -108,18 +109,19 @@ FIXME - add getting started instructions (possibly with a screenshare video)
|
|
|
108
109
|
- `sb select date <after|before|between> <DATE> [DATE]` - Limit to sessions in the specified date range
|
|
109
110
|
- `sb select export SESSIONNUM DESTDIR` - Export the images for indicated session number into the specified directory (or current directory if not specified). If possible symbolic links are used, if not the files are copied.
|
|
110
111
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
### Setup & Configuration
|
|
112
|
+
### Selection information
|
|
114
113
|
- `sb info` - Show user preferences location and other app info
|
|
115
114
|
- `sb info target` - List targets (filtered based on the current selection)
|
|
116
115
|
- `sb info telescope` - List instruments (filtered based on the current selection)
|
|
117
116
|
- `sb info filter` - List all filters found in current selection
|
|
117
|
+
- `sb info master [KIND]` - List all precalculated master images (darks, biases, flats). Optional KIND argument to filter by image type (e.g., BIAS, DARK, FLAT)
|
|
118
|
+
|
|
119
|
+
## Not yet supported commands
|
|
118
120
|
|
|
119
121
|
### Export & Processing
|
|
120
|
-
- `sb process siril` - Generate Siril directory tree and run Siril GUI
|
|
121
|
-
- `sb process auto` - Automatic processing
|
|
122
|
-
- `sb process masters` - Generate master flats, darks, and biases from available raw frames
|
|
122
|
+
- `sb process siril [--run] SESSIONNUM DESTDIR` - Generate Siril directory tree and optionally run Siril GUI
|
|
123
|
+
- `sb process auto [SESSIONNUM]` - Automatic processing. If session # is specified process only that session, otherwise all selected sessions will be processed
|
|
124
|
+
- `sb process masters` - Generate master flats, darks, and biases from available raw frames in the current selection
|
|
123
125
|
|
|
124
126
|
## Supported tools (now)
|
|
125
127
|
|
|
@@ -138,3 +140,8 @@ We try to make this project useful and friendly. If you find problems please fi
|
|
|
138
140
|
We accept pull-requests and enjoy discussing possible new development directions via github issues. If you might want to work on this, just describe what your interests are and we can talk about how to get it merged.
|
|
139
141
|
|
|
140
142
|
Project members can access crash reports [here](https://geeksville.sentry.io/insights/projects/starbash/?project=4510264204132352).
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
Copyright 2025 Kevin Hester, kevinh@geeksville.com.
|
|
147
|
+
Licensed under the [GPL v3](LICENSE)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Starbash
|
|
1
|
+
# [Starbash](https://github.com/geeksville/starbash)
|
|
2
2
|
|
|
3
3
|
<img src="https://raw.githubusercontent.com/geeksville/starbash/refs/heads/main/img/icon.png" alt="Starbash: Astrophotography workflows simplified" width="30%" align="right" style="margin-bottom: 20px;">
|
|
4
4
|
|
|
@@ -11,6 +11,7 @@ A tool for automating/standardizing/sharing astrophotography workflows.
|
|
|
11
11
|
* Automatic - with sensible defaults, that you can change as needed.
|
|
12
12
|
* Easy - provides a 'seestar like' starting-point for autoprocessing all your sessions (by default).
|
|
13
13
|
* Fast - even with large image repositories. Automatic master bias and flat generation and reasonable defaults
|
|
14
|
+
* Multi-session - by default. So your workflows can stack from multiple nights (and still use the correct flats etc...)
|
|
14
15
|
* Sharable - you can share/use recipes for image preprocessing flows.
|
|
15
16
|
|
|
16
17
|
(This project is currently 'alpha' and missing recipes for some workflows, but adding new recipes is easy and we're happy to help. Please file a github issue if your images are not auto-processed and we'll work out a fix.)
|
|
@@ -45,7 +46,7 @@ See the current [TODO](TODO.md) file for work items. I'll be looking for pre-al
|
|
|
45
46
|
|
|
46
47
|
Currently the easiest way to install this command-line based tool is to install is via [pipx](https://pipx.pypa.io/stable/). If you don't already have pipx and you have python installed, you can auto install it by running "pip install --user pipx." If you don't have python installed see the pipx link for pipx installers for any OS.
|
|
47
48
|
|
|
48
|
-
Once pipx is installed just run the following **two** commands (the sb --install-completion will make TAB auto-complete automatically complete sb options (for most platforms)):
|
|
49
|
+
Once pipx is installed just run the following **two** commands (the sb --install-completion will make TAB auto-complete automatically complete sb options (for most platforms)). Installing auto-complete is **highly** recommended because it makes entering starbash commands fast - by pressing the TAB key:
|
|
49
50
|
|
|
50
51
|
```
|
|
51
52
|
➜ pipx install starbash
|
|
@@ -66,10 +67,10 @@ FIXME - add getting started instructions (possibly with a screenshare video)
|
|
|
66
67
|
## Supported commands
|
|
67
68
|
|
|
68
69
|
### Repository Management
|
|
69
|
-
- `sb repo [--verbose]` - List installed repos (use `-v` for details)
|
|
70
|
-
- `sb repo add
|
|
71
|
-
- `sb repo remove <
|
|
72
|
-
- `sb repo reindex [--force]
|
|
70
|
+
- `sb repo list [--verbose]` - List installed repos (use `-v` for details)
|
|
71
|
+
- `sb repo add [--master] [filepath|URL]` - Add a repository, optionally specifying the type
|
|
72
|
+
- `sb repo remove <REPOURL>` - Remove the indicated repo from the repo list
|
|
73
|
+
- `sb repo reindex [--force] <REPOURL>` - Reindex the specified repo (or all repos if none specified)
|
|
73
74
|
|
|
74
75
|
### User Preferences
|
|
75
76
|
- `sb user name "Your Name"` - Set name for attribution in generated images
|
|
@@ -86,18 +87,19 @@ FIXME - add getting started instructions (possibly with a screenshare video)
|
|
|
86
87
|
- `sb select date <after|before|between> <DATE> [DATE]` - Limit to sessions in the specified date range
|
|
87
88
|
- `sb select export SESSIONNUM DESTDIR` - Export the images for indicated session number into the specified directory (or current directory if not specified). If possible symbolic links are used, if not the files are copied.
|
|
88
89
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
### Setup & Configuration
|
|
90
|
+
### Selection information
|
|
92
91
|
- `sb info` - Show user preferences location and other app info
|
|
93
92
|
- `sb info target` - List targets (filtered based on the current selection)
|
|
94
93
|
- `sb info telescope` - List instruments (filtered based on the current selection)
|
|
95
94
|
- `sb info filter` - List all filters found in current selection
|
|
95
|
+
- `sb info master [KIND]` - List all precalculated master images (darks, biases, flats). Optional KIND argument to filter by image type (e.g., BIAS, DARK, FLAT)
|
|
96
|
+
|
|
97
|
+
## Not yet supported commands
|
|
96
98
|
|
|
97
99
|
### Export & Processing
|
|
98
|
-
- `sb process siril` - Generate Siril directory tree and run Siril GUI
|
|
99
|
-
- `sb process auto` - Automatic processing
|
|
100
|
-
- `sb process masters` - Generate master flats, darks, and biases from available raw frames
|
|
100
|
+
- `sb process siril [--run] SESSIONNUM DESTDIR` - Generate Siril directory tree and optionally run Siril GUI
|
|
101
|
+
- `sb process auto [SESSIONNUM]` - Automatic processing. If session # is specified process only that session, otherwise all selected sessions will be processed
|
|
102
|
+
- `sb process masters` - Generate master flats, darks, and biases from available raw frames in the current selection
|
|
101
103
|
|
|
102
104
|
## Supported tools (now)
|
|
103
105
|
|
|
@@ -115,4 +117,9 @@ FIXME - add getting started instructions (possibly with a screenshare video)
|
|
|
115
117
|
We try to make this project useful and friendly. If you find problems please file a github issue.
|
|
116
118
|
We accept pull-requests and enjoy discussing possible new development directions via github issues. If you might want to work on this, just describe what your interests are and we can talk about how to get it merged.
|
|
117
119
|
|
|
118
|
-
Project members can access crash reports [here](https://geeksville.sentry.io/insights/projects/starbash/?project=4510264204132352).
|
|
120
|
+
Project members can access crash reports [here](https://geeksville.sentry.io/insights/projects/starbash/?project=4510264204132352).
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
Copyright 2025 Kevin Hester, kevinh@geeksville.com.
|
|
125
|
+
Licensed under the [GPL v3](LICENSE)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Manages the repository of processing recipes and configurations.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
import logging
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from importlib import resources
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import tomlkit
|
|
12
|
+
from tomlkit.toml_file import TOMLFile
|
|
13
|
+
from tomlkit.items import AoT
|
|
14
|
+
from multidict import MultiDict
|
|
15
|
+
from repo.repo import Repo
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RepoManager:
|
|
19
|
+
"""
|
|
20
|
+
Manages the collection of starbash repositories.
|
|
21
|
+
|
|
22
|
+
This class is responsible for finding, loading, and providing an API
|
|
23
|
+
for searching through known repositories defined in TOML configuration
|
|
24
|
+
files (like appdefaults.sb.toml).
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
"""
|
|
29
|
+
Initializes the RepoManager by loading the application default repos.
|
|
30
|
+
"""
|
|
31
|
+
self.repos = []
|
|
32
|
+
|
|
33
|
+
# We expose the app default preferences as a special root repo with a private URL
|
|
34
|
+
# root_repo = Repo(self, "pkg://starbash-defaults", config=app_defaults)
|
|
35
|
+
# self.repos.append(root_repo)
|
|
36
|
+
|
|
37
|
+
# Most users will just want to read from merged
|
|
38
|
+
self.merged = MultiDict()
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def regular_repos(self) -> list[Repo]:
|
|
42
|
+
"We exclude certain repo types (preferences, recipe) from the list of repos users care about."
|
|
43
|
+
return [
|
|
44
|
+
r
|
|
45
|
+
for r in self.repos
|
|
46
|
+
if r.kind() not in ("preferences") and not r.is_scheme("pkg")
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
def add_repo(self, url: str) -> Repo:
|
|
50
|
+
logging.debug(f"Adding repo: {url}")
|
|
51
|
+
r = Repo(self, url)
|
|
52
|
+
self.repos.append(r)
|
|
53
|
+
|
|
54
|
+
# FIXME, generate the merged dict lazily
|
|
55
|
+
self._add_merged(r)
|
|
56
|
+
|
|
57
|
+
# if this new repo has sub-repos, add them too
|
|
58
|
+
r.add_by_repo_refs()
|
|
59
|
+
|
|
60
|
+
return r
|
|
61
|
+
|
|
62
|
+
def get_repo_by_url(self, url: str) -> Repo | None:
|
|
63
|
+
"""
|
|
64
|
+
Retrieves a repository by its URL.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
url: The URL of the repository to retrieve.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
The Repo instance with the matching URL, or None if not found.
|
|
71
|
+
"""
|
|
72
|
+
for repo in self.repos:
|
|
73
|
+
if repo.url == url:
|
|
74
|
+
return repo
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
def get_repo_by_kind(self, kind: str) -> Repo | None:
|
|
78
|
+
"""
|
|
79
|
+
Retrieves the first repository matching the specified kind.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
kind: The kind of repository to search for (e.g., "recipe", "preferences").
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
The first Repo instance matching the kind, or None if not found.
|
|
86
|
+
"""
|
|
87
|
+
for repo in self.repos:
|
|
88
|
+
if repo.kind() == kind:
|
|
89
|
+
return repo
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
def get(self, key: str, default=None):
|
|
93
|
+
"""
|
|
94
|
+
Searches for a key across all repositories and returns the first value found.
|
|
95
|
+
The search is performed in reverse order of repository loading, so the
|
|
96
|
+
most recently added repositories have precedence.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
key: The dot-separated key to search for (e.g., "repo.kind").
|
|
100
|
+
default: The value to return if the key is not found in any repo.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
The found value or the default.
|
|
104
|
+
"""
|
|
105
|
+
# Iterate in reverse to give precedence to later-loaded repos
|
|
106
|
+
for repo in reversed(self.repos):
|
|
107
|
+
value = repo.get(key)
|
|
108
|
+
if value is not None:
|
|
109
|
+
return value
|
|
110
|
+
|
|
111
|
+
return default
|
|
112
|
+
|
|
113
|
+
def dump(self):
|
|
114
|
+
"""
|
|
115
|
+
Prints a detailed, multi-line description of the combined top-level keys
|
|
116
|
+
and values from all repositories, using a MultiDict for aggregation.
|
|
117
|
+
This is useful for debugging and inspecting the consolidated configuration.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
combined_config = self.merged
|
|
121
|
+
logging.info("RepoManager Dump")
|
|
122
|
+
for key, value in combined_config.items():
|
|
123
|
+
# tomlkit.items() can return complex types (e.g., ArrayOfTables, Table)
|
|
124
|
+
# For a debug dump, a simple string representation is usually sufficient.
|
|
125
|
+
logging.info(f" %s: %s", key, value)
|
|
126
|
+
|
|
127
|
+
def _add_merged(self, repo: Repo) -> None:
|
|
128
|
+
for key, value in repo.config.items():
|
|
129
|
+
# if the toml object is an AoT type, monkey patch each element in the array instead
|
|
130
|
+
if isinstance(value, AoT):
|
|
131
|
+
for v in value:
|
|
132
|
+
setattr(v, "source", repo)
|
|
133
|
+
else:
|
|
134
|
+
# We monkey patch source into any object that came from a repo, so that users can
|
|
135
|
+
# find the source repo (for attribution, URL relative resolution, whatever...)
|
|
136
|
+
setattr(value, "source", repo)
|
|
137
|
+
|
|
138
|
+
self.merged.add(key, value)
|
|
139
|
+
|
|
140
|
+
def __str__(self):
|
|
141
|
+
lines = [f"RepoManager with {len(self.repos)} repositories:"]
|
|
142
|
+
for i, repo in enumerate(self.repos):
|
|
143
|
+
lines.append(f" [{i}] {repo.url}")
|
|
144
|
+
return "\n".join(lines)
|
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Manages the repository of processing recipes and configurations.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
1
|
from __future__ import annotations
|
|
6
2
|
import logging
|
|
7
3
|
from pathlib import Path
|
|
8
4
|
from importlib import resources
|
|
9
|
-
from typing import Any
|
|
5
|
+
from typing import Any, TYPE_CHECKING
|
|
10
6
|
|
|
11
7
|
import tomlkit
|
|
12
8
|
from tomlkit.toml_file import TOMLFile
|
|
13
9
|
from tomlkit.items import AoT
|
|
14
10
|
from multidict import MultiDict
|
|
15
11
|
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from repo.manager import RepoManager
|
|
16
14
|
|
|
17
15
|
repo_suffix = "starbash.toml"
|
|
18
16
|
|
|
@@ -39,7 +37,7 @@ class Repo:
|
|
|
39
37
|
|
|
40
38
|
Example: "Repo(kind=recipe, local=True, url=file:///path/to/repo)"
|
|
41
39
|
"""
|
|
42
|
-
return f"Repo(kind={self.kind}, url={self.url})"
|
|
40
|
+
return f"Repo(kind={self.kind()}, url={self.url})"
|
|
43
41
|
|
|
44
42
|
__repr__ = __str__
|
|
45
43
|
|
|
@@ -53,14 +51,14 @@ class Repo:
|
|
|
53
51
|
c = self.get("repo.kind", unknown_kind)
|
|
54
52
|
return str(c)
|
|
55
53
|
|
|
56
|
-
def add_repo_ref(self, dir:
|
|
54
|
+
def add_repo_ref(self, dir: Path) -> Repo | None:
|
|
57
55
|
"""
|
|
58
56
|
Adds a new repo-ref to this repository's configuration.
|
|
59
57
|
if new returns the newly added Repo object, if already exists returns None"""
|
|
60
58
|
|
|
61
59
|
# if dir is not absolute, we need to resolve it relative to the cwd
|
|
62
|
-
if not
|
|
63
|
-
dir =
|
|
60
|
+
if not dir.is_absolute():
|
|
61
|
+
dir = (Path.cwd() / dir).resolve()
|
|
64
62
|
|
|
65
63
|
# Add the ref to this repo
|
|
66
64
|
aot = self.config.get(REPO_REF, None)
|
|
@@ -72,11 +70,11 @@ class Repo:
|
|
|
72
70
|
raise ValueError(f"repo-ref in {self.url} is not an array")
|
|
73
71
|
|
|
74
72
|
for t in aot:
|
|
75
|
-
if "dir" in t and t["dir"] == dir:
|
|
73
|
+
if "dir" in t and t["dir"] == str(dir):
|
|
76
74
|
logging.warning(f"Repo ref {dir} already exists - ignoring.")
|
|
77
75
|
return None # already exists
|
|
78
76
|
|
|
79
|
-
ref = {"dir": dir}
|
|
77
|
+
ref = {"dir": str(dir)}
|
|
80
78
|
aot.append(ref)
|
|
81
79
|
|
|
82
80
|
# Also add the repo to the manager
|
|
@@ -156,22 +154,22 @@ class Repo:
|
|
|
156
154
|
for ref in repo_refs:
|
|
157
155
|
self.add_from_ref(ref)
|
|
158
156
|
|
|
159
|
-
def
|
|
157
|
+
def resolve_path(self, filepath: str) -> Path:
|
|
160
158
|
"""
|
|
161
|
-
|
|
159
|
+
Resolve a filepath relative to the base of this repo.
|
|
162
160
|
|
|
163
161
|
Args:
|
|
164
162
|
filepath: The path to the file, relative to the repository root.
|
|
165
163
|
|
|
166
164
|
Returns:
|
|
167
|
-
The
|
|
165
|
+
The resolved Path object.
|
|
168
166
|
"""
|
|
169
167
|
base_path = self.get_path()
|
|
170
168
|
if base_path is None:
|
|
171
|
-
raise ValueError("Cannot
|
|
169
|
+
raise ValueError("Cannot resolve filepaths for non-local repositories")
|
|
172
170
|
target_path = (base_path / filepath).resolve()
|
|
173
171
|
|
|
174
|
-
# Security check to prevent
|
|
172
|
+
# Security check to prevent accessing files outside the repo directory.
|
|
175
173
|
# FIXME SECURITY - temporarily disabled because I want to let file urls say things like ~/foo.
|
|
176
174
|
# it would false trigger if user homedir path has a symlink in it (such as /home -> /var/home)
|
|
177
175
|
# base_path = PosixPath('/home/kevinh/.config/starbash') │ │
|
|
@@ -180,7 +178,21 @@ class Repo:
|
|
|
180
178
|
# target_path = PosixPath('/var/home/kevinh/.config/starbash/starbash.toml')
|
|
181
179
|
#
|
|
182
180
|
# if base_path not in target_path.parents and target_path != base_path:
|
|
183
|
-
# raise PermissionError("Attempted to
|
|
181
|
+
# raise PermissionError("Attempted to access file outside of repository")
|
|
182
|
+
|
|
183
|
+
return target_path
|
|
184
|
+
|
|
185
|
+
def _read_file(self, filepath: str) -> str:
|
|
186
|
+
"""
|
|
187
|
+
Read a filepath relative to the base of this repo. Return the contents in a string.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
filepath: The path to the file, relative to the repository root.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
The content of the file as a string.
|
|
194
|
+
"""
|
|
195
|
+
target_path = self.resolve_path(filepath)
|
|
184
196
|
|
|
185
197
|
return target_path.read_text()
|
|
186
198
|
|
|
@@ -280,102 +292,3 @@ class Repo:
|
|
|
280
292
|
|
|
281
293
|
# Set the final value
|
|
282
294
|
current[keys[-1]] = value
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
class RepoManager:
|
|
286
|
-
"""
|
|
287
|
-
Manages the collection of starbash repositories.
|
|
288
|
-
|
|
289
|
-
This class is responsible for finding, loading, and providing an API
|
|
290
|
-
for searching through known repositories defined in TOML configuration
|
|
291
|
-
files (like appdefaults.sb.toml).
|
|
292
|
-
"""
|
|
293
|
-
|
|
294
|
-
def __init__(self):
|
|
295
|
-
"""
|
|
296
|
-
Initializes the RepoManager by loading the application default repos.
|
|
297
|
-
"""
|
|
298
|
-
self.repos = []
|
|
299
|
-
|
|
300
|
-
# We expose the app default preferences as a special root repo with a private URL
|
|
301
|
-
# root_repo = Repo(self, "pkg://starbash-defaults", config=app_defaults)
|
|
302
|
-
# self.repos.append(root_repo)
|
|
303
|
-
|
|
304
|
-
# Most users will just want to read from merged
|
|
305
|
-
self.merged = MultiDict()
|
|
306
|
-
|
|
307
|
-
@property
|
|
308
|
-
def regular_repos(self) -> list[Repo]:
|
|
309
|
-
"We exclude certain repo types (preferences, recipe) from the list of repos users care about."
|
|
310
|
-
return [
|
|
311
|
-
r
|
|
312
|
-
for r in self.repos
|
|
313
|
-
if r.kind() not in ("preferences") and not r.is_scheme("pkg")
|
|
314
|
-
]
|
|
315
|
-
|
|
316
|
-
def add_repo(self, url: str) -> Repo:
|
|
317
|
-
logging.debug(f"Adding repo: {url}")
|
|
318
|
-
r = Repo(self, url)
|
|
319
|
-
self.repos.append(r)
|
|
320
|
-
|
|
321
|
-
# FIXME, generate the merged dict lazily
|
|
322
|
-
self._add_merged(r)
|
|
323
|
-
|
|
324
|
-
# if this new repo has sub-repos, add them too
|
|
325
|
-
r.add_by_repo_refs()
|
|
326
|
-
|
|
327
|
-
return r
|
|
328
|
-
|
|
329
|
-
def get(self, key: str, default=None):
|
|
330
|
-
"""
|
|
331
|
-
Searches for a key across all repositories and returns the first value found.
|
|
332
|
-
The search is performed in reverse order of repository loading, so the
|
|
333
|
-
most recently added repositories have precedence.
|
|
334
|
-
|
|
335
|
-
Args:
|
|
336
|
-
key: The dot-separated key to search for (e.g., "repo.kind").
|
|
337
|
-
default: The value to return if the key is not found in any repo.
|
|
338
|
-
|
|
339
|
-
Returns:
|
|
340
|
-
The found value or the default.
|
|
341
|
-
"""
|
|
342
|
-
# Iterate in reverse to give precedence to later-loaded repos
|
|
343
|
-
for repo in reversed(self.repos):
|
|
344
|
-
value = repo.get(key)
|
|
345
|
-
if value is not None:
|
|
346
|
-
return value
|
|
347
|
-
|
|
348
|
-
return default
|
|
349
|
-
|
|
350
|
-
def dump(self):
|
|
351
|
-
"""
|
|
352
|
-
Prints a detailed, multi-line description of the combined top-level keys
|
|
353
|
-
and values from all repositories, using a MultiDict for aggregation.
|
|
354
|
-
This is useful for debugging and inspecting the consolidated configuration.
|
|
355
|
-
"""
|
|
356
|
-
|
|
357
|
-
combined_config = self.merged
|
|
358
|
-
logging.info("RepoManager Dump")
|
|
359
|
-
for key, value in combined_config.items():
|
|
360
|
-
# tomlkit.items() can return complex types (e.g., ArrayOfTables, Table)
|
|
361
|
-
# For a debug dump, a simple string representation is usually sufficient.
|
|
362
|
-
logging.info(f" %s: %s", key, value)
|
|
363
|
-
|
|
364
|
-
def _add_merged(self, repo: Repo) -> None:
|
|
365
|
-
for key, value in repo.config.items():
|
|
366
|
-
# if the toml object is an AoT type, monkey patch each element in the array instead
|
|
367
|
-
if isinstance(value, AoT):
|
|
368
|
-
for v in value:
|
|
369
|
-
setattr(v, "source", repo)
|
|
370
|
-
else:
|
|
371
|
-
# We monkey patch source into any object that came from a repo, so that users can
|
|
372
|
-
# find the source repo (for attribution, URL relative resolution, whatever...)
|
|
373
|
-
setattr(value, "source", repo)
|
|
374
|
-
|
|
375
|
-
self.merged.add(key, value)
|
|
376
|
-
|
|
377
|
-
def __str__(self):
|
|
378
|
-
lines = [f"RepoManager with {len(self.repos)} repositories:"]
|
|
379
|
-
for i, repo in enumerate(self.repos):
|
|
380
|
-
lines.append(f" [{i}] {repo.url}")
|
|
381
|
-
return "\n".join(lines)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import datetime
|
|
1
2
|
import logging
|
|
2
3
|
import os
|
|
4
|
+
from datetime import datetime
|
|
3
5
|
|
|
4
6
|
from .database import Database # re-export for convenience
|
|
5
7
|
from rich.console import Console
|
|
@@ -15,4 +17,22 @@ console = Console(
|
|
|
15
17
|
# Global variable for log filter level (can be changed via --debug flag)
|
|
16
18
|
log_filter_level = logging.INFO
|
|
17
19
|
|
|
20
|
+
|
|
21
|
+
def to_shortdate(date_iso: str) -> str:
|
|
22
|
+
"""Convert ISO UTC datetime string to local short date string (YYYY-MM-DD).
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
date_iso: ISO format datetime string (e.g., "2023-10-15T14:30:00Z")
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Short date string in YYYY-MM-DD format
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
dt_utc = datetime.fromisoformat(date_iso)
|
|
32
|
+
dt_local = dt_utc.astimezone()
|
|
33
|
+
return dt_local.strftime("%Y-%m-%d")
|
|
34
|
+
except (ValueError, TypeError):
|
|
35
|
+
return date_iso
|
|
36
|
+
|
|
37
|
+
|
|
18
38
|
__all__ = ["Database"]
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import string
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
_translator = str.maketrans("", "", string.punctuation + string.whitespace)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def normalize_target_name(name: str | None) -> str | None:
|
|
8
|
+
"""Converts a target name to an any filesystem-safe format by removing spaces"""
|
|
9
|
+
if name is None:
|
|
10
|
+
return None
|
|
11
|
+
return name.replace(" ", "").lower()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def pre_normalize(name: str) -> str:
|
|
15
|
+
"""Pre-normalize a name by removing all whitespace and punctuation, and converting to lowercase.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
name: The name to pre-normalize.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
Normalized string with only alphanumeric characters in lowercase.
|
|
22
|
+
"""
|
|
23
|
+
# Create translation table that removes all punctuation and whitespace
|
|
24
|
+
return name.lower().translate(_translator)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class UnrecognizedAliasError(ValueError):
|
|
28
|
+
"""Exception raised when an unrecognized alias is encountered during normalization."""
|
|
29
|
+
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Aliases:
|
|
34
|
+
def __init__(self, alias_dict: dict[str, list[str]]):
|
|
35
|
+
"""Initialize the Aliases object with a dictionary mapping keys to their alias lists.
|
|
36
|
+
|
|
37
|
+
The alias_dict structure follows the TOML format:
|
|
38
|
+
- Keys are reference names used in code (e.g., "dark", "flat", "bias", "fits", "SiiOiii", "HaOiii")
|
|
39
|
+
- Values are lists of aliases where the FIRST item is the canonical/preferred name
|
|
40
|
+
- The dictionary key may or may not match the canonical name
|
|
41
|
+
|
|
42
|
+
Example from TOML:
|
|
43
|
+
[aliases]
|
|
44
|
+
dark = ["dark", "darks"] # key "dark" -> canonical "dark"
|
|
45
|
+
flat = ["flat", "flats"] # key "flat" -> canonical "flat"
|
|
46
|
+
SiiOiii = ["SiiOiii", "SII-OIII", "S2-O3"] # key "SiiOiii" -> canonical "SiiOiii"
|
|
47
|
+
"""
|
|
48
|
+
self.alias_dict = alias_dict
|
|
49
|
+
self.reverse_dict = {}
|
|
50
|
+
|
|
51
|
+
# Build reverse lookup: any alias variant maps to canonical name
|
|
52
|
+
for _key, aliases in alias_dict.items():
|
|
53
|
+
if not aliases:
|
|
54
|
+
continue
|
|
55
|
+
# The first item in the list is ALWAYS the canonical/preferred form
|
|
56
|
+
canonical = aliases[0]
|
|
57
|
+
for alias in aliases:
|
|
58
|
+
# Map each alias (case-insensitive) to the canonical form (first in list)
|
|
59
|
+
# Also remove spaces, hypens and underscores when matching for normalization
|
|
60
|
+
self.reverse_dict[pre_normalize(alias)] = canonical
|
|
61
|
+
|
|
62
|
+
def get(self, name: str) -> list[str] | None:
|
|
63
|
+
"""Get the list of aliases for a given key name.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
name: The key name to look up (as used in code/TOML)
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
List of all aliases for this key, or None if not found.
|
|
70
|
+
The first item in the returned list is the canonical form.
|
|
71
|
+
"""
|
|
72
|
+
return self.alias_dict.get(name)
|
|
73
|
+
|
|
74
|
+
def normalize(self, name: str) -> str:
|
|
75
|
+
"""Normalize a name to its canonical form using aliases.
|
|
76
|
+
|
|
77
|
+
This performs case-insensitive matching to find the canonical form.
|
|
78
|
+
The canonical form is the first item in the alias list from the TOML.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
name: The name to normalize (e.g., "darks", "FLAT", "HA-OIII")
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
The canonical/preferred form (e.g., "dark", "flat", "HaOiii"), or None if not found
|
|
85
|
+
|
|
86
|
+
Examples:
|
|
87
|
+
normalize("darks") -> "dark"
|
|
88
|
+
normalize("FLAT") -> "flat"
|
|
89
|
+
normalize("HA-OIII") -> "HaOiii"
|
|
90
|
+
"""
|
|
91
|
+
result = self.reverse_dict.get(pre_normalize(name))
|
|
92
|
+
if not result:
|
|
93
|
+
raise UnrecognizedAliasError(f"'{name}' not found in aliases.")
|
|
94
|
+
return result
|
|
95
|
+
|
|
96
|
+
def equals(self, name1: str, name2: str) -> bool:
|
|
97
|
+
"""Check if two names are equivalent based on aliases."""
|
|
98
|
+
norm1 = self.normalize(name1.strip())
|
|
99
|
+
norm2 = self.normalize(name2.strip())
|
|
100
|
+
return norm1 == norm2
|