Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Tests

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'

- name: Install dependencies
run: pip install -e .[test]

- name: Run pytest
run: pytest
59 changes: 56 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
# Starlet Setup
A lightweight Python utility to quickly clone, configure, and build CMake projects — from single repos to full mono-repos.


[![Tests](https://github.com/masonlet/starlet-setup/actions/workflows/tests.yml/badge.svg)](https://github.com/masonlet/starlet-setup/actions/workflows/tests.yml)
[![PyPI version](https://badge.fury.io/py/starlet-setup.svg)](https://badge.fury.io/py/starlet-setup)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
[![Python 3.6+](https://img.shields.io/badge/python-3.6%2B-blue.svg)]()


## Table of Contents
- [Features](#features)
- [Prerequisites](#prerequisites)
Expand All @@ -16,10 +15,15 @@ A lightweight Python utility to quickly clone, configure, and build CMake projec
- [Single Repository Mode](#single-repository-mode)
- [Mono-Repo Mode](#mono-repo-mode)
- [Profile Mode](#profile-mode-saved-configurations)
- [Development](#development)
- [License](#license)



<br/>



## Features
- **Single Repository Mode**:
- Clone a GitHub repository with simple `username/repo` syntax
Expand All @@ -42,8 +46,12 @@ A lightweight Python utility to quickly clone, configure, and build CMake projec
- Manage multiple development environments effortlessly
- Default profile includes all core Starlet modules



<br/>



## Prerequisites
- Python 3.6+
- Git
Expand Down Expand Up @@ -94,8 +102,12 @@ Alternatively, you can run the script directly:
python -m starlet_setup username/repo
```



<br/>



## Configuration
Starlet Setup supports persistent configuration through a JSON file, allowing you to save your preferred defaults (e.g., SSH mode, build directory, mono-repo repositories).

Expand All @@ -111,8 +123,12 @@ Starlet Setup checks for configuration files in this order:
- `./.starlet-setup.json` (current directory)
- `~/.starlet-setup.json` (home directory)



<br/>



## Usage

### Single Repository Mode
Expand Down Expand Up @@ -270,7 +286,44 @@ starlet-setup username/repo --profile myprofile
starlet-setup username/repo --profile myprofile --ssh
```



<br/>



## Development

### Running Tests

#### 1. Clone the Repository
```bash
git clone https://github.com/masonlet/starlet-setup.git
cd starlet-setup
```

#### 2. Install in Development Mode
```bash
pip install -e .
```

#### 3. Run Tests
```bash
# Run all tests
pytest

# Run specific test file
pytest tests/test_config.py

# Run tests with flags
pytest -v
```



<br/>



## License
MIT License — see [LICENSE](./LICENSE) for details.
MIT License — see [LICENSE](./LICENSE) for details.
7 changes: 6 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "starlet-setup"
version = "1.0.1"
version = "1.0.2"
description = "Quick setup for CMake projects"
readme = "README.md"
requires-python = ">=3.6"
Expand All @@ -13,6 +13,11 @@ authors = [
{name = "Mason L'Etoile", email = "masonletoile@hotmail.com"}
]

[project.optional-dependencies]
test = [
"pytest>=7.0",
]

[project.urls]
Homepage = "https://github.com/masonlet/starlet-setup"

Expand Down
4 changes: 4 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[pytest]
minversion = 6.0
addopts = -v
testpaths = tests
1 change: 1 addition & 0 deletions src/starlet_setup/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from .utils import check_prerequisites
from .commands import mono_repo_mode, single_repo_mode


def main() -> None:
"""Main entry point for Starlet Setup."""
args = parse_args()
Expand Down
10 changes: 6 additions & 4 deletions src/starlet_setup/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
from argparse import Namespace
from .config import get_config_value, load_config


def parse_args() -> Namespace:
"""
Parse command-line arguments for Starlet Setup.

Returns:
Parsed arguments namespace
"""
config = load_config()
config, config_path = load_config()

parser = argparse.ArgumentParser(
description="Starlet Setup - Quick setup script for CMake projects",
Expand Down Expand Up @@ -60,9 +61,9 @@ def parse_args() -> Namespace:
)
parser.add_argument(
'--cmake-arg',
nargs='*',
default=None,
help='Additional CMake arguments (e.g., --cmake-arg=-D_BUILD_TESTS=ON)'
action='append',
dest='cmake_arg',
help='Additional CMake arguments (e.g., --cmake-arg=-D_BUILD_TESTS=ON). Can be used multiple times.'
)

# Configuration arguments
Expand Down Expand Up @@ -148,6 +149,7 @@ def parse_args() -> Namespace:

args = parser.parse_args()
args.config = config
args.config_path = config_path

if args.init_config or args.list_profiles or args.profile_add or args.profile_remove:
return args
Expand Down
2 changes: 0 additions & 2 deletions src/starlet_setup/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from argparse import Namespace
from pathlib import Path
from typing import Optional

from .repository import (
resolve_repo_url,
get_default_repos,
Expand Down Expand Up @@ -109,7 +108,6 @@ def single_repo_mode(args: Namespace) -> None:
print(f"Project finished in {repo_name}/{args.build_dir}")



def _create_mono_repo_cmakelists(mono_dir: Path, test_repo: str, repos: list[str]):
"""
Create a root CMakeLists.txt for the mono-repo.
Expand Down
66 changes: 47 additions & 19 deletions src/starlet_setup/config.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
"""Configuration file management"""


import json
from pathlib import Path
from typing import Any


def load_config() -> dict:
def load_config() -> tuple[dict, Path | None]:
"""
Load configuration from file, falling back to defaults.

Expand All @@ -18,19 +17,37 @@ def load_config() -> dict:
Path.home() / '.starlet-setup.json'
]

invalid_count = 0
for config_path in config_locations:
if config_path.exists():
try:
with open(config_path) as f:
return json.load(f)
return json.load(f), config_path
except json.JSONDecodeError as e:
print(f"Warning: Invalid JSON in {config_path}: {e}")
invalid_count += 1
continue
except PermissionError:
print(f"Error: No permission to read the file in {config_path}.")
invalid_count += 1
continue
except Exception as e:
print(f"An unexpected error occurred reading {config_path}: {e}")
invalid_count += 1
continue


return {}
if invalid_count != 0:
print(f"Found {invalid_count} config file{'s' if invalid_count != 1 else ''} that had errors")
else:
print("Failed to find config file")
return {}, None


def save_config(config) -> Path:
def save_config(
config: dict,
config_path: Path | None = None
) -> Path:
"""
Save configuration to a file.

Expand All @@ -40,13 +57,18 @@ def save_config(config) -> Path:
Returns:
Path where config was saved
"""
config_path = Path('.starlet-setup.json')
if not config_path.exists():
config_path = Path.home() / '.starlet-setup.json'

with open(config_path, 'w') as f:
json.dump(config, f, indent=2)

if config_path is None:
config_path = Path('.starlet-setup.json')

try:
with open(config_path, 'w') as f:
json.dump(config, f, indent=2)
except PermissionError:
print(f"Error: No permission to write to {config_path}")
raise
except Exception as e:
print(f"An unexpected error occurred writing {config_path}: {e}")
raise
return config_path


Expand All @@ -56,16 +78,15 @@ def get_config_value(config: dict, key: str, default: Any) -> Any:

Args:
config: Configuration dictionary
key: Dot-seperated key path (e.g, 'defaults.ssh')
key: Dot-separated key path (e.g, 'defaults.ssh')
default: Default value if key not found
"""
parts = key.split('.')
value = config
for part in parts:
if isinstance(value, dict) and part in value:
value = value[part]
else:
if not isinstance(value, dict) or part not in value:
return default
value = value[part]
return value


Expand Down Expand Up @@ -101,11 +122,18 @@ def create_default_config() -> None:
print("Aborted.")
return

with open(config_path, 'w') as f:
json.dump(default_config, f, indent=2)
try:
with open(config_path, 'w') as f:
json.dump(default_config, f, indent=2)
except PermissionError:
print(f"Error: No permission to write to {config_path}")
return
except Exception as e:
print(f"An unexpected error occurred writing {config_path}: {e}")
return

print(f"Created config file: {config_path.absolute()}")
print("Edit this file to customize your defaults.")
print("\nConfig files are checked in this order:")
print(" 1. ./.starlet-setup.json (current directory)")
print(" 2. ~/.starlet-setup.json (home directory)")
print(" 2. ~/.starlet-setup.json (home directory)")
3 changes: 2 additions & 1 deletion src/starlet_setup/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from .config import get_config_value
from .utils import run_command


def resolve_repo_url(repo_input: str, use_ssh: bool=False) -> str:
"""
Convert repository input to full URL.
Expand Down Expand Up @@ -78,4 +79,4 @@ def clone_repository(
run_command(['git', 'clone', repo_url], cwd=target_dir, verbose=verbose)
except SystemExit:
print(f" Failed to clone {repo_path}")
raise
raise
4 changes: 2 additions & 2 deletions src/starlet_setup/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ def check_prerequisites(verbose: bool=False) -> None:

def run_command(
cmd: list[str],
cwd: Optional[Union[str, Path]]=None,
verbose: bool=False
cwd: Optional[Union[str, Path]] = None,
verbose: bool = False
) -> subprocess.CompletedProcess:
"""
Run a shell command with proper error handling
Expand Down
Empty file added tests/__init__.py
Empty file.
Loading