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
36 changes: 36 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: uv sync --dev
- name: Run tests
run: uv run pytest -v

lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
- name: Install dependencies
run: uv sync --dev
- name: Run ruff
run: uv run ruff check src tests
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,6 @@ cython_debug/
.censored/
settings.toml
.vscode/

# Nix build output
result
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ add_pack("https://omid.gg/THC", packs, courses)

`itg-cli` will generate a config file if one is not found at the default
location. This location and the default values vary depending on the platform.
The location of the created config file will be output by after it's created.
The location of the created config file will be printed after it's created.

The `init-config` command is used to write a default config file to a location
of your choice (or the default location if none is supplied). You can supply
Expand Down Expand Up @@ -111,7 +111,7 @@ MacOS: ~/Library/Application Support/ITGMania

This project is my first published/marketed open source project, so I'm still
learning how all this works in practice. That being said, If you run into any bugs
or have ideas for new features, please feel free to create an [issue](https://github.com/lucdar/itg-cli/issues) or [pull request](https://github.com/lucdar/itg-cli/pulls).
or have ideas for new features, please feel free to create an [issue](https://github.com/celex3/itg-cli/issues) or [pull request](https://github.com/celex3/itg-cli/pulls).

## License

Expand Down
27 changes: 27 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

140 changes: 140 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
{
description = "A CLI tool that automates common administrative tasks for ITGmania/StepMania";

inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
};

outputs =
{ self, nixpkgs }:
let
forAllSystems = nixpkgs.lib.genAttrs [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
# Parse the version from __init__.py so it stays in sync
version = builtins.elemAt (builtins.match ".*__version__ = \"([^\"]+)\".*" (
builtins.readFile ./src/itg_cli/__init__.py
)) 0;
in
{
packages = forAllSystems (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
python = pkgs.python312; # itg-cli requires python >=3.10,<3.13

# Dependencies not available in nixpkgs, built from PyPI sdists.
# Hashes come from uv.lock (converted with `nix hash convert`).
msdparser = python.pkgs.buildPythonPackage rec {
pname = "msdparser";
version = "2.0.0";
pyproject = true;
src = python.pkgs.fetchPypi {
inherit pname version;
hash = "sha256-m7bPS3BcdoULHOIoI8dotiuiu2PRwkB7vj4sI+OL6OM=";
};
build-system = [ python.pkgs.setuptools ];
# sdist ships no tests
doCheck = false;
};

simfile = python.pkgs.buildPythonPackage rec {
pname = "simfile";
version = "2.1.1";
pyproject = true;
src = python.pkgs.fetchPypi {
inherit pname version;
hash = "sha256-p4hfg5XN0PGc152jTlZ12neKoOjdp2VDzwdeIbhixLc=";
};
build-system = [ python.pkgs.setuptools ];
dependencies = [
msdparser
python.pkgs.fs
];
# tests require pyfakefs test fixtures not shipped in the sdist
doCheck = false;
};

pyrfc6266 = python.pkgs.buildPythonPackage rec {
pname = "pyrfc6266";
version = "1.0.2";
pyproject = true;
src = python.pkgs.fetchPypi {
inherit pname version;
hash = "sha256-PEFha2ofLpom338AX7qmNPlgEhdpzMREWs+0BOn4/Uw=";
};
build-system = [ python.pkgs.setuptools ];
dependencies = [ python.pkgs.pyparsing ];
# upstream pins pyparsing~=3.0.7; newer versions work fine
pythonRelaxDeps = [ "pyparsing" ];
doCheck = false;
};

itg-cli = python.pkgs.buildPythonApplication {
pname = "itg-cli";
inherit version;
pyproject = true;
src = ./.;

build-system = [ python.pkgs.setuptools ];

dependencies =
[
simfile
pyrfc6266
]
++ (with python.pkgs; [
gdown
requests
rich
setuptools
tomlkit
tqdm
typer
]);

# nixpkgs ships setuptools >=80; the <80 pin only matters for
# unpatched PyPI installs (pkg_resources compatibility)
pythonRelaxDeps = [ "setuptools" ];

# fs emits a pkg_resources deprecation warning on import with
# setuptools >=81; silence it so every CLI run isn't noisy
makeWrapperArgs = [
"--set-default PYTHONWARNINGS ignore::UserWarning:fs"
];

nativeCheckInputs = [ python.pkgs.pytestCheckHook ];

meta = {
description = "A CLI tool that automates common administrative tasks for ITGmania/StepMania";
homepage = "https://github.com/celex3/itg-cli";
license = nixpkgs.lib.licenses.mit;
mainProgram = "itg-cli";
};
};
in
{
default = itg-cli;
inherit itg-cli;
}
);

devShells = forAllSystems (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
default = pkgs.mkShell {
packages = [
pkgs.uv
pkgs.python312
];
};
}
);
};
}
23 changes: 20 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ dependencies = [
"simfile==2.1.1",
"gdown>=5.2.0",
"pyrfc6266>=1.0.2",
"requests>=2.32.0",
"rich>=13.0.0",
"tomlkit>=0.13.2",
"tqdm>=4.66.0",
"typer>=0.12.5",
"setuptools<80.0.0",
]
requires-python = ">=3.10"
authors = [{name = "Lucas Clark"}]
requires-python = ">=3.10,<3.13"
authors = [{name = "Celeste Clark"}]
keywords = ["itg", "cli", "python", "itgmania", "stepmania"]
classifiers = [
"Development Status :: 3 - Alpha",
Expand All @@ -20,7 +24,7 @@ readme = "README.md"
license = {file = "LICENSE"}

[project.urls]
Repository = "https://github.com/lucdar/itg-cli"
Repository = "https://github.com/celex3/itg-cli"

[build-system]
requires = ["setuptools"]
Expand All @@ -34,3 +38,16 @@ version = {attr = "itg_cli.__version__"}

[tool.setuptools.package-data]
itg_cli = ["config_template.toml"]

[dependency-groups]
dev = [
"pytest>=8.0",
"ruff>=0.8",
]

[tool.ruff]
line-length = 79
src = ["src"]

[tool.pytest.ini_options]
testpaths = ["tests"]
6 changes: 3 additions & 3 deletions src/itg_cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from itg_cli.commands import (
OverwriteException,
UncensorException,
add_pack,
add_song,
censor,
get_censored,
uncensor,
OverwriteException,
UncensorException,
)

__all__ = [
Expand All @@ -17,4 +17,4 @@
"OverwriteException",
"UncensorException",
]
__version__ = "1.0.4"
__version__ = "1.0.8"
21 changes: 15 additions & 6 deletions src/itg_cli/__main__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import sys
import typer
from pathlib import Path
from rich.columns import Columns
Expand All @@ -8,8 +7,15 @@
from simfile.dir import SimfilePack
from simfile.types import Simfile
from typing import Annotated, Callable, Optional, TypeAlias, TypeVar
from itg_cli import *
from itg_cli import __version__
from itg_cli import (
OverwriteException,
UncensorException,
__version__,
add_pack,
add_song,
censor,
uncensor,
)
from itg_cli._config import CLISettings

DEFAULT_CONFIG_PATH = Path(typer.get_app_dir("itg-cli")) / "config.toml"
Expand Down Expand Up @@ -69,7 +75,6 @@ def song_overwrite_handler(
help="automatically overwrite without confirming",
),
]
cli = typer.Typer(no_args_is_help=True)


## Version Flag ##
Expand All @@ -81,6 +86,7 @@ def version_callback(run: bool):

@cli.callback()
def typer_entry(
ctx: typer.Context,
version: Annotated[
bool,
typer.Option(
Expand All @@ -92,7 +98,10 @@ def typer_entry(
] = False,
):
# Initialize config if it doesn't exist
if not DEFAULT_CONFIG_PATH.exists() and "init-config" not in sys.argv:
if (
not DEFAULT_CONFIG_PATH.exists()
and ctx.invoked_subcommand != "init-config"
):
init_config_command(DEFAULT_CONFIG_PATH)


Expand All @@ -110,7 +119,7 @@ def init_config_command(
path.exists()
and not overwrite
and not Confirm.ask(
f"Overwrite existing config with default?", default=True
"Overwrite existing config with default?", default=False
)
):
print(f"[green]Keeping existing config file: [bright_white]{path}")
Expand Down
9 changes: 7 additions & 2 deletions src/itg_cli/_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def __init__(self, toml: Path, write_default: bool = False):
raise ConfigError(f"Missing table ({table}) in config: {self.location}")
required = toml_doc["required"]
for key in ["root", "singles_pack_name", "delete_macos_files"]:
if required.get(key) is None:
if required.get(key) is None or required.get(key) == "":
raise ConfigError(
f"Required field ({key}) is empty or unbound in config: {self.location}"
)
Expand Down Expand Up @@ -79,7 +79,12 @@ def __write_default_toml(self, toml: Path) -> None:
root, cache = None, None
match platform.system():
case "Windows":
root = Path(os.getenv("APPDATA")) / "ITGmania"
appdata = os.getenv("APPDATA")
if not appdata:
raise ConfigError(
"APPDATA environment variable is not set."
)
root = Path(appdata) / "ITGmania"
case "Linux":
# TODO: verify/add new locations
root = Path.home() / ".itgmania"
Expand Down
Loading
Loading