Skip to content

Commit

Permalink
feat: publish package (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
tnunamak authored and Tim Nunamaker committed Jul 27, 2024
1 parent 99889aa commit bfc972a
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 42 deletions.
131 changes: 114 additions & 17 deletions .github/workflows/package.yml
Original file line number Diff line number Diff line change
@@ -1,36 +1,133 @@
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
- name: Set up Python ${{ matrix.python-version }}
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: googleapis/release-please-action@v4
id: release
with:
token: ${{ secrets.MY_RELEASE_PLEASE_TOKEN }}
release-type: python

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/[email protected]

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/[email protected]
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 }}'
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -24,44 +24,44 @@ pip install -r requirements.txt -r requirements-dev.txt
## Installation

```bash
pip install stringify
pip install rstring
```

## Usage

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"
```

3. Use a saved preset:
```bash
stringify -p my_preset
rstring -p my_preset
```

4. Enter interactive mode:
```bash
stringify -i
rstring -i
```
File renamed without changes.
1 change: 0 additions & 1 deletion stringify/cli.py → rstring/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
File renamed without changes.
File renamed without changes.
28 changes: 14 additions & 14 deletions tests/test_stringify.py → tests/test_rstring.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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']

Expand Down Expand Up @@ -162,18 +162,18 @@ 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))

mock_file_list = ['file1.py', 'file2.py', 'file3.py', 'file4.py', 'file5.py', 'file6.py', 'file7.py']
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)

0 comments on commit bfc972a

Please sign in to comment.