slugsmith 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- slugsmith-0.1.0/.github/dependabot.yml +10 -0
- slugsmith-0.1.0/.github/workflows/ci.yml +41 -0
- slugsmith-0.1.0/.github/workflows/publish.yml +20 -0
- slugsmith-0.1.0/.gitignore +14 -0
- slugsmith-0.1.0/CHANGELOG.md +13 -0
- slugsmith-0.1.0/Makefile +22 -0
- slugsmith-0.1.0/PKG-INFO +176 -0
- slugsmith-0.1.0/PLAN.md +65 -0
- slugsmith-0.1.0/README.md +155 -0
- slugsmith-0.1.0/pyproject.toml +49 -0
- slugsmith-0.1.0/src/slugsmith/__init__.py +6 -0
- slugsmith-0.1.0/src/slugsmith/_version.py +1 -0
- slugsmith-0.1.0/src/slugsmith/py.typed +0 -0
- slugsmith-0.1.0/src/slugsmith/slugify.py +112 -0
- slugsmith-0.1.0/src/slugsmith/special.py +62 -0
- slugsmith-0.1.0/src/slugsmith/transliterate.py +182 -0
- slugsmith-0.1.0/tests/__init__.py +0 -0
- slugsmith-0.1.0/tests/test_basic.py +13 -0
- slugsmith-0.1.0/tests/test_compat.py +58 -0
- slugsmith-0.1.0/tests/test_slugify.py +161 -0
- slugsmith-0.1.0/tests/test_transliterate.py +118 -0
- slugsmith-0.1.0/uv.lock +398 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ${{ matrix.os }}
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
os: [ubuntu-latest, macos-latest]
|
|
15
|
+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
16
|
+
fail-fast: false
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v6
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python-version }}
|
|
22
|
+
- name: Install uv
|
|
23
|
+
uses: astral-sh/setup-uv@v5
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: uv sync
|
|
26
|
+
- name: Run tests
|
|
27
|
+
run: uv run pytest tests/ -v
|
|
28
|
+
|
|
29
|
+
typecheck:
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v6
|
|
33
|
+
- uses: actions/setup-python@v5
|
|
34
|
+
with:
|
|
35
|
+
python-version: "3.13"
|
|
36
|
+
- name: Install uv
|
|
37
|
+
uses: astral-sh/setup-uv@v5
|
|
38
|
+
- name: Install dependencies
|
|
39
|
+
run: uv sync
|
|
40
|
+
- name: Run mypy
|
|
41
|
+
run: uv run mypy src/ --strict
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
environment: pypi
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v6
|
|
15
|
+
- name: Install uv
|
|
16
|
+
uses: astral-sh/setup-uv@v5
|
|
17
|
+
- name: Build distribution
|
|
18
|
+
run: uv build
|
|
19
|
+
- name: Publish to PyPI
|
|
20
|
+
run: uv publish
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.1.0] - 2026-03-14
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Initial release of slugsmith — modern, zero-dependency URL slug generator
|
|
8
|
+
- Built-in Unicode transliteration (no external deps) covering Latin, Greek, Cyrillic, and CJK scripts
|
|
9
|
+
- Language-specific character mappings (German ü→ue, ß→ss, etc.)
|
|
10
|
+
- Full `python-slugify`-compatible API (`slugify()` with `separator`, `max_length`, `word_boundary`, `stopwords`, `replacements`, `lowercase`, `truncate_chars` parameters)
|
|
11
|
+
- Strict type annotations throughout (PEP 561 `py.typed` marker)
|
|
12
|
+
- Comprehensive test suite (87 tests) covering edge cases and Unicode handling
|
|
13
|
+
- Python 3.9–3.13 support
|
slugsmith-0.1.0/Makefile
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
.PHONY: install build test lint fmt clean ci
|
|
2
|
+
|
|
3
|
+
install:
|
|
4
|
+
uv sync
|
|
5
|
+
|
|
6
|
+
build:
|
|
7
|
+
uv build
|
|
8
|
+
|
|
9
|
+
test:
|
|
10
|
+
uv run pytest tests/ -v
|
|
11
|
+
|
|
12
|
+
lint:
|
|
13
|
+
uv run ruff check src/
|
|
14
|
+
uv run mypy src/
|
|
15
|
+
|
|
16
|
+
fmt:
|
|
17
|
+
uv run ruff format src/ tests/
|
|
18
|
+
|
|
19
|
+
clean:
|
|
20
|
+
rm -rf dist/ build/ *.egg-info/ .pytest_cache/ .mypy_cache/
|
|
21
|
+
|
|
22
|
+
ci: lint test
|
slugsmith-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: slugsmith
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Modern, zero-dependency URL slug generator with built-in Unicode transliteration
|
|
5
|
+
Project-URL: Homepage, https://github.com/agentine/slugsmith
|
|
6
|
+
Project-URL: Repository, https://github.com/agentine/slugsmith
|
|
7
|
+
Author: Agentine
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# slugsmith
|
|
23
|
+
|
|
24
|
+
[](https://github.com/agentine/slugsmith/actions/workflows/ci.yml)
|
|
25
|
+
[](https://pypi.org/project/slugsmith/)
|
|
26
|
+
[](https://pypi.org/project/slugsmith/)
|
|
27
|
+
|
|
28
|
+
Modern, zero-dependency Python library for generating URL-friendly slugs from Unicode text.
|
|
29
|
+
|
|
30
|
+
A drop-in replacement for [python-slugify](https://github.com/un33k/python-slugify) with built-in transliteration, no external dependencies, and full type annotations.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install slugsmith
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from slugsmith import slugify
|
|
42
|
+
|
|
43
|
+
slugify("Hello World") # "hello-world"
|
|
44
|
+
slugify("café latte") # "cafe-latte"
|
|
45
|
+
slugify("Ελληνικά") # "ellinika"
|
|
46
|
+
slugify("Привет мир") # "privet-mir"
|
|
47
|
+
slugify("北京") # "bei-jing"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Features
|
|
51
|
+
|
|
52
|
+
- **Zero dependencies** — built-in Unicode→ASCII transliteration; no `text-unidecode` or `Unidecode` required
|
|
53
|
+
- **Drop-in replacement** for `python-slugify` — same `slugify()` signature
|
|
54
|
+
- **Language-aware** — language-specific mappings for German, Turkish, Polish, Czech, Finnish, Swedish
|
|
55
|
+
- **Type-safe** — full type annotations, PEP 561 compliant, passes mypy strict
|
|
56
|
+
- **MIT licensed** — no GPL dependency concerns
|
|
57
|
+
|
|
58
|
+
## API Reference
|
|
59
|
+
|
|
60
|
+
### `slugify(text, **options) -> str`
|
|
61
|
+
|
|
62
|
+
Generate a URL-friendly slug from `text`.
|
|
63
|
+
|
|
64
|
+
| Parameter | Type | Default | Description |
|
|
65
|
+
|-----------|------|---------|-------------|
|
|
66
|
+
| `text` | `str` | *(required)* | Input string to slugify |
|
|
67
|
+
| `separator` | `str` | `"-"` | Character(s) between words |
|
|
68
|
+
| `lowercase` | `bool` | `True` | Convert to lowercase |
|
|
69
|
+
| `max_length` | `int` | `0` | Max slug length (0 = unlimited) |
|
|
70
|
+
| `word_boundary` | `bool` | `False` | Truncate at word boundary when `max_length` is set |
|
|
71
|
+
| `save_order` | `bool` | `False` | Accepted for python-slugify compatibility (word order is always preserved) |
|
|
72
|
+
| `stopwords` | `Iterable[str]` | `()` | Words to remove from the slug |
|
|
73
|
+
| `regex_pattern` | `str \| None` | `None` | Custom regex pattern for allowed characters |
|
|
74
|
+
| `replacements` | `Sequence[Sequence[str]] \| None` | `None` | Sequence of `(old, new)` pairs applied before transliteration; accepts lists of tuples or lists of lists |
|
|
75
|
+
| `allow_unicode` | `bool` | `False` | Keep Unicode characters in the slug |
|
|
76
|
+
| `lang` | `str \| None` | `None` | Language code for language-specific transliteration |
|
|
77
|
+
|
|
78
|
+
Returns an empty string if `text` is empty.
|
|
79
|
+
|
|
80
|
+
## Examples
|
|
81
|
+
|
|
82
|
+
### Separator and case
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
slugify("Hello World", separator="_") # "hello_world"
|
|
86
|
+
slugify("Hello World", lowercase=False) # "Hello-World"
|
|
87
|
+
slugify("Hello World", separator="") # "helloworld"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Length limiting
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
slugify("the quick brown fox", max_length=15) # "the-quick-brow"
|
|
94
|
+
slugify("the quick brown fox", max_length=15, word_boundary=True) # "the-quick"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Stopwords
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
slugify("the quick brown fox", stopwords=("the",)) # "quick-brown-fox"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Replacements
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
slugify("C++ is great", replacements=[("++", "pp")]) # "cpp-is-great"
|
|
107
|
+
slugify("$100 deal", replacements=[("$", "dollar")]) # "dollar100-deal"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Unicode passthrough
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
slugify("café au lait", allow_unicode=True) # "café-au-lait"
|
|
114
|
+
slugify("北京 city", allow_unicode=True) # "北京-city"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Language-specific transliteration
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
slugify("Ü-bung", lang="de") # "ue-bung" (German: ü→ue, ö→oe, ä→ae)
|
|
121
|
+
slugify("çalış", lang="tr") # "calis" (Turkish: ç→c, ş→s)
|
|
122
|
+
slugify("łódź", lang="pl") # "lodz" (Polish: ł→l, ó→o, ź→z)
|
|
123
|
+
slugify("říjen", lang="cs") # "rijen" (Czech: ř→r, í→i)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Supported language codes: `de` (German), `tr` (Turkish), `pl` (Polish), `cs` (Czech), `fi` (Finnish), `sv` (Swedish).
|
|
127
|
+
|
|
128
|
+
### Script coverage
|
|
129
|
+
|
|
130
|
+
slugsmith's built-in transliteration covers a wide range of Unicode scripts without any external dependencies:
|
|
131
|
+
|
|
132
|
+
| Script | Coverage | Example |
|
|
133
|
+
|--------|----------|---------|
|
|
134
|
+
| Latin Extended | Full diacritics (NFKD + explicit map) | `café` → `cafe` |
|
|
135
|
+
| Cyrillic | Russian + Ukrainian | `Привет` → `Privet`, `їжак` → `yizhak` |
|
|
136
|
+
| Greek | Modern Greek alphabet | `Ελληνικά` → `Ellinika` |
|
|
137
|
+
| Arabic | Basic alphabet (28 letters) | `مرحبا` → `mrhba` |
|
|
138
|
+
| Hebrew | Basic alphabet (22 letters + finals) | `שלום` → `shalom` |
|
|
139
|
+
| Symbols | Common currency and typographic symbols | `€100` → `euro100`, `™` → `tm` |
|
|
140
|
+
|
|
141
|
+
Characters not covered by the table are decomposed via NFKD normalisation; if decomposition yields ASCII, that is used. Remaining non-ASCII characters are silently dropped (same behaviour as `text-unidecode`).
|
|
142
|
+
|
|
143
|
+
### Custom regex
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
# Allow only alphanumeric and hyphens (strip underscores too)
|
|
147
|
+
slugify("hello_world foo", regex_pattern=r"[^a-z0-9-]") # "helloworld-foo"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Migration from python-slugify
|
|
151
|
+
|
|
152
|
+
slugsmith is a drop-in replacement:
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
# Before
|
|
156
|
+
from slugify import slugify
|
|
157
|
+
|
|
158
|
+
# After
|
|
159
|
+
from slugsmith import slugify
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The `slugify()` signature is identical. No other code changes are needed.
|
|
163
|
+
|
|
164
|
+
**Behavioural differences:**
|
|
165
|
+
|
|
166
|
+
| Feature | python-slugify | slugsmith |
|
|
167
|
+
|---------|---------------|-----------|
|
|
168
|
+
| Transliteration | `text-unidecode` (external dep) | Built-in table (zero deps) |
|
|
169
|
+
| License | MIT | MIT (no GPL risk) |
|
|
170
|
+
| Python support | 3.7+ | 3.9+ |
|
|
171
|
+
| Type annotations | Partial | Full (mypy strict) |
|
|
172
|
+
| Language-specific | Not built-in | `lang=` parameter |
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
MIT
|
slugsmith-0.1.0/PLAN.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Slugsmith — Implementation Plan
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
**Slugsmith** is a modern, self-contained Python library for generating URL-friendly slugs from Unicode text. It replaces **python-slugify** (62M/month downloads, single maintainer inactive since March 2024, stale dependency chain).
|
|
6
|
+
|
|
7
|
+
**PyPI name:** `slugsmith` (verified available)
|
|
8
|
+
|
|
9
|
+
## Target Library: python-slugify
|
|
10
|
+
|
|
11
|
+
- **Downloads:** 62M/month (2.4M/day)
|
|
12
|
+
- **Maintainer:** un33k (Val Neekman) — last code commit March 2024
|
|
13
|
+
- **Issues:** 4 open, 3 unmerged PRs, no review activity
|
|
14
|
+
- **Key risk:** Depends on `text-unidecode` (last release 2019, abandoned)
|
|
15
|
+
- **License concern:** Alternative dependency `Unidecode` is GPL; many projects cannot use it
|
|
16
|
+
|
|
17
|
+
## Architecture
|
|
18
|
+
|
|
19
|
+
### Core Module: `slugsmith/`
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
slugsmith/
|
|
23
|
+
├── __init__.py # Public API: slugify()
|
|
24
|
+
├── slugify.py # Core slugification logic
|
|
25
|
+
├── transliterate.py # Built-in Unicode→ASCII transliteration (no external deps)
|
|
26
|
+
├── special.py # Language-specific character mappings (German ü→ue, etc.)
|
|
27
|
+
├── py.typed # PEP 561 marker
|
|
28
|
+
└── _version.py # Version string
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Key Design Decisions
|
|
32
|
+
|
|
33
|
+
1. **Zero dependencies** — Built-in transliteration tables eliminate the need for text-unidecode or Unidecode. Ship comprehensive Unicode→ASCII mappings derived from CLDR/Unicode data (MIT-compatible sources only).
|
|
34
|
+
|
|
35
|
+
2. **API compatibility** — Provide a `slugify()` function with the same core parameters as python-slugify (`text`, `separator`, `lowercase`, `max_length`, `word_boundary`, `save_order`, `stopwords`, `regex_pattern`, `replacements`, `allow_unicode`). Easy migration path.
|
|
36
|
+
|
|
37
|
+
3. **Type-safe** — Full type annotations, PEP 561 compliant, passes mypy strict.
|
|
38
|
+
|
|
39
|
+
4. **Performance** — Optimize hot paths. Avoid unnecessary regex compilation on each call (pre-compile patterns). Target ≥2x throughput vs python-slugify.
|
|
40
|
+
|
|
41
|
+
5. **Modern Python** — Require Python 3.9+. Use modern string/regex features.
|
|
42
|
+
|
|
43
|
+
6. **MIT licensed** — Clear, permissive license with no GPL dependencies.
|
|
44
|
+
|
|
45
|
+
### Transliteration Strategy
|
|
46
|
+
|
|
47
|
+
- Ship a comprehensive mapping table covering Latin Extended, Cyrillic, Greek, CJK (basic), Arabic, Hebrew, Thai, and other major scripts.
|
|
48
|
+
- Source mappings from CLDR transliteration rules and Unicode NFKD decomposition.
|
|
49
|
+
- Support language-specific transliteration (e.g., German: ü→ue, ö→oe; Turkish: ı→i).
|
|
50
|
+
- Use `unicodedata.normalize('NFKD', ...)` as first pass, then apply custom mappings for characters that don't decompose cleanly.
|
|
51
|
+
|
|
52
|
+
## Deliverables
|
|
53
|
+
|
|
54
|
+
1. Core `slugify()` function with full parameter compatibility
|
|
55
|
+
2. Built-in transliteration engine (no external dependencies)
|
|
56
|
+
3. Language-specific character mappings
|
|
57
|
+
4. Comprehensive test suite (unit + property-based)
|
|
58
|
+
5. Migration guide from python-slugify
|
|
59
|
+
6. PyPI package published as `slugsmith`
|
|
60
|
+
|
|
61
|
+
## Non-Goals
|
|
62
|
+
|
|
63
|
+
- Django integration (users can wrap `slugify()` themselves)
|
|
64
|
+
- CLI tool (keep scope focused on the library)
|
|
65
|
+
- Python 2 or Python <3.9 support
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# slugsmith
|
|
2
|
+
|
|
3
|
+
[](https://github.com/agentine/slugsmith/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/slugsmith/)
|
|
5
|
+
[](https://pypi.org/project/slugsmith/)
|
|
6
|
+
|
|
7
|
+
Modern, zero-dependency Python library for generating URL-friendly slugs from Unicode text.
|
|
8
|
+
|
|
9
|
+
A drop-in replacement for [python-slugify](https://github.com/un33k/python-slugify) with built-in transliteration, no external dependencies, and full type annotations.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install slugsmith
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from slugsmith import slugify
|
|
21
|
+
|
|
22
|
+
slugify("Hello World") # "hello-world"
|
|
23
|
+
slugify("café latte") # "cafe-latte"
|
|
24
|
+
slugify("Ελληνικά") # "ellinika"
|
|
25
|
+
slugify("Привет мир") # "privet-mir"
|
|
26
|
+
slugify("北京") # "bei-jing"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
- **Zero dependencies** — built-in Unicode→ASCII transliteration; no `text-unidecode` or `Unidecode` required
|
|
32
|
+
- **Drop-in replacement** for `python-slugify` — same `slugify()` signature
|
|
33
|
+
- **Language-aware** — language-specific mappings for German, Turkish, Polish, Czech, Finnish, Swedish
|
|
34
|
+
- **Type-safe** — full type annotations, PEP 561 compliant, passes mypy strict
|
|
35
|
+
- **MIT licensed** — no GPL dependency concerns
|
|
36
|
+
|
|
37
|
+
## API Reference
|
|
38
|
+
|
|
39
|
+
### `slugify(text, **options) -> str`
|
|
40
|
+
|
|
41
|
+
Generate a URL-friendly slug from `text`.
|
|
42
|
+
|
|
43
|
+
| Parameter | Type | Default | Description |
|
|
44
|
+
|-----------|------|---------|-------------|
|
|
45
|
+
| `text` | `str` | *(required)* | Input string to slugify |
|
|
46
|
+
| `separator` | `str` | `"-"` | Character(s) between words |
|
|
47
|
+
| `lowercase` | `bool` | `True` | Convert to lowercase |
|
|
48
|
+
| `max_length` | `int` | `0` | Max slug length (0 = unlimited) |
|
|
49
|
+
| `word_boundary` | `bool` | `False` | Truncate at word boundary when `max_length` is set |
|
|
50
|
+
| `save_order` | `bool` | `False` | Accepted for python-slugify compatibility (word order is always preserved) |
|
|
51
|
+
| `stopwords` | `Iterable[str]` | `()` | Words to remove from the slug |
|
|
52
|
+
| `regex_pattern` | `str \| None` | `None` | Custom regex pattern for allowed characters |
|
|
53
|
+
| `replacements` | `Sequence[Sequence[str]] \| None` | `None` | Sequence of `(old, new)` pairs applied before transliteration; accepts lists of tuples or lists of lists |
|
|
54
|
+
| `allow_unicode` | `bool` | `False` | Keep Unicode characters in the slug |
|
|
55
|
+
| `lang` | `str \| None` | `None` | Language code for language-specific transliteration |
|
|
56
|
+
|
|
57
|
+
Returns an empty string if `text` is empty.
|
|
58
|
+
|
|
59
|
+
## Examples
|
|
60
|
+
|
|
61
|
+
### Separator and case
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
slugify("Hello World", separator="_") # "hello_world"
|
|
65
|
+
slugify("Hello World", lowercase=False) # "Hello-World"
|
|
66
|
+
slugify("Hello World", separator="") # "helloworld"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Length limiting
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
slugify("the quick brown fox", max_length=15) # "the-quick-brow"
|
|
73
|
+
slugify("the quick brown fox", max_length=15, word_boundary=True) # "the-quick"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Stopwords
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
slugify("the quick brown fox", stopwords=("the",)) # "quick-brown-fox"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Replacements
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
slugify("C++ is great", replacements=[("++", "pp")]) # "cpp-is-great"
|
|
86
|
+
slugify("$100 deal", replacements=[("$", "dollar")]) # "dollar100-deal"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Unicode passthrough
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
slugify("café au lait", allow_unicode=True) # "café-au-lait"
|
|
93
|
+
slugify("北京 city", allow_unicode=True) # "北京-city"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Language-specific transliteration
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
slugify("Ü-bung", lang="de") # "ue-bung" (German: ü→ue, ö→oe, ä→ae)
|
|
100
|
+
slugify("çalış", lang="tr") # "calis" (Turkish: ç→c, ş→s)
|
|
101
|
+
slugify("łódź", lang="pl") # "lodz" (Polish: ł→l, ó→o, ź→z)
|
|
102
|
+
slugify("říjen", lang="cs") # "rijen" (Czech: ř→r, í→i)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Supported language codes: `de` (German), `tr` (Turkish), `pl` (Polish), `cs` (Czech), `fi` (Finnish), `sv` (Swedish).
|
|
106
|
+
|
|
107
|
+
### Script coverage
|
|
108
|
+
|
|
109
|
+
slugsmith's built-in transliteration covers a wide range of Unicode scripts without any external dependencies:
|
|
110
|
+
|
|
111
|
+
| Script | Coverage | Example |
|
|
112
|
+
|--------|----------|---------|
|
|
113
|
+
| Latin Extended | Full diacritics (NFKD + explicit map) | `café` → `cafe` |
|
|
114
|
+
| Cyrillic | Russian + Ukrainian | `Привет` → `Privet`, `їжак` → `yizhak` |
|
|
115
|
+
| Greek | Modern Greek alphabet | `Ελληνικά` → `Ellinika` |
|
|
116
|
+
| Arabic | Basic alphabet (28 letters) | `مرحبا` → `mrhba` |
|
|
117
|
+
| Hebrew | Basic alphabet (22 letters + finals) | `שלום` → `shalom` |
|
|
118
|
+
| Symbols | Common currency and typographic symbols | `€100` → `euro100`, `™` → `tm` |
|
|
119
|
+
|
|
120
|
+
Characters not covered by the table are decomposed via NFKD normalisation; if decomposition yields ASCII, that is used. Remaining non-ASCII characters are silently dropped (same behaviour as `text-unidecode`).
|
|
121
|
+
|
|
122
|
+
### Custom regex
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
# Allow only alphanumeric and hyphens (strip underscores too)
|
|
126
|
+
slugify("hello_world foo", regex_pattern=r"[^a-z0-9-]") # "helloworld-foo"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Migration from python-slugify
|
|
130
|
+
|
|
131
|
+
slugsmith is a drop-in replacement:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
# Before
|
|
135
|
+
from slugify import slugify
|
|
136
|
+
|
|
137
|
+
# After
|
|
138
|
+
from slugsmith import slugify
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The `slugify()` signature is identical. No other code changes are needed.
|
|
142
|
+
|
|
143
|
+
**Behavioural differences:**
|
|
144
|
+
|
|
145
|
+
| Feature | python-slugify | slugsmith |
|
|
146
|
+
|---------|---------------|-----------|
|
|
147
|
+
| Transliteration | `text-unidecode` (external dep) | Built-in table (zero deps) |
|
|
148
|
+
| License | MIT | MIT (no GPL risk) |
|
|
149
|
+
| Python support | 3.7+ | 3.9+ |
|
|
150
|
+
| Type annotations | Partial | Full (mypy strict) |
|
|
151
|
+
| Language-specific | Not built-in | `lang=` parameter |
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
MIT
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "slugsmith"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Modern, zero-dependency URL slug generator with built-in Unicode transliteration"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Agentine" }
|
|
9
|
+
]
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
dependencies = []
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 3 - Alpha",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.9",
|
|
18
|
+
"Programming Language :: Python :: 3.10",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Typing :: Typed",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Homepage = "https://github.com/agentine/slugsmith"
|
|
27
|
+
Repository = "https://github.com/agentine/slugsmith"
|
|
28
|
+
|
|
29
|
+
[build-system]
|
|
30
|
+
requires = ["hatchling"]
|
|
31
|
+
build-backend = "hatchling.build"
|
|
32
|
+
|
|
33
|
+
[tool.hatch.build.targets.wheel]
|
|
34
|
+
packages = ["src/slugsmith"]
|
|
35
|
+
|
|
36
|
+
[dependency-groups]
|
|
37
|
+
dev = [
|
|
38
|
+
"mypy>=1.19",
|
|
39
|
+
"pytest>=8.0",
|
|
40
|
+
"ruff>=0.11",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
testpaths = ["tests"]
|
|
45
|
+
|
|
46
|
+
[tool.mypy]
|
|
47
|
+
strict = true
|
|
48
|
+
warn_return_any = true
|
|
49
|
+
warn_unused_configs = true
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
File without changes
|