pixi-ros 0.1.0__tar.gz → 0.2.0__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.
Files changed (51) hide show
  1. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/.github/workflows/ci.yml +2 -0
  2. pixi_ros-0.2.0/LICENSE +21 -0
  3. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/PKG-INFO +88 -6
  4. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/README.md +85 -5
  5. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/pixi.lock +2 -1
  6. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/pyproject.toml +2 -1
  7. pixi_ros-0.2.0/src/pixi_ros/cli.py +134 -0
  8. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/src/pixi_ros/init.py +273 -66
  9. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/src/pixi_ros/mappings.py +35 -0
  10. pixi_ros-0.2.0/tests/examples/ws1/src/package-b/package-b/__init__.py +0 -0
  11. pixi_ros-0.2.0/tests/test_init.py +366 -0
  12. pixi_ros-0.1.0/src/pixi_ros/__init__.py +0 -3
  13. pixi_ros-0.1.0/src/pixi_ros/cli.py +0 -77
  14. pixi_ros-0.1.0/tests/test_init.py +0 -144
  15. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/.gitattributes +0 -0
  16. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/.github/workflows/publish-pypi.yml +0 -0
  17. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/.gitignore +0 -0
  18. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/pixi.toml +0 -0
  19. {pixi_ros-0.1.0/tests/examples/ws1/src/package-b/package-b → pixi_ros-0.2.0/src/pixi_ros}/__init__.py +0 -0
  20. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/src/pixi_ros/config.py +0 -0
  21. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/src/pixi_ros/data/README.md +0 -0
  22. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/src/pixi_ros/data/README_PIXI.md.template +0 -0
  23. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/src/pixi_ros/data/conda-forge.yaml +0 -0
  24. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/src/pixi_ros/package_xml.py +0 -0
  25. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/src/pixi_ros/utils.py +0 -0
  26. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/src/pixi_ros/workspace.py +0 -0
  27. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/tests/examples/ws1/pixi.lock +0 -0
  28. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/tests/examples/ws1/pixi.toml +0 -0
  29. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/tests/examples/ws1/src/package-a/CMakeLists.txt +0 -0
  30. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/tests/examples/ws1/src/package-a/LICENSE +0 -0
  31. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/tests/examples/ws1/src/package-a/package.xml +0 -0
  32. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/tests/examples/ws1/src/package-b/package.xml +0 -0
  33. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/tests/examples/ws1/src/package-b/setup.cfg +0 -0
  34. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/tests/examples/ws1/src/package-b/setup.py +0 -0
  35. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/tests/examples/ws1/src/package-b/test/test_copyright.py +0 -0
  36. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/tests/examples/ws1/src/package-b/test/test_flake8.py +0 -0
  37. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/tests/examples/ws1/src/package-b/test/test_pep257.py +0 -0
  38. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/tests/fixtures/mock_workspace/README.md +0 -0
  39. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/tests/fixtures/mock_workspace/src/legacy_pkg/package.xml +0 -0
  40. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/tests/fixtures/mock_workspace/src/my_cpp_pkg/CMakeLists.txt +0 -0
  41. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/tests/fixtures/mock_workspace/src/my_cpp_pkg/package.xml +0 -0
  42. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/tests/fixtures/mock_workspace/src/my_mixed_pkg/package.xml +0 -0
  43. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/tests/fixtures/mock_workspace/src/my_python_pkg/package.xml +0 -0
  44. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/tests/fixtures/mock_workspace/src/my_python_pkg/setup.py +0 -0
  45. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/tests/test_cli.py +0 -0
  46. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/tests/test_config.py +0 -0
  47. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/tests/test_gateway_availability.py +0 -0
  48. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/tests/test_mappings.py +0 -0
  49. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/tests/test_package_xml.py +0 -0
  50. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/tests/test_utils.py +0 -0
  51. {pixi_ros-0.1.0 → pixi_ros-0.2.0}/tests/test_workspace.py +0 -0
@@ -1,3 +1,5 @@
1
+ name: CI
2
+
1
3
  on:
2
4
  push:
3
5
  pull_request:
pixi_ros-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) [year] [fullname]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,10 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pixi-ros
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Pixi extension for ROS package management
5
5
  Project-URL: Homepage, https://github.com/ruben-arts/pixi-ros
6
6
  Project-URL: Repository, https://github.com/ruben-arts/pixi-ros
7
7
  Author-email: Ruben Arts <ruben@prefix.dev>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
8
10
  Requires-Python: >=3.10
9
11
  Requires-Dist: lxml>=5.0.0
10
12
  Requires-Dist: pathspec>=0.11.0
@@ -88,13 +90,16 @@ pixi shell
88
90
 
89
91
  ### Dependency Mapping
90
92
 
91
- `pixi-ros` reads all dependency types from `package.xml` files.
93
+ `pixi-ros` reads all dependency types from `package.xml` files.
92
94
  It then does a best effort mapping of ROS package names to conda packages.
93
95
 
94
96
  - **ROS packages**: `ros-{distro}-{package}` from robostack channels (e.g., `ros-humble-rclcpp`)
95
97
  - **System packages**: Mapped to conda-forge equivalents (e.g., `cmake`, `eigen`)
98
+ - **Platform-specific packages**: Different mappings per platform (e.g., OpenGL → `libgl-devel` on Linux, X11 packages on macOS)
96
99
 
97
- After the mapping, it validates package availability in the configured channels. This starts a connection with `https://prefix.dev` to check if packages exist.
100
+ The mapping rules are defined in YAML files (see `src/pixi_ros/data/conda-forge.yaml`) and can be customized by placing your own mapping files in `pixi-ros/*.yaml` or `~/.pixi-ros/*.yaml`.
101
+
102
+ After the mapping, it validates package availability in the configured channels for each target platform. This starts a connection with `https://prefix.dev` to check if packages exist.
98
103
 
99
104
  ### Example
100
105
 
@@ -130,24 +135,101 @@ Initialize or update a ROS workspace's `pixi.toml`.
130
135
 
131
136
  ```bash
132
137
  pixi-ros init --distro <ros_distro>
138
+ pixi-ros init --distro humble --platform linux-64 --platform osx-arm64
133
139
  pixi-ros init
134
140
  ```
135
141
 
136
142
  **Options:**
137
- - `--distro`, `-d`: ROS distribution (optional)
143
+ - `--distro`, `-d`: ROS distribution (optional, will prompt if not provided)
144
+ - `--platform`, `-p`: Target platforms (optional, can be specified multiple times, will prompt if not provided)
145
+ - Available: `linux-64`, `osx-64`, `osx-arm64`, `win-64`
146
+ - Platforms come from the mapping files and determine which dependencies are available
138
147
 
139
148
  **What it does:**
140
149
  - Scans workspace for `package.xml` files
141
150
  - Reads all dependency types (build, exec, test)
142
- - Maps ROS dependencies to conda packages
151
+ - Maps ROS dependencies to conda packages for each platform
143
152
  - Configures robostack channels
144
- - Checks package availability
153
+ - Checks package availability per platform
145
154
  - Creates build tasks using colcon
146
155
  - Generates helpful `README_PIXI.md`
156
+ - Sets up platform-specific dependencies in `pixi.toml`
147
157
 
148
158
  **Running multiple times:**
149
159
  The command is idempotent - you can run it multiple times to update dependencies as your workspace changes.
150
160
 
161
+ ## Multi-Platform Support
162
+
163
+ `pixi-ros` supports generating cross-platform configurations. When you specify multiple platforms, it:
164
+
165
+ 1. **Analyzes dependencies per platform**: Some packages have platform-specific mappings (e.g., OpenGL requirements differ between Linux and macOS)
166
+
167
+ 2. **Organizes dependencies intelligently**:
168
+ - **Common dependencies** (available on all platforms) → `[dependencies]`
169
+ - **Unix dependencies** (available on Linux and macOS, but not Windows) → `[target.unix.dependencies]`
170
+ - **Platform-specific dependencies** → `[target.linux.dependencies]`, `[target.osx.dependencies]`, etc.
171
+
172
+ 3. **Sets up correct platform list**: The `[workspace]` section gets the appropriate pixi platform names
173
+
174
+ ### Platform Naming
175
+
176
+ pixi-ros uses standard pixi platform names:
177
+ - `linux-64` - Linux x86_64
178
+ - `osx-64` - macOS Intel
179
+ - `osx-arm64` - macOS Apple Silicon (M1/M2/M3)
180
+ - `win-64` - Windows x86_64
181
+
182
+ Internally, mapping files use a simplified format (`linux`, `osx`, `win64`), but this is transparent to users. When you specify `osx-64` and `osx-arm64`, they both use the same `osx` mapping rules since package availability is typically the same for both architectures.
183
+
184
+ ### Example: Multi-Platform Setup
185
+
186
+ ```bash
187
+ pixi-ros init --distro humble --platform linux-64 --platform osx-arm64
188
+ ```
189
+
190
+ Generates:
191
+
192
+ ```toml
193
+ [workspace]
194
+ name = "my_workspace"
195
+ channels = [
196
+ "https://prefix.dev/robostack-humble",
197
+ "https://prefix.dev/conda-forge",
198
+ ]
199
+ platforms = ["linux-64", "osx-arm64"]
200
+
201
+ [dependencies]
202
+ # Common dependencies (available on all platforms)
203
+ ros-humble-rclcpp = "*"
204
+ ros-humble-std-msgs = "*"
205
+
206
+ [target.unix.dependencies]
207
+ # Unix-specific dependencies (Linux and macOS)
208
+ xorg-libx11 = "*"
209
+ xorg-libxext = "*"
210
+
211
+ [target.linux.dependencies]
212
+ # Linux-specific dependencies
213
+ libgl-devel = "*"
214
+ libopengl-devel = "*"
215
+ ```
216
+
217
+ ### Interactive Platform Selection
218
+
219
+ If you don't specify platforms, you'll be prompted:
220
+
221
+ ```bash
222
+ $ pixi-ros init --distro humble
223
+
224
+ Available target platforms:
225
+ 1. linux-64
226
+ 2. osx-64
227
+ 3. osx-arm64
228
+ 4. win-64
229
+
230
+ Select platforms (enter numbers or names, comma or space separated): 1 3
231
+ ```
232
+
151
233
  ## Philosophy
152
234
 
153
235
  `pixi-ros` aims to be a quick **gateway drug**. It:
@@ -71,13 +71,16 @@ pixi shell
71
71
 
72
72
  ### Dependency Mapping
73
73
 
74
- `pixi-ros` reads all dependency types from `package.xml` files.
74
+ `pixi-ros` reads all dependency types from `package.xml` files.
75
75
  It then does a best effort mapping of ROS package names to conda packages.
76
76
 
77
77
  - **ROS packages**: `ros-{distro}-{package}` from robostack channels (e.g., `ros-humble-rclcpp`)
78
78
  - **System packages**: Mapped to conda-forge equivalents (e.g., `cmake`, `eigen`)
79
+ - **Platform-specific packages**: Different mappings per platform (e.g., OpenGL → `libgl-devel` on Linux, X11 packages on macOS)
79
80
 
80
- After the mapping, it validates package availability in the configured channels. This starts a connection with `https://prefix.dev` to check if packages exist.
81
+ The mapping rules are defined in YAML files (see `src/pixi_ros/data/conda-forge.yaml`) and can be customized by placing your own mapping files in `pixi-ros/*.yaml` or `~/.pixi-ros/*.yaml`.
82
+
83
+ After the mapping, it validates package availability in the configured channels for each target platform. This starts a connection with `https://prefix.dev` to check if packages exist.
81
84
 
82
85
  ### Example
83
86
 
@@ -113,24 +116,101 @@ Initialize or update a ROS workspace's `pixi.toml`.
113
116
 
114
117
  ```bash
115
118
  pixi-ros init --distro <ros_distro>
119
+ pixi-ros init --distro humble --platform linux-64 --platform osx-arm64
116
120
  pixi-ros init
117
121
  ```
118
122
 
119
123
  **Options:**
120
- - `--distro`, `-d`: ROS distribution (optional)
124
+ - `--distro`, `-d`: ROS distribution (optional, will prompt if not provided)
125
+ - `--platform`, `-p`: Target platforms (optional, can be specified multiple times, will prompt if not provided)
126
+ - Available: `linux-64`, `osx-64`, `osx-arm64`, `win-64`
127
+ - Platforms come from the mapping files and determine which dependencies are available
121
128
 
122
129
  **What it does:**
123
130
  - Scans workspace for `package.xml` files
124
131
  - Reads all dependency types (build, exec, test)
125
- - Maps ROS dependencies to conda packages
132
+ - Maps ROS dependencies to conda packages for each platform
126
133
  - Configures robostack channels
127
- - Checks package availability
134
+ - Checks package availability per platform
128
135
  - Creates build tasks using colcon
129
136
  - Generates helpful `README_PIXI.md`
137
+ - Sets up platform-specific dependencies in `pixi.toml`
130
138
 
131
139
  **Running multiple times:**
132
140
  The command is idempotent - you can run it multiple times to update dependencies as your workspace changes.
133
141
 
142
+ ## Multi-Platform Support
143
+
144
+ `pixi-ros` supports generating cross-platform configurations. When you specify multiple platforms, it:
145
+
146
+ 1. **Analyzes dependencies per platform**: Some packages have platform-specific mappings (e.g., OpenGL requirements differ between Linux and macOS)
147
+
148
+ 2. **Organizes dependencies intelligently**:
149
+ - **Common dependencies** (available on all platforms) → `[dependencies]`
150
+ - **Unix dependencies** (available on Linux and macOS, but not Windows) → `[target.unix.dependencies]`
151
+ - **Platform-specific dependencies** → `[target.linux.dependencies]`, `[target.osx.dependencies]`, etc.
152
+
153
+ 3. **Sets up correct platform list**: The `[workspace]` section gets the appropriate pixi platform names
154
+
155
+ ### Platform Naming
156
+
157
+ pixi-ros uses standard pixi platform names:
158
+ - `linux-64` - Linux x86_64
159
+ - `osx-64` - macOS Intel
160
+ - `osx-arm64` - macOS Apple Silicon (M1/M2/M3)
161
+ - `win-64` - Windows x86_64
162
+
163
+ Internally, mapping files use a simplified format (`linux`, `osx`, `win64`), but this is transparent to users. When you specify `osx-64` and `osx-arm64`, they both use the same `osx` mapping rules since package availability is typically the same for both architectures.
164
+
165
+ ### Example: Multi-Platform Setup
166
+
167
+ ```bash
168
+ pixi-ros init --distro humble --platform linux-64 --platform osx-arm64
169
+ ```
170
+
171
+ Generates:
172
+
173
+ ```toml
174
+ [workspace]
175
+ name = "my_workspace"
176
+ channels = [
177
+ "https://prefix.dev/robostack-humble",
178
+ "https://prefix.dev/conda-forge",
179
+ ]
180
+ platforms = ["linux-64", "osx-arm64"]
181
+
182
+ [dependencies]
183
+ # Common dependencies (available on all platforms)
184
+ ros-humble-rclcpp = "*"
185
+ ros-humble-std-msgs = "*"
186
+
187
+ [target.unix.dependencies]
188
+ # Unix-specific dependencies (Linux and macOS)
189
+ xorg-libx11 = "*"
190
+ xorg-libxext = "*"
191
+
192
+ [target.linux.dependencies]
193
+ # Linux-specific dependencies
194
+ libgl-devel = "*"
195
+ libopengl-devel = "*"
196
+ ```
197
+
198
+ ### Interactive Platform Selection
199
+
200
+ If you don't specify platforms, you'll be prompted:
201
+
202
+ ```bash
203
+ $ pixi-ros init --distro humble
204
+
205
+ Available target platforms:
206
+ 1. linux-64
207
+ 2. osx-64
208
+ 3. osx-arm64
209
+ 4. win-64
210
+
211
+ Select platforms (enter numbers or names, comma or space separated): 1 3
212
+ ```
213
+
134
214
  ## Philosophy
135
215
 
136
216
  `pixi-ros` aims to be a quick **gateway drug**. It:
@@ -1073,7 +1073,7 @@ packages:
1073
1073
  timestamp: 1769677743677
1074
1074
  - conda: .
1075
1075
  name: pixi-ros
1076
- version: 0.1.0
1076
+ version: 0.1.2
1077
1077
  build: pyh4616a5c_0
1078
1078
  subdir: noarch
1079
1079
  variants:
@@ -1088,6 +1088,7 @@ packages:
1088
1088
  - py-rattler >=0.6.0
1089
1089
  - pathspec >=0.11.0
1090
1090
  - rich >=13.0.0
1091
+ license: MIT
1091
1092
  - conda: https://prefix.dev/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda
1092
1093
  sha256: e14aafa63efa0528ca99ba568eaf506eb55a0371d12e6250aaaa61718d2eb62e
1093
1094
  md5: d7585b6550ad04c8c5e21097ada2888e
@@ -4,12 +4,13 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pixi-ros"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "Pixi extension for ROS package management"
9
9
  authors = [
10
10
  { name = "Ruben Arts", email = "ruben@prefix.dev" }
11
11
  ]
12
12
  readme = "README.md"
13
+ license = "MIT"
13
14
  requires-python = ">=3.10"
14
15
  dependencies = [
15
16
  "typer>=0.12.0",
@@ -0,0 +1,134 @@
1
+ """Main CLI entry point for pixi-ros."""
2
+
3
+ from typing import Annotated
4
+
5
+ import typer
6
+
7
+ from pixi_ros.init import init_workspace
8
+ from pixi_ros.mappings import get_platforms, get_ros_distros
9
+
10
+ app = typer.Typer(
11
+ name="pixi-ros",
12
+ help="Pixi extension for ROS package management",
13
+ no_args_is_help=True,
14
+ )
15
+
16
+ pkg_app = typer.Typer(help="Manage ROS packages")
17
+ app.add_typer(pkg_app, name="pkg")
18
+
19
+
20
+ @app.command()
21
+ def init(
22
+ distro: Annotated[
23
+ str | None,
24
+ typer.Option(
25
+ "--distro",
26
+ "-d",
27
+ help="ROS distribution (e.g., humble, iron, jazzy)",
28
+ ),
29
+ ] = None,
30
+ platforms: Annotated[
31
+ list[str] | None,
32
+ typer.Option(
33
+ "--platform",
34
+ "-p",
35
+ help="Target platforms (e.g., linux-64, osx-arm64, win-64). Can be specified multiple times.",
36
+ ),
37
+ ] = None,
38
+ ):
39
+ """Initialize pixi.toml for a ROS workspace."""
40
+ # If distro not provided, prompt user to select one
41
+ if distro is None:
42
+ available_distros = get_ros_distros()
43
+ typer.echo("Available ROS distributions:")
44
+ for i, d in enumerate(available_distros, 1):
45
+ typer.echo(f" {i}. {d}")
46
+
47
+ # Prompt for selection
48
+ selection = typer.prompt(
49
+ "\nSelect a distribution (enter number or name)",
50
+ type=str,
51
+ )
52
+
53
+ # Parse selection (either number or name)
54
+ try:
55
+ selection_num = int(selection)
56
+ dist_count = len(available_distros)
57
+ if 1 <= selection_num <= dist_count:
58
+ distro = available_distros[selection_num - 1]
59
+ else:
60
+ typer.echo(
61
+ f"Error: Invalid selection. Please choose 1-{dist_count}",
62
+ err=True,
63
+ )
64
+ raise typer.Exit(code=1)
65
+ except ValueError as err:
66
+ # User entered a name instead of number
67
+ if selection in available_distros:
68
+ distro = selection
69
+ else:
70
+ typer.echo(
71
+ f"Error: '{selection}' is not a valid ROS distribution", err=True
72
+ )
73
+ typer.echo(f"Available: {', '.join(available_distros)}", err=True)
74
+ raise typer.Exit(code=1) from err
75
+
76
+ # If platforms not provided, prompt user to select
77
+ if platforms is None or len(platforms) == 0:
78
+ available_platforms = get_platforms()
79
+ typer.echo("\nAvailable target platforms:")
80
+ for i, p in enumerate(available_platforms, 1):
81
+ typer.echo(f" {i}. {p}")
82
+
83
+ # Prompt for selection (can be comma-separated or space-separated)
84
+ selection = typer.prompt(
85
+ "\nSelect platforms (enter numbers or names, comma or space separated)",
86
+ type=str,
87
+ )
88
+
89
+ # Parse selection (can be numbers or names, comma or space separated)
90
+ platforms = []
91
+ # Split by comma or space
92
+ selections = selection.replace(",", " ").split()
93
+
94
+ for sel in selections:
95
+ sel = sel.strip()
96
+ if not sel:
97
+ continue
98
+
99
+ try:
100
+ # Try parsing as number
101
+ sel_num = int(sel)
102
+ if 1 <= sel_num <= len(available_platforms):
103
+ platforms.append(available_platforms[sel_num - 1])
104
+ else:
105
+ typer.echo(
106
+ f"Error: Invalid selection {sel_num}. Please choose 1-{len(available_platforms)}",
107
+ err=True,
108
+ )
109
+ raise typer.Exit(code=1)
110
+ except ValueError:
111
+ # User entered a name instead of number
112
+ if sel in available_platforms:
113
+ platforms.append(sel)
114
+ else:
115
+ typer.echo(
116
+ f"Error: '{sel}' is not a valid platform", err=True
117
+ )
118
+ typer.echo(f"Available: {', '.join(available_platforms)}", err=True)
119
+ raise typer.Exit(code=1)
120
+
121
+ if not platforms:
122
+ typer.echo("Error: No platforms selected", err=True)
123
+ raise typer.Exit(code=1)
124
+
125
+ init_workspace(distro, platforms=platforms)
126
+
127
+
128
+ def main():
129
+ """Entry point for the CLI."""
130
+ app()
131
+
132
+
133
+ if __name__ == "__main__":
134
+ main()