Metadata-Version: 2.4
Name: diffwatch
Version: 1.0.0
Summary: File & directory watcher with line-level, word-level, and JSON patch diffs plus a CLI tool
Author: prabhay759
License: MIT
Project-URL: Homepage, https://github.com/prabhay759/diffwatch
Project-URL: Repository, https://github.com/prabhay759/diffwatch
Project-URL: Issues, https://github.com/prabhay759/diffwatch/issues
Keywords: diff,watch,file-watcher,json-patch,cli,devtools
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Utilities
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Dynamic: license-file

# diffwatch

> Watch any file or directory for changes and get structured diffs — line-level, word-level, JSON Patch (RFC 6902), callbacks, and a CLI tool. Zero dependencies.

[![PyPI version](https://img.shields.io/pypi/v/diffwatch.svg)](https://pypi.org/project/diffwatch/)
[![Python](https://img.shields.io/pypi/pyversions/diffwatch.svg)](https://pypi.org/project/diffwatch/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

---

## Installation

```bash
pip install diffwatch
```

No dependencies. Requires Python 3.8+.

---

## Quick Start

```python
from diffwatch import DiffWatch

watcher = DiffWatch()

@watcher.on_change
def handle(event):
    print(f"{event.type}: {event.path}")
    print(event.diff.unified_diff())

watcher.watch("config.yaml").start(block=True)
```

---

## CLI

```bash
# Watch a file with unified diff output (default)
diffwatch config.yaml

# Watch a directory recursively, only .py files
diffwatch ./src --recursive --ext .py

# Word-level inline diff
diffwatch README.md --format word

# JSON Patch output for JSON files
diffwatch settings.json --format json-patch

# Summary only (lines added/removed)
diffwatch . --recursive --format summary
```

### CLI Options

| Option | Description |
|---|---|
| `--format` | `unified` (default), `line`, `word`, `json-patch`, `summary` |
| `--recursive`, `-r` | Watch directories recursively |
| `--ext .py .yaml` | Only watch files with these extensions |
| `--interval 0.5` | Polling interval in seconds |
| `--context 3` | Context lines for unified/line diff |

---

## Python API

### Watch files and directories

```python
from diffwatch import DiffWatch

watcher = DiffWatch(interval=1.0)

# Watch a single file
watcher.watch("config.yaml")

# Watch a directory (non-recursive)
watcher.watch("./configs")

# Watch recursively, only .json files
watcher.watch("./src", recursive=True, extensions=[".json", ".yaml"])

watcher.start()   # background thread
watcher.stop()    # graceful stop
watcher.start(block=True)  # block until Ctrl+C
```

### Callbacks

```python
# Any change (modified, created, deleted)
@watcher.on_change
def on_any(event):
    print(event.type, event.path)

# Creation only
@watcher.on_create
def on_create(event):
    print("New file:", event.path)

# Deletion only
@watcher.on_delete
def on_delete(event):
    print("Deleted:", event.path)

# Register without decorator
watcher.on_change(my_callback)
```

### Diff formats

```python
diff = event.diff  # DiffResult object

# Unified diff string (like diff -u)
print(diff.unified_diff(context=3))

# Structured line diff
for chunk in diff.line_diff():
    if chunk["type"] != "equal":
        print(chunk["type"], chunk["old_lines"], "→", chunk["new_lines"])

# Structured word diff
for token in diff.word_diff():
    print(token["type"], repr(token["value"]))

# Inline word diff string
from diffwatch.diff import word_diff_inline
print(word_diff_inline(old, new))
# "Hello [-world-] [+Python+]!"

# JSON Patch (RFC 6902) — only for JSON files
ops = diff.json_patch()
# [{"op": "replace", "path": "/port", "value": 9090}]

# Quick summary
print(diff.summary())
# {"path": "...", "lines_added": 3, "lines_removed": 1, ...}
```

### One-shot check (no background thread)

```python
events = watcher.check_once()
for event in events:
    print(event.type, event.path)
```

---

## API Reference

### `DiffWatch`

| Method | Description |
|---|---|
| `watch(path, recursive, extensions)` | Register a file or directory |
| `on_change(func)` | Callback for any change (decorator or direct) |
| `on_create(func)` | Callback for file creation |
| `on_delete(func)` | Callback for file deletion |
| `start(block=False)` | Start background watcher thread |
| `stop()` | Stop the watcher |
| `check_once()` | Poll once and return list of FileEvents |

### `FileEvent`

| Attribute | Type | Description |
|---|---|---|
| `type` | `str` | `"modified"`, `"created"`, or `"deleted"` |
| `path` | `str` | Absolute path of the changed file |
| `diff` | `DiffResult` | Diff result with old and new content |
| `occurred_at` | `datetime` | When the change was detected |

### `DiffResult`

| Method | Returns | Description |
|---|---|---|
| `line_diff(context=3)` | `list[dict]` | Structured line diff |
| `word_diff()` | `list[dict]` | Structured word diff |
| `unified_diff(context=3)` | `str` | Unified diff string |
| `json_patch()` | `list[dict]` | RFC 6902 patch ops |
| `summary()` | `dict` | Lines added/removed summary |

---

## Running Tests

```bash
pip install pytest
pytest tests/ -v
```

---

## License

MIT © prabhay759
