Initial gradle-mirror CLI project
This commit is contained in:
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*.egg-info/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
.pytest_cache/
|
||||||
|
.mypy_cache/
|
||||||
117
README.fa.md
Normal file
117
README.fa.md
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
# gradle-mirror
|
||||||
|
|
||||||
|
`gradle-mirror` یک ابزار خط فرمان (CLI) با پایتون است که با ویرایش فایل سراسری Gradle (`init.gradle.kts`) آینههای مخزن (mirror repositories) را فعال یا غیرفعال میکند.
|
||||||
|
|
||||||
|
## قابلیتها
|
||||||
|
|
||||||
|
- فعالسازی آینهها با درج یک بلاک مدیریتشده داخل `init.gradle.kts`
|
||||||
|
- غیرفعالسازی با بازیابی فایل پشتیبان یا حذف بلاک بین markerها
|
||||||
|
- رفتار idempotent (اجرای چندباره `active` باعث تکرار بلاک نمیشود)
|
||||||
|
- خروجی رنگی در CLI
|
||||||
|
- لاگگیری از عملیات
|
||||||
|
- سازگار با Linux، macOS و Windows
|
||||||
|
|
||||||
|
## ساختار پروژه
|
||||||
|
|
||||||
|
```text
|
||||||
|
gradle_mirror/
|
||||||
|
__init__.py
|
||||||
|
init.py
|
||||||
|
cli.py
|
||||||
|
mirror_manager.py
|
||||||
|
gradle_locator.py
|
||||||
|
constants.py
|
||||||
|
utils.py
|
||||||
|
main.py
|
||||||
|
pyproject.toml
|
||||||
|
README.md
|
||||||
|
README.fa.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## پیشنیازها
|
||||||
|
|
||||||
|
- Python 3.10 یا بالاتر
|
||||||
|
- pip
|
||||||
|
|
||||||
|
## نصب
|
||||||
|
|
||||||
|
### روش 1 (پیشنهادی): نصب بهصورت ابزار CLI
|
||||||
|
|
||||||
|
در ریشه پروژه اجرا کنید:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m pip install .
|
||||||
|
```
|
||||||
|
|
||||||
|
بعد از نصب:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gradle-mirror --help
|
||||||
|
```
|
||||||
|
|
||||||
|
### روش 2: اجرا بدون نصب
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m gradle_mirror.main --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## استفاده
|
||||||
|
|
||||||
|
### فعالسازی آینهها
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gradle-mirror active
|
||||||
|
```
|
||||||
|
|
||||||
|
نمونه خروجی:
|
||||||
|
|
||||||
|
```text
|
||||||
|
✅ Gradle mirror activated
|
||||||
|
📁 Updated /Users/you/.gradle/init.gradle.kts
|
||||||
|
```
|
||||||
|
|
||||||
|
### غیرفعالسازی آینهها
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gradle-mirror deactive
|
||||||
|
```
|
||||||
|
|
||||||
|
## نحوه کار ابزار
|
||||||
|
|
||||||
|
- مسیر Gradle User Home به این ترتیب تشخیص داده میشود:
|
||||||
|
1. اگر متغیر محیطی `GRADLE_USER_HOME` تنظیم شده باشد، همان استفاده میشود.
|
||||||
|
2. در غیر این صورت، مسیر پیشفرض `~/.gradle` استفاده میشود.
|
||||||
|
- فایل هدف: `<gradle_home>/init.gradle.kts`
|
||||||
|
- markerهای بلاک مدیریتشده:
|
||||||
|
- `/* gradle-mirror-start */`
|
||||||
|
- `/* gradle-mirror-end */`
|
||||||
|
- فایل پشتیبان: `init.gradle.kts.bak`
|
||||||
|
- فایل لاگ: `gradle-mirror.log`
|
||||||
|
|
||||||
|
## نکات مهم
|
||||||
|
|
||||||
|
- فرمان `active` اگر markerها از قبل موجود باشند، محتوای بین آنها را جایگزین میکند (بدون تکرار).
|
||||||
|
- فرمان `deactive` ابتدا تلاش میکند از فایل پشتیبان بازیابی کند.
|
||||||
|
- اگر بعد از حذف بلاک، فایل `init.gradle.kts` خالی شود، فایل حذف میشود.
|
||||||
|
|
||||||
|
## عیبیابی سریع
|
||||||
|
|
||||||
|
- اگر خطای `No module named typer` گرفتید:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m pip install .
|
||||||
|
```
|
||||||
|
|
||||||
|
یا:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m pip install typer
|
||||||
|
```
|
||||||
|
|
||||||
|
- برای بررسی لاگها، فایل زیر را ببینید:
|
||||||
|
|
||||||
|
```text
|
||||||
|
~/.gradle/gradle-mirror.log
|
||||||
|
```
|
||||||
|
|
||||||
|
(اگر `GRADLE_USER_HOME` تنظیم شده باشد، لاگ داخل همان مسیر ساخته میشود.)
|
||||||
99
README.md
Normal file
99
README.md
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
# gradle-mirror
|
||||||
|
|
||||||
|
`gradle-mirror` is a production-quality Python CLI for switching Gradle repository mirrors on and off globally for Android/Gradle projects.
|
||||||
|
|
||||||
|
When enabled, it manages a safe, idempotent block inside your global Gradle init script (`init.gradle.kts`) and enforces mirror repositories for both project dependencies and plugin management.
|
||||||
|
|
||||||
|
## Why this tool
|
||||||
|
|
||||||
|
- One command to activate mirrors globally
|
||||||
|
- One command to restore original behavior
|
||||||
|
- Backup/restore support for safe rollback
|
||||||
|
- Idempotent updates (no duplicated blocks)
|
||||||
|
- Colored console output and action logs
|
||||||
|
- Cross-platform support (Linux, macOS, Windows)
|
||||||
|
|
||||||
|
## Project structure
|
||||||
|
|
||||||
|
```text
|
||||||
|
gradle_mirror/
|
||||||
|
__init__.py
|
||||||
|
init.py
|
||||||
|
cli.py
|
||||||
|
mirror_manager.py
|
||||||
|
gradle_locator.py
|
||||||
|
constants.py
|
||||||
|
utils.py
|
||||||
|
main.py
|
||||||
|
pyproject.toml
|
||||||
|
README.md
|
||||||
|
README.fa.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Python 3.10+
|
||||||
|
- pip
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Install as CLI (recommended)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m pip install .
|
||||||
|
```
|
||||||
|
|
||||||
|
Then verify:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gradle-mirror --help
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run without install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m gradle_mirror.main --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Activate mirrors:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gradle-mirror active
|
||||||
|
```
|
||||||
|
|
||||||
|
Deactivate mirrors:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gradle-mirror deactive
|
||||||
|
```
|
||||||
|
|
||||||
|
## What `active` does
|
||||||
|
|
||||||
|
- Resolves Gradle home from `GRADLE_USER_HOME` or falls back to `~/.gradle`
|
||||||
|
- Targets `<gradle_home>/init.gradle.kts`
|
||||||
|
- Creates backup `init.gradle.kts.bak` if file already exists
|
||||||
|
- Inserts/replaces content between:
|
||||||
|
- `/* gradle-mirror-start */`
|
||||||
|
- `/* gradle-mirror-end */`
|
||||||
|
- Enforces mirror repositories for:
|
||||||
|
- `allprojects.repositories`
|
||||||
|
- `settings.pluginManagement.repositories`
|
||||||
|
|
||||||
|
## What `deactive` does
|
||||||
|
|
||||||
|
- Restores from `init.gradle.kts.bak` when available
|
||||||
|
- Otherwise removes managed marker block from `init.gradle.kts`
|
||||||
|
- Deletes `init.gradle.kts` if it becomes empty
|
||||||
|
|
||||||
|
## Logging
|
||||||
|
|
||||||
|
- Log file: `<gradle_home>/gradle-mirror.log`
|
||||||
|
|
||||||
|
## Example output
|
||||||
|
|
||||||
|
```text
|
||||||
|
✅ Gradle mirror activated
|
||||||
|
📁 Updated /Users/you/.gradle/init.gradle.kts
|
||||||
|
```
|
||||||
4
gradle_mirror/__init__.py
Normal file
4
gradle_mirror/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
"""gradle_mirror package."""
|
||||||
|
|
||||||
|
__all__ = ["__version__"]
|
||||||
|
__version__ = "1.0.0"
|
||||||
52
gradle_mirror/cli.py
Normal file
52
gradle_mirror/cli.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
"""Typer CLI entrypoint for gradle-mirror."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import typer
|
||||||
|
|
||||||
|
from .gradle_locator import detect_gradle_user_home
|
||||||
|
from .mirror_manager import MirrorManager
|
||||||
|
from .utils import setup_logger
|
||||||
|
|
||||||
|
app = typer.Typer(
|
||||||
|
add_completion=False,
|
||||||
|
no_args_is_help=True,
|
||||||
|
help="Activate/deactivate Gradle mirror repositories via init.gradle.kts.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_manager() -> MirrorManager:
|
||||||
|
gradle_home = detect_gradle_user_home()
|
||||||
|
logger = setup_logger(gradle_home)
|
||||||
|
return MirrorManager(gradle_home=gradle_home, logger=logger)
|
||||||
|
|
||||||
|
|
||||||
|
@app.command()
|
||||||
|
def active() -> None:
|
||||||
|
"""Activate global mirror configuration."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
manager = _build_manager()
|
||||||
|
result = manager.activate()
|
||||||
|
typer.secho(f"✅ {result.message}", fg=typer.colors.GREEN)
|
||||||
|
typer.secho(f"📁 Updated {result.init_file}", fg=typer.colors.BRIGHT_BLUE)
|
||||||
|
except Exception as exc: # pragma: no cover - top-level safety
|
||||||
|
typer.secho(f"❌ Failed to activate mirror: {exc}", fg=typer.colors.RED, err=True)
|
||||||
|
raise typer.Exit(code=1) from exc
|
||||||
|
|
||||||
|
|
||||||
|
@app.command()
|
||||||
|
def deactive() -> None:
|
||||||
|
"""Deactivate mirror configuration."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
manager = _build_manager()
|
||||||
|
result = manager.deactivate()
|
||||||
|
color = typer.colors.YELLOW if not result.changed else typer.colors.GREEN
|
||||||
|
emoji = "ℹ️" if not result.changed else "✅"
|
||||||
|
typer.secho(f"{emoji} {result.message}", fg=color)
|
||||||
|
if result.init_file.exists():
|
||||||
|
typer.secho(f"📁 Current file {result.init_file}", fg=typer.colors.BRIGHT_BLUE)
|
||||||
|
except Exception as exc: # pragma: no cover - top-level safety
|
||||||
|
typer.secho(f"❌ Failed to deactivate mirror: {exc}", fg=typer.colors.RED, err=True)
|
||||||
|
raise typer.Exit(code=1) from exc
|
||||||
22
gradle_mirror/constants.py
Normal file
22
gradle_mirror/constants.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
"""Application constants used by gradle_mirror."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
APP_NAME = "gradle-mirror"
|
||||||
|
INIT_FILE_NAME = "init.gradle.kts"
|
||||||
|
BACKUP_SUFFIX = ".bak"
|
||||||
|
LOG_FILE_NAME = "gradle-mirror.log"
|
||||||
|
|
||||||
|
START_MARKER = "/* gradle-mirror-start */"
|
||||||
|
END_MARKER = "/* gradle-mirror-end */"
|
||||||
|
|
||||||
|
MIRROR_REPOSITORIES = [
|
||||||
|
"https://maven.myket.ir/",
|
||||||
|
"https://mirror-maven.runflare.com/maven2/",
|
||||||
|
"https://mirror-maven.runflare.com/android/maven2/",
|
||||||
|
"https://mirror-maven.runflare.com/gradle-plugins/",
|
||||||
|
]
|
||||||
|
|
||||||
|
DEFAULT_GRADLE_HOME = Path.home() / ".gradle"
|
||||||
21
gradle_mirror/gradle_locator.py
Normal file
21
gradle_mirror/gradle_locator.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
"""Gradle home detection utilities."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from .constants import DEFAULT_GRADLE_HOME
|
||||||
|
|
||||||
|
|
||||||
|
def detect_gradle_user_home() -> Path:
|
||||||
|
"""Return Gradle user home using env override when set.
|
||||||
|
|
||||||
|
Gradle honors ``GRADLE_USER_HOME``. If it is absent, default to ``~/.gradle``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
env_home = os.getenv("GRADLE_USER_HOME")
|
||||||
|
if env_home:
|
||||||
|
return Path(env_home).expanduser().resolve()
|
||||||
|
|
||||||
|
return DEFAULT_GRADLE_HOME
|
||||||
8
gradle_mirror/init.py
Normal file
8
gradle_mirror/init.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
"""Compatibility module requested by project structure.
|
||||||
|
|
||||||
|
Use ``gradle_mirror.__init__`` for package metadata.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .__init__ import __version__
|
||||||
|
|
||||||
|
__all__ = ["__version__"]
|
||||||
9
gradle_mirror/main.py
Normal file
9
gradle_mirror/main.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
"""Executable module for `python -m gradle_mirror.main`."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from .cli import app
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app()
|
||||||
176
gradle_mirror/mirror_manager.py
Normal file
176
gradle_mirror/mirror_manager.py
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
"""Core business logic for activating/deactivating Gradle mirrors."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from .constants import (
|
||||||
|
BACKUP_SUFFIX,
|
||||||
|
END_MARKER,
|
||||||
|
INIT_FILE_NAME,
|
||||||
|
START_MARKER,
|
||||||
|
)
|
||||||
|
from .utils import read_text_if_exists, write_text
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class OperationResult:
|
||||||
|
"""Result object returned by mirror operations."""
|
||||||
|
|
||||||
|
changed: bool
|
||||||
|
message: str
|
||||||
|
init_file: Path
|
||||||
|
backup_file: Path
|
||||||
|
|
||||||
|
|
||||||
|
class MirrorManager:
|
||||||
|
"""Manage insertion/removal of mirror repositories in init.gradle.kts."""
|
||||||
|
|
||||||
|
def __init__(self, gradle_home: Path, logger: logging.Logger) -> None:
|
||||||
|
self.gradle_home = gradle_home
|
||||||
|
self.logger = logger
|
||||||
|
self.init_file = self.gradle_home / INIT_FILE_NAME
|
||||||
|
self.backup_file = self.gradle_home / f"{INIT_FILE_NAME}{BACKUP_SUFFIX}"
|
||||||
|
|
||||||
|
def activate(self) -> OperationResult:
|
||||||
|
"""Activate global repository mirrors via init.gradle.kts."""
|
||||||
|
|
||||||
|
self.gradle_home.mkdir(parents=True, exist_ok=True)
|
||||||
|
existing = read_text_if_exists(self.init_file)
|
||||||
|
|
||||||
|
if self.init_file.exists():
|
||||||
|
# Keep the original state so deactivation can restore it exactly.
|
||||||
|
write_text(self.backup_file, existing)
|
||||||
|
self.logger.info("Created backup at %s", self.backup_file)
|
||||||
|
|
||||||
|
block = self._build_marked_block()
|
||||||
|
updated = self._upsert_marked_block(existing, block)
|
||||||
|
|
||||||
|
if updated == existing:
|
||||||
|
self.logger.info("Mirror block already up-to-date: %s", self.init_file)
|
||||||
|
return OperationResult(
|
||||||
|
changed=False,
|
||||||
|
message="Gradle mirror is already active",
|
||||||
|
init_file=self.init_file,
|
||||||
|
backup_file=self.backup_file,
|
||||||
|
)
|
||||||
|
|
||||||
|
write_text(self.init_file, updated)
|
||||||
|
self.logger.info("Activated mirrors in %s", self.init_file)
|
||||||
|
return OperationResult(
|
||||||
|
changed=True,
|
||||||
|
message="Gradle mirror activated",
|
||||||
|
init_file=self.init_file,
|
||||||
|
backup_file=self.backup_file,
|
||||||
|
)
|
||||||
|
|
||||||
|
def deactivate(self) -> OperationResult:
|
||||||
|
"""Deactivate mirror configuration and restore original behavior."""
|
||||||
|
|
||||||
|
if self.backup_file.exists():
|
||||||
|
backup_content = read_text_if_exists(self.backup_file)
|
||||||
|
if backup_content.strip():
|
||||||
|
write_text(self.init_file, backup_content)
|
||||||
|
self.logger.info("Restored init script from backup %s", self.backup_file)
|
||||||
|
else:
|
||||||
|
if self.init_file.exists():
|
||||||
|
self.init_file.unlink()
|
||||||
|
self.logger.info("Deleted empty init script %s", self.init_file)
|
||||||
|
self.backup_file.unlink(missing_ok=True)
|
||||||
|
self.logger.info("Removed backup file %s", self.backup_file)
|
||||||
|
return OperationResult(
|
||||||
|
changed=True,
|
||||||
|
message="Gradle mirror deactivated (restored from backup)",
|
||||||
|
init_file=self.init_file,
|
||||||
|
backup_file=self.backup_file,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not self.init_file.exists():
|
||||||
|
self.logger.info("Nothing to deactivate; %s does not exist", self.init_file)
|
||||||
|
return OperationResult(
|
||||||
|
changed=False,
|
||||||
|
message="Nothing to deactivate",
|
||||||
|
init_file=self.init_file,
|
||||||
|
backup_file=self.backup_file,
|
||||||
|
)
|
||||||
|
|
||||||
|
current = read_text_if_exists(self.init_file)
|
||||||
|
updated = self._remove_marked_block(current)
|
||||||
|
|
||||||
|
if updated == current:
|
||||||
|
self.logger.info("No mirror markers found in %s", self.init_file)
|
||||||
|
return OperationResult(
|
||||||
|
changed=False,
|
||||||
|
message="Mirror markers were not found",
|
||||||
|
init_file=self.init_file,
|
||||||
|
backup_file=self.backup_file,
|
||||||
|
)
|
||||||
|
|
||||||
|
if updated.strip():
|
||||||
|
write_text(self.init_file, updated)
|
||||||
|
self.logger.info("Removed mirror block from %s", self.init_file)
|
||||||
|
else:
|
||||||
|
self.init_file.unlink(missing_ok=True)
|
||||||
|
self.logger.info("Removed empty init script %s", self.init_file)
|
||||||
|
|
||||||
|
return OperationResult(
|
||||||
|
changed=True,
|
||||||
|
message="Gradle mirror deactivated",
|
||||||
|
init_file=self.init_file,
|
||||||
|
backup_file=self.backup_file,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _upsert_marked_block(self, content: str, marked_block: str) -> str:
|
||||||
|
pattern = self._marker_pattern()
|
||||||
|
|
||||||
|
if pattern.search(content):
|
||||||
|
return pattern.sub(marked_block, content)
|
||||||
|
|
||||||
|
base = content.rstrip()
|
||||||
|
if base:
|
||||||
|
return f"{base}\n\n{marked_block}\n"
|
||||||
|
return f"{marked_block}\n"
|
||||||
|
|
||||||
|
def _remove_marked_block(self, content: str) -> str:
|
||||||
|
pattern = self._marker_pattern()
|
||||||
|
removed = pattern.sub("", content)
|
||||||
|
|
||||||
|
# Normalize repeated blank lines left by block removal.
|
||||||
|
removed = re.sub(r"\n{3,}", "\n\n", removed)
|
||||||
|
return removed.strip() + ("\n" if removed.strip() else "")
|
||||||
|
|
||||||
|
def _marker_pattern(self) -> re.Pattern[str]:
|
||||||
|
escaped_start = re.escape(START_MARKER)
|
||||||
|
escaped_end = re.escape(END_MARKER)
|
||||||
|
return re.compile(rf"{escaped_start}[\s\S]*?{escaped_end}", re.MULTILINE)
|
||||||
|
|
||||||
|
def _build_marked_block(self) -> str:
|
||||||
|
script_body = """allprojects {
|
||||||
|
repositories {
|
||||||
|
clear()
|
||||||
|
|
||||||
|
maven { url = uri(\"https://maven.myket.ir/\") }
|
||||||
|
maven { url = uri(\"https://mirror-maven.runflare.com/maven2/\") }
|
||||||
|
maven { url = uri(\"https://mirror-maven.runflare.com/android/maven2/\") }
|
||||||
|
maven { url = uri(\"https://mirror-maven.runflare.com/gradle-plugins/\") }
|
||||||
|
|
||||||
|
mavenCentral()
|
||||||
|
google()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gradle.settingsEvaluated {
|
||||||
|
settings.pluginManagement.repositories.apply {
|
||||||
|
clear()
|
||||||
|
|
||||||
|
maven { url = uri(\"https://maven.myket.ir/\") }
|
||||||
|
maven { url = uri(\"https://mirror-maven.runflare.com/gradle-plugins/\") }
|
||||||
|
|
||||||
|
mavenCentral()
|
||||||
|
google()
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
return f"{START_MARKER}\n{script_body}\n{END_MARKER}"
|
||||||
50
gradle_mirror/utils.py
Normal file
50
gradle_mirror/utils.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
"""General utility helpers for file operations and logging."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from .constants import APP_NAME, LOG_FILE_NAME
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logger(gradle_home: Path) -> logging.Logger:
|
||||||
|
"""Configure and return the application logger.
|
||||||
|
|
||||||
|
Logs are written to ``<gradle_home>/gradle-mirror.log`` and include timestamps.
|
||||||
|
"""
|
||||||
|
|
||||||
|
gradle_home.mkdir(parents=True, exist_ok=True)
|
||||||
|
log_path = gradle_home / LOG_FILE_NAME
|
||||||
|
|
||||||
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
if logger.handlers:
|
||||||
|
return logger
|
||||||
|
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
formatter = logging.Formatter(
|
||||||
|
fmt="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
|
||||||
|
datefmt="%Y-%m-%d %H:%M:%S",
|
||||||
|
)
|
||||||
|
|
||||||
|
file_handler = logging.FileHandler(log_path, encoding="utf-8")
|
||||||
|
file_handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
logger.addHandler(file_handler)
|
||||||
|
logger.propagate = False
|
||||||
|
return logger
|
||||||
|
|
||||||
|
|
||||||
|
def read_text_if_exists(path: Path) -> str:
|
||||||
|
"""Read UTF-8 text if file exists; otherwise return empty string."""
|
||||||
|
|
||||||
|
if not path.exists():
|
||||||
|
return ""
|
||||||
|
return path.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def write_text(path: Path, content: str) -> None:
|
||||||
|
"""Write UTF-8 text content, creating parent folders if needed."""
|
||||||
|
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
path.write_text(content, encoding="utf-8")
|
||||||
22
pyproject.toml
Normal file
22
pyproject.toml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=68", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "gradle-mirror"
|
||||||
|
version = "1.0.0"
|
||||||
|
description = "CLI to activate/deactivate Gradle repository mirrors via init.gradle.kts"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
authors = [
|
||||||
|
{ name = "Your Team" }
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"typer>=0.12,<1.0"
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
gradle-mirror = "gradle_mirror.cli:app"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
packages = ["gradle_mirror"]
|
||||||
Reference in New Issue
Block a user