diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index ad45b04..7d4ada0 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -1,18 +1,34 @@ -name: Python package +name: CI/CD for Rstring on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] + release: + types: [published] + workflow_dispatch: -jobs: - build: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + dependency-review: runs-on: ubuntu-latest + steps: + - name: 'Checkout Repository' + uses: actions/checkout@v4 + - name: 'Dependency Review' + uses: actions/dependency-review-action@v4 + + test: + runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ["3.10", "3.11", "3.12"] +# os: [ubuntu-latest, windows-latest, macos-latest] + os: [ubuntu-latest, macos-latest] + python-version: [3.11, 3.12] steps: - uses: actions/checkout@v4 @@ -20,17 +36,99 @@ jobs: uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + - name: Cache pip packages + uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt', '**/requirements-dev.txt') }} + restore-keys: | + ${{ runner.os }}-pip- - name: Install dependencies run: | python -m pip install --upgrade pip - pip install flake8 pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - name: Test with pytest - run: | - pytest + pip install -r requirements.txt + pip install -r requirements-dev.txt + - name: Run tests + run: pytest + + release-please: + needs: test + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + outputs: + release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} + + steps: + - uses: google-github-actions/release-please-action@v4 + id: release + with: + token: ${{ secrets.GITHUB_TOKEN }} + release-type: python + package-name: rstring + + build-and-publish: + needs: [test, release-please] + if: needs.release-please.outputs.release_created + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/rstring + permissions: + id-token: write + contents: write + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Install pypa/build + run: python3 -m pip install build --user + - name: Build a binary wheel and a source tarball + run: python3 -m build + - name: Store the distribution packages + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution 📦 to PyPI + uses: pypa/gh-action-pypi-publish@v1.8.11 + + github-release: + needs: [release-please, build-and-publish] + if: needs.release-please.outputs.release_created + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + + steps: + - uses: actions/checkout@v4 + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Sign the dists with Sigstore + uses: sigstore/gh-action-sigstore-python@v2.1.3 + with: + inputs: >- + ./dist/*.tar.gz + ./dist/*.whl + - name: Create GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + run: >- + gh release create + '${{ needs.release-please.outputs.tag_name }}' + --repo '${{ github.repository }}' + --notes "" + - name: Upload artifact signatures to GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + run: >- + gh release upload + '${{ needs.release-please.outputs.tag_name }}' dist/** + --repo '${{ github.repository }}' diff --git a/README.md b/README.md index db4191d..34b0e42 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# Stringify +# Rstring -Stringify is a developer-friendly tool that uses rsync to gather and stringify code and other text from specified files and directories. It offers features like preset management, interactive mode, and easy clipboard integration. +Rstring is a developer-friendly tool that uses [Rsync](https://linux.die.net/man/1/rsync) to gather and stringify code and other text from specified files and directories. It offers features like preset management, interactive mode, and easy clipboard integration. ## Features -- Use [rsync](https://linux.die.net/man/1/rsync) to efficiently filter files and gather their contents +- Use Rsync to efficiently filter files and gather their contents - Save and manage presets for quick access to common configurations - Interactive mode for fine-tuning file selection - Automatic clipboard copying (with option to disable) @@ -24,7 +24,7 @@ pip install -r requirements.txt -r requirements-dev.txt ## Installation ```bash -pip install stringify +pip install rstring ``` ## Usage @@ -32,25 +32,25 @@ pip install stringify Basic usage: ```bash -stringify [rsync_options] +rstring [rsync_options] ``` For more options: ```bash -stringify --help +rstring --help ``` ## Examples 1. Stringify all Python files in the current directory: ```bash - stringify --include="*.py" --exclude="*" . + rstring --include="*.py" --exclude="*" . ``` 2. Stringify a Next.js project and save a preset: ```bash - stringify --save-as-preset="nextjs" --include="*.js" --include="*.jsx" --include="*.ts" --include="*.tsx" \ + rstring --save-as-preset="nextjs" --include="*.js" --include="*.jsx" --include="*.ts" --include="*.tsx" \ --exclude="node_modules" --exclude=".next" --exclude=".vercel" \ --exclude=".git" --exclude=".vscode" --exclude=".idea" --exclude=".DS_Store" \ --exclude="*.log" @@ -58,10 +58,10 @@ stringify --help 3. Use a saved preset: ```bash - stringify -p my_preset + rstring -p my_preset ``` 4. Enter interactive mode: ```bash - stringify -i + rstring -i ``` diff --git a/stringify/__init__.py b/rstring/__init__.py similarity index 100% rename from stringify/__init__.py rename to rstring/__init__.py diff --git a/stringify/cli.py b/rstring/cli.py similarity index 98% rename from stringify/cli.py rename to rstring/cli.py index 3e293df..f49de43 100644 --- a/stringify/cli.py +++ b/rstring/cli.py @@ -17,7 +17,6 @@ def main(): print("Error: rsync is not installed on this system. Please install rsync and try again.") return - # Use allow_abbrev=False because rsync options can look like abbreviated stringify options parser = argparse.ArgumentParser(description="Stringify code with rsync and manage presets.", allow_abbrev=False) parser.add_argument("-p", "--preset", help="Use a saved preset") parser.add_argument("-sp", "--save-preset", nargs=2, metavar=("NAME", "ARGS"), help="Save a new preset") diff --git a/stringify/default_presets.yaml b/rstring/default_presets.yaml similarity index 100% rename from stringify/default_presets.yaml rename to rstring/default_presets.yaml diff --git a/stringify/utils.py b/rstring/utils.py similarity index 100% rename from stringify/utils.py rename to rstring/utils.py diff --git a/tests/test_stringify.py b/tests/test_rstring.py similarity index 85% rename from tests/test_stringify.py rename to tests/test_rstring.py index 99615a8..800a243 100644 --- a/tests/test_stringify.py +++ b/tests/test_rstring.py @@ -3,14 +3,14 @@ import pytest import yaml -from stringify import utils, cli +from rstring import utils, cli @pytest.fixture def temp_config(tmp_path): - config_file = tmp_path / '.stringify.yaml' + config_file = tmp_path / '.rstring.yaml' config_file.touch() - with patch('stringify.utils.PRESETS_FILE', str(config_file)): + with patch('rstring.utils.PRESETS_FILE', str(config_file)): yield config_file if config_file.exists(): config_file.unlink() @@ -49,8 +49,8 @@ def test_load_presets_file_not_found(temp_config): temp_config.unlink() mock_default_content = yaml.dump({'default_preset': {'args': ['--include=*.py']}}) - with patch('stringify.utils.PRESETS_FILE', 'nonexistent_file'): - with patch('stringify.utils.DEFAULT_PRESETS_FILE', 'default_presets.yaml'): + with patch('rstring.utils.PRESETS_FILE', 'nonexistent_file'): + with patch('rstring.utils.DEFAULT_PRESETS_FILE', 'default_presets.yaml'): with patch('builtins.open', mock_open(read_data=mock_default_content)) as mock_file: presets = utils.load_presets() @@ -79,7 +79,7 @@ def test_run_rsync(): def test_validate_rsync_args(): - with patch('stringify.utils.run_rsync') as mock_run_rsync: + with patch('rstring.utils.run_rsync') as mock_run_rsync: mock_run_rsync.return_value = ["file1.py", "file2.py"] assert utils.validate_rsync_args(["--include=*.py", "."]) == True @@ -107,7 +107,7 @@ def mock_open_file(filename, *args, **kwargs): return mock_open(read_data=file_contents[filename])() with patch('builtins.open', side_effect=mock_open_file): - with patch('stringify.utils.is_binary', return_value=False): + with patch('rstring.utils.is_binary', return_value=False): with patch('os.path.isfile', return_value=True): result = utils.gather_code(file_list) assert "--- /path/to/file1.py ---" in result @@ -119,8 +119,8 @@ def mock_open_file(filename, *args, **kwargs): def test_interactive_mode(): with patch('builtins.input') as mock_input: mock_input.side_effect = ['a', '*.txt', 'd'] - with patch('stringify.utils.validate_rsync_args', return_value=True): - with patch('stringify.utils.run_rsync', return_value=['file1.txt']): + with patch('rstring.utils.validate_rsync_args', return_value=True): + with patch('rstring.utils.run_rsync', return_value=['file1.txt']): result = utils.interactive_mode(['--include=*.py']) assert result == ['--include=*.py', '--include', '*.txt'] @@ -162,7 +162,7 @@ def test_copy_to_clipboard(system, command): def test_main(temp_config): - test_args = ['stringify', '--preset', 'test_preset'] + test_args = ['rstring', '--preset', 'test_preset'] test_preset = {'test_preset': {'args': ['--include=*.py']}} temp_config.write_text(yaml.dump(test_preset)) @@ -170,10 +170,10 @@ def test_main(temp_config): mock_gathered_code = 'print("Hello")\n' * 26 with patch('sys.argv', test_args): - with patch('stringify.utils.check_rsync', return_value=True): - with patch('stringify.cli.run_rsync', return_value=mock_file_list): - with patch('stringify.cli.gather_code', return_value=mock_gathered_code): - with patch('stringify.cli.copy_to_clipboard') as mock_copy: + with patch('rstring.utils.check_rsync', return_value=True): + with patch('rstring.cli.run_rsync', return_value=mock_file_list): + with patch('rstring.cli.gather_code', return_value=mock_gathered_code): + with patch('rstring.cli.copy_to_clipboard') as mock_copy: cli.main() mock_copy.assert_called_once() mock_copy.assert_called_with(mock_gathered_code, mock_file_list, 7)