diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d4d628a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,196 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Release Package + +on: + push: + branches: + - '*' + tags: + - 'v*' + pull_request: + branches: + - '*' + release: + types: [published] + +defaults: + run: + shell: bash + +env: + PYTHON_VERSION: "3.11" +permissions: + contents: read +jobs: + build-sdist-and-wheel: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install Hatch + run: | + python -m pip install --upgrade pip + pip install hatch + + - name: Build package + run: python -m hatch build + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: sdist-and-wheel + path: dist/* + if-no-files-found: error + + + build-binaries: + name: Build binaries for ${{ matrix.job.target }} (${{ matrix.job.os }}) + needs: + - build-sdist-and-wheel + runs-on: ${{ matrix.job.os }} + strategy: + fail-fast: false + matrix: + job: + # Linux + - target: x86_64-unknown-linux-gnu + os: ubuntu-latest + cross: true + release_suffix: x86_64-linux + - target: aarch64-unknown-linux-gnu + os: ubuntu-latest + cross: true + release_suffix: aarch64-linux + # Windows + - target: x86_64-pc-windows-msvc + os: windows-2022 + release_suffix: x86_64-windows + # macOS + - target: aarch64-apple-darwin + os: macos-14 + release_suffix: aarch64-osx + - target: x86_64-apple-darwin + os: macos-12 + release_suffix: x86_64-osx + + env: + PYAPP_PASS_LOCATION: "1" + PYAPP_UV_ENABLED: "1" + HATCH_BUILD_LOCATION: dist + CARGO: cargo + CARGO_BUILD_TARGET: ${{ matrix.job.target }} + PYAPP_REPO: pyapp # Use local copy of pyapp (needed for cross-compiling) + PYAPP_VERSION: v0.23.0 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Clone PyApp + run: git clone --depth 1 --branch $PYAPP_VERSION https://github.com/ofek/pyapp $PYAPP_REPO + + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install Hatch + run: | + python -m pip install --upgrade pip + pip install hatch + + - name: Install Rust toolchain + if: ${{ !matrix.job.cross }} + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.job.target }} + + - name: Set up cross compiling tools + if: matrix.job.cross + uses: taiki-e/setup-cross-toolchain-action@v1 + with: + target: ${{ matrix.job.target}} + + - name: Show toolchain information + run: |- + rustup toolchain list + rustup default + rustup -V + rustc -V + cargo -V + hatch --version + + - name: Get artifact + uses: actions/download-artifact@v4 + with: + name: sdist-and-wheel + path: ${{ github.workspace }}/dist + merge-multiple: true + + - name: Build Binary + working-directory: ${{ github.workspace }} + run: |- + current_version=$(hatch version) + PYAPP_PROJECT_PATH="${{ github.workspace }}/dist/isponsorblocktv-${current_version}-py3-none-any.whl" hatch -v build -t binary + + - name: Rename binary + working-directory: ${{ github.workspace }} + run: |- + mv dist/binary/iSponsorBlockTV* dist/binary/iSponsorBlockTV-${{ matrix.job.release_suffix }} + + - name: Upload built binary package + uses: actions/upload-artifact@v4 + with: + name: binaries-${{ matrix.job.release_suffix }} + path: dist/binary/* + if-no-files-found: error + + + publish-to-pypi: + needs: build-sdist-and-wheel + permissions: + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing + # only run step if the event is a published release + if: github.event_name == 'release' && github.event.action == 'published' + runs-on: ubuntu-latest + steps: + - name: Get artifact + uses: actions/download-artifact@v4 + with: + name: sdist-and-wheel + path: dist + merge-multiple: true + + - name: Publish package + uses: pypa/gh-action-pypi-publish@release/v1 + + + publish-to-release: + needs: + - build-sdist-and-wheel + - build-binaries + if: github.event_name == 'release' && github.event.action == 'published' + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v4 + name: Get artifact + with: + path: dist + merge-multiple: true + - name: Add assets to release + uses: softprops/action-gh-release@v2 + with: + files: dist/* diff --git a/.github/workflows/release_pypi.yml b/.github/workflows/release_pypi.yml deleted file mode 100644 index 180d5b9..0000000 --- a/.github/workflows/release_pypi.yml +++ /dev/null @@ -1,37 +0,0 @@ -# This workflow will upload a Python Package using Twine when a release is created -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries - -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -name: Upload Python Package - -on: - release: - types: [published] - -permissions: - contents: read - id-token: write # IMPORTANT: this permission is mandatory for trusted publishing -jobs: - deploy: - - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install build wheel - - name: Build package - run: python -m build - - name: Publish package - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/requirements.txt b/requirements.txt index e30c014..0b6deb3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,3 +8,4 @@ ssdp==1.3.0 textual==0.58.0 textual-slider==0.1.1 xmltodict==0.13.0 +rich_click==1.8.3 diff --git a/src/iSponsorBlockTV/helpers.py b/src/iSponsorBlockTV/helpers.py index 3bce12c..6b4880e 100644 --- a/src/iSponsorBlockTV/helpers.py +++ b/src/iSponsorBlockTV/helpers.py @@ -1,10 +1,10 @@ -import argparse import json import logging import os import sys import time +import rich_click as click from appdirs import user_data_dir from . import config_setup, main, setup_wizard @@ -126,35 +126,93 @@ class Config: return False -def app_start(): - # If env has a data dir use that, otherwise use the default - default_data_dir = os.getenv("iSPBTV_data_dir") or user_data_dir( - "iSponsorBlockTV", "dmunozv04" - ) - parser = argparse.ArgumentParser(description="iSponsorblockTV") - parser.add_argument( - "--data-dir", "-d", default=default_data_dir, help="data directory" - ) - parser.add_argument( - "--setup", "-s", action="store_true", help="setup the program graphically" - ) - parser.add_argument( - "--setup-cli", - "-sc", - action="store_true", - help="setup the program in the command line", - ) - parser.add_argument("--debug", action="store_true", help="debug mode") - args = parser.parse_args() - - config = Config(args.data_dir) - if args.debug: +@click.group(invoke_without_command=True) +@click.option( + "--data", + "-d", + default=lambda: os.getenv("iSPBTV_data_dir") + or user_data_dir("iSponsorBlockTV", "dmunozv04"), + help="data directory", +) +@click.option("--debug", is_flag=True, help="debug mode") +# legacy commands as arguments +@click.option( + "--setup", is_flag=True, help="Setup the program graphically", hidden=True +) +@click.option( + "--setup-cli", + is_flag=True, + help="Setup the program in the command line", + hidden=True, +) +@click.pass_context +def cli(ctx, data, debug, setup, setup_cli): + """iSponsorblockTV""" + ctx.ensure_object(dict) + ctx.obj["data_dir"] = data + ctx.obj["debug"] = debug + if debug: logging.basicConfig(level=logging.DEBUG) - if args.setup: # Set up the config file graphically - setup_wizard.main(config) - sys.exit() - if args.setup_cli: # Set up the config file - config_setup.main(config, args.debug) - else: - config.validate() - main.main(config, args.debug) + if ctx.invoked_subcommand is None: + if setup: + ctx.invoke(setup_command) + elif setup_cli: + ctx.invoke(setup_cli_command) + else: + ctx.invoke(start) + + +@cli.command() +@click.pass_context +def setup(ctx): + """Setup the program graphically""" + config = Config(ctx.obj["data_dir"]) + setup_wizard.main(config) + sys.exit() + + +setup_command = setup + + +@cli.command() +@click.pass_context +def setup_cli(ctx): + """Setup the program in the command line""" + config = Config(ctx.obj["data_dir"]) + config_setup.main(config, ctx.obj["debug"]) + + +setup_cli_command = setup_cli + + +@cli.command() +@click.pass_context +def start(ctx): + """Start the main program""" + config = Config(ctx.obj["data_dir"]) + config.validate() + main.main(config, ctx.obj["debug"]) + + +# Create fake "self" group to show pyapp options in help menu +# Subcommands remove, restore, update +pyapp_group = click.RichGroup("self", help="pyapp options (update, remove, restore)") +pyapp_group.add_command( + click.RichCommand("update", help="Update the package to the latest version") +) +pyapp_group.add_command( + click.Command( + "remove", help="Remove the package, wiping the installation but not the data" + ) +) +pyapp_group.add_command( + click.RichCommand( + "restore", help="Restore the package to its original state by reinstalling it" + ) +) +if os.getenv("PYAPP"): + cli.add_command(pyapp_group) + + +def app_start(): + cli(obj={})