Skip to content

Commit 25240e4

Browse files
author
Andrey Danilov
committed
Refactor modules
1 parent f2b9b04 commit 25240e4

20 files changed

Lines changed: 704 additions & 447 deletions

.github/workflows/publish.yml

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
name: Publish Python Package
2+
3+
on:
4+
push:
5+
tags:
6+
- '*'
7+
8+
jobs:
9+
build:
10+
name: Build Distribution
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- uses: actions/checkout@v4
15+
- name: Setup Python
16+
uses: actions/setup-python@v5
17+
with:
18+
python-version: "3.11"
19+
- name: Upgrade `pip`
20+
run: pip install --upgrade pip
21+
- name: Install Build Tool
22+
run: python -m pip install build --user
23+
- name: Install Wheel
24+
run: python -m pip install wheel --user
25+
- name: Install Dependencies
26+
run: pip install -r requirements.txt
27+
- name: Generate Wheel
28+
run: python setup.py bdist_wheel
29+
- name: Upload Artifacts
30+
uses: actions/upload-artifact@v4
31+
with:
32+
name: python-package-distributions
33+
path: dist/
34+
35+
publish-to-pypi:
36+
name: Publish to PyPI
37+
needs:
38+
- build
39+
runs-on: ubuntu-latest
40+
environment:
41+
name: pypi
42+
url: https://pypi.org/p/inaSpeechSegmenter-client
43+
permissions:
44+
id-token: write
45+
46+
steps:
47+
- name: Download Artifacts
48+
uses: actions/download-artifact@v4
49+
with:
50+
name: python-package-distributions
51+
path: dist/
52+
- name: Publish to PyPI
53+
uses: pypa/gh-action-pypi-publish@release/v1
54+
55+
github-release:
56+
name: Sign and Release on GitHub
57+
needs:
58+
- build
59+
runs-on: ubuntu-latest
60+
61+
permissions:
62+
contents: write
63+
id-token: write
64+
65+
steps:
66+
- name: Download Artifacts
67+
uses: actions/download-artifact@v4
68+
with:
69+
name: python-package-distributions
70+
path: dist/
71+
- name: Sign Artifacts
72+
uses: sigstore/gh-action-sigstore-python@v3.0.0
73+
with:
74+
inputs: ./dist/*.whl
75+
- name: Create GitHub Release
76+
env:
77+
GITHUB_TOKEN: ${{ github.token }}
78+
run: >-
79+
gh release create
80+
'${{ github.ref_name }}'
81+
--repo '${{ github.repository }}'
82+
--notes ''
83+
- name: Upload to GitHub Release
84+
env:
85+
GITHUB_TOKEN: ${{ github.token }}
86+
run: >-
87+
gh release upload
88+
'${{ github.ref_name }}'
89+
dist/**
90+
--repo '${{ github.repository }}'

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
/.venv/
22
**/__pycache__/
3+
/*.egg-info/
4+
/dist/

.vscode/launch.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
"name": "Debug",
6+
"type": "debugpy",
7+
"request": "launch",
8+
"program": "code4swipe.py",
9+
"console": "integratedTerminal"
10+
}
11+
]
12+
}

README.md

Lines changed: 30 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# `code4swipe`: The Dopamine CLI for Coders
22

3-
`code4swipe` is a command-line interface (CLI) tool that gamifies your coding process by rewarding you with an automatic "swipe up" action (like the satisfying gesture on TikTok or other social media feeds) whenever you introduce new changes to your local git repository.
3+
`code4swipe` is a CLI tool that gamifies your coding process by rewarding you with an automatic "swipe up" action (like the satisfying gesture on TikTok or other social media feeds) whenever you introduce new changes to your local git repository.
44

55
It monitors your current working directory's `git diff` and triggers a **swipe** via a chosen provider (currently, **ADB** for Android devices) to give you a quick, satisfying break or a virtual "pat on the back" for your new lines of code.
66

@@ -9,7 +9,7 @@ It monitors your current working directory's `git diff` and triggers a **swipe**
99
- **Git Diff Monitoring:** Continuously monitors your local repository's uncommitted changes.
1010
- **Two Detection Strategies:**
1111
- **`exact` (Default):** Triggers on **ANY** change to the git diff (additions, deletions, modifications).
12-
- **`linecount`:** Triggers only if the total number of lines in the git diff has **increased** since the last check (focused on rewarding additions).
12+
- **`linecount`:** Triggers only if the total number of lines in the git diff has changed.
1313
- **ADB Swipe Provider:** Uses the **Android Debug Bridge (ADB)** to execute a simulated "swipe up" on a connectedAndroid device.
1414
- **Customizable:** Set your preferred repository path and polling interval.
1515

@@ -29,72 +29,41 @@ Before installation, ensure you have the following ready:
2929

3030
## Installation
3131

32-
You can install `code4swipe` either globally for easy access or within a Python Virtual Environment (`.venv`) for isolated usage.
32+
### Install Globally
3333

34-
### 1\. Global Installation (Recommended for quick use)
35-
36-
The script depends on the `click` package for its CLI functionality.
34+
To install `code4swipe`, use the following command:
3735

3836
```bash
39-
# Install the required package
40-
pip install click
37+
pip install code4swipe
4138
```
4239

43-
For a global installation, copy the script file (let's assume you save it as `code4swipe.py`) and make it executable.
40+
This will install the package and its dependencies, making the `code4swipe` command available globally.
4441

45-
1. **Save the script:**
42+
### Install in Virtual Environment
4643

47-
```bash
48-
# Save the script contents to a file
49-
wget -O code4swipe.py https://raw.githubusercontent.com/Danand/code4swipe/refs/heads/main/code4swipe.md
50-
# OR
51-
# Create the file and paste the content
52-
```
44+
Using a virtual environment is the recommended way to manage Python dependencies for development.
5345

54-
2. **Make it executable:**
46+
1. Clone and `cd` in repo.
5547

56-
```bash
57-
chmod +x code4swipe.py
58-
```
59-
60-
3. **Move to a PATH directory:**
48+
2. **Create and activate the environment:**
6149

6250
```bash
63-
# For Linux/macOS
64-
sudo mv code4swipe.py /usr/local/bin/code4swipe
65-
# For Windows, you'd place it in a directory listed in your PATH and rename
66-
# it, or call it directly with `python code4swipe.py`.
51+
python3.11 -m venv .venv
52+
source .venv/bin/activate # On Windows, use: .venv\Scripts\activate
6753
```
6854

69-
Now you can run it from any directory using the command `code4swipe`.
70-
71-
### 3\. Virtual Environment (`.venv`) Installation
72-
73-
Using a virtual environment is the recommended way to manage Python dependencies for development.
74-
75-
1. **Create and activate the environment:**
55+
3. **Install requirements:**
7656

7757
```bash
78-
python3 -m venv .venv
79-
source .venv/bin/activate # On Windows, use: .venv\Scripts\activate
58+
pip install -r requirements.txt
8059
```
8160

82-
2. **Install requirements:**
61+
4. **Make script executable:**
8362

8463
```bash
85-
pip install click
64+
chmod +x code4swipe.py
8665
```
8766

88-
3. **Save the script:** Save the script contents as `code4swipe.py` inside your project directory.
89-
90-
4. **Run the script:**
91-
92-
```bash
93-
python code4swipe.py
94-
```
95-
96-
*(You'll need to run this command from the directory containing `code4swipe.py` or specify the full path.)*
97-
9867
-----
9968

10069
## Usage
@@ -105,18 +74,17 @@ Run the tool from within the git repository you want to monitor.
10574

10675
The simplest way to run `code4swipe` is from the root of your git repository.
10776

77+
If installed **globally**:
78+
10879
```bash
109-
code4swipe
110-
# OR if using venv:
111-
python code4swipe.py
80+
python3 -m code4swipe
11281
```
11382

114-
This will:
83+
If installed in **virtual environment**:
11584

116-
- Monitor the current directory (`--repo .`).
117-
- Use the `adb` provider (`--provider adb`).
118-
- Use the `exact` change detection strategy (`--changes exact`).
119-
- Check for changes every 5 seconds (`--poll-interval 5.0`).
85+
```bash
86+
./code4swipe.py --repo /path/to/my/other/project
87+
```
12088

12189
### Options
12290

@@ -130,22 +98,22 @@ This will:
13098

13199
### Examples
132100

133-
#### 1\. Reward only for code additions (linecount strategy)
101+
#### Reward only for code additions (linecount strategy)
134102

135103
```bash
136-
code4swipe --changes linecount
104+
python3 -m code4swipe --changes linecount
137105
```
138106

139-
#### 2\. Monitor a different repository with a longer interval
107+
#### Monitor a different repository with a longer interval
140108

141109
```bash
142-
code4swipe --repo /path/to/my/other/project --poll-interval 10
110+
python3 -m code4swipe --repo /path/to/my/other/project --poll-interval 10
143111
```
144112

145-
#### 3\. Debug ADB connection issues
113+
#### Debug ADB connection issues
146114

147115
```bash
148-
code4swipe --verbose
116+
python3 -m code4swipe --verbose
149117
```
150118

151119
-----
@@ -174,4 +142,4 @@ If you see this warning, your Python version is too old. You must upgrade to **P
174142

175143
1. **Check your device:** Make sure the Android device is unlocked and the screen is on, ready to receive input.
176144
2. **Check the git diff:** The script only reacts to uncommitted changes. Try making a small change to a file in the monitored repo. Run `git diff` manually to confirm you have changes.
177-
3. **Check the strategy:** If using `--changes linecount`, ensure your new code *increases* the total line count of the diff (i.e., you haven't just deleted lines).
145+
3. **Check the strategy:** If using `--changes linecount`, ensure your new code changes the total line count of the diff.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import abc
2+
import pathlib
3+
import typing
4+
5+
class GitDiffChangesDetectorBase(abc.ABC):
6+
"""
7+
Abstract base class for all git diff change detectors.
8+
9+
Defines the contract for determining if a "rewardable" amount
10+
of new work has occurred since the last check.
11+
"""
12+
13+
def __init__(self, repo_path: pathlib.Path) -> None:
14+
"""Initializes the detector with the repository path."""
15+
self.repo_path = repo_path
16+
self._last_state = self.get_current_state()
17+
18+
@abc.abstractmethod
19+
def get_current_state(self) -> typing.Any:
20+
"""Calculates and returns the current state of the diff."""
21+
raise NotImplementedError
22+
23+
@abc.abstractmethod
24+
def check_for_new_work(self) -> bool:
25+
"""
26+
Checks for new work, updates the internal state, and returns True
27+
if a reward should be triggered.
28+
"""
29+
raise NotImplementedError
30+
31+
@property
32+
def initial_status_message(self) -> str:
33+
"""Returns a string describing the initial state."""
34+
return f"Initial state: {self._last_state}. Waiting for new code..."

base/git_diff_provider_base.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import abc
2+
import pathlib
3+
4+
class GitDiffProviderBase(abc.ABC):
5+
"""
6+
Abstract base class for providing git diffs.
7+
"""
8+
9+
@abc.abstractmethod
10+
def get_current_git_diff(self, repo_path: pathlib.Path) -> str:
11+
"""
12+
Runs git diff and returns the entire output string.
13+
14+
Args:
15+
repo_path: The path to the repository.
16+
17+
Returns:
18+
The full output of git diff as a string.
19+
"""
20+
raise NotImplementedError

base/impl_factory_base.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import abc
2+
import typing
3+
4+
from enum import StrEnum
5+
6+
import click
7+
8+
TBaseClass = typing.TypeVar("TBaseClass", bound=abc.ABC)
9+
TImplName = typing.TypeVar("TImplName", bound=StrEnum)
10+
11+
class ImplFactoryBase(abc.ABC, typing.Generic[TBaseClass, TImplName]):
12+
"""
13+
Base class for implementation factories.
14+
"""
15+
16+
@property
17+
@abc.abstractmethod
18+
def impl_name_class(self) -> type[TImplName]:
19+
raise NotImplementedError
20+
21+
@abc.abstractmethod
22+
def get_impl_map(self) -> typing.Dict[TImplName, typing.Callable[[], TBaseClass]]:
23+
"""
24+
Returns a map of implementation names to their factory functions.
25+
"""
26+
raise NotImplementedError
27+
28+
def get_impl_instance(self, impl_name_str: str) -> TBaseClass:
29+
"""
30+
Gets an instance of the implementation based on the provided name.
31+
32+
Args:
33+
impl_name: The name of the implementation to retrieve.
34+
35+
Returns:
36+
An instance of the implementation.
37+
38+
Raises:
39+
click.Abort: If the implementation name is not found.
40+
"""
41+
impl_map = self.get_impl_map()
42+
43+
impl_name = self.impl_name_class(impl_name_str.lower())
44+
45+
instance_factory = impl_map.get(impl_name)
46+
47+
if not instance_factory:
48+
click.echo(
49+
f"Error: Unknown implementation {impl_name}. "
50+
f"Available: {self.get_instance_names_list()}",
51+
err=True,
52+
)
53+
54+
raise click.Abort()
55+
56+
return instance_factory()
57+
58+
def get_instance_names_list(self) -> typing.List[str]:
59+
"""
60+
Returns a list of available implementation names.
61+
"""
62+
return [name.value for name in self.get_impl_map().keys()]

0 commit comments

Comments
 (0)