all: add a text sub-package with SmartyPants formatting
- ID
6aad8e3- date
2026-03-29 20:37:20+00:00- author
Alex Chan <alex@alexwlchan.net>- parent
7386d03- message
all: add a `text` sub-package with SmartyPants formatting- changed files
Changed files
CHANGELOG.md (3523) → CHANGELOG.md (3706)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e35ddb7..336df4a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
# CHANGELOG
+## v29 - 2026-03-29
+
+Add a `chives.text` package which exports a `smartify()` function for applying SmartyPants-style formatting to a string -- adding curly quotes and smart dashes.
+
## v28 - 2026-03-28
Remove the httpx dependency in `chives.urls`.
dev_requirements.in (85) → dev_requirements.in (90)
diff --git a/dev_requirements.in b/dev_requirements.in
index 735adba..99e47b4 100644
--- a/dev_requirements.in
+++ b/dev_requirements.in
@@ -1,4 +1,4 @@
--e file:.[media,static_site_tests,urls]
+-e file:.[media,static_site_tests,text,urls]
build
mypy
dev_requirements.txt (2259) → dev_requirements.txt (2306)
diff --git a/dev_requirements.txt b/dev_requirements.txt
index 1e90bc9..274e85e 100644
--- a/dev_requirements.txt
+++ b/dev_requirements.txt
@@ -28,7 +28,7 @@ iniconfig==2.3.0
# via pytest
jaraco-classes==3.4.0
# via keyring
-jaraco-context==6.1.1
+jaraco-context==6.1.2
# via keyring
jaraco-functools==4.4.0
# via keyring
@@ -79,7 +79,7 @@ pytest==9.0.2
# alexwlchan-chives
# pytest-cov
# pytest-vcr
-pytest-cov==7.0.0
+pytest-cov==7.1.0
# via -r dev_requirements.in
pytest-vcr==1.0.2
# via -r dev_requirements.in
@@ -101,6 +101,8 @@ rich==14.3.3
# via twine
ruff==0.15.7
# via -r dev_requirements.in
+smartypants==2.0.2
+ # via alexwlchan-chives
twine==6.2.0
# via -r dev_requirements.in
typing-extensions==4.15.0
pyproject.toml (1297) → pyproject.toml (1320)
diff --git a/pyproject.toml b/pyproject.toml
index 355cb94..5f44086 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -26,6 +26,7 @@ license = "MIT"
[project.optional-dependencies]
media = ["Pillow"]
static_site_tests = ["playwright", "pytest", "rapidfuzz"]
+text = ["smartypants"]
urls = ["certifi", "hyperlink"]
[project.urls]
src/chives/__init__.py (391) → src/chives/__init__.py (391)
diff --git a/src/chives/__init__.py b/src/chives/__init__.py
index 65fd082..1401076 100644
--- a/src/chives/__init__.py
+++ b/src/chives/__init__.py
@@ -11,4 +11,4 @@ I share across multiple sites.
"""
-__version__ = "28"
+__version__ = "29"
src/chives/text.py (0) → src/chives/text.py (713)
diff --git a/src/chives/text.py b/src/chives/text.py
new file mode 100644
index 0000000..bc3617d
--- /dev/null
+++ b/src/chives/text.py
@@ -0,0 +1,32 @@
+"""
+Functions for dealing with text.
+"""
+
+import functools
+
+import smartypants
+
+
+@functools.cache
+def smartify(text: str) -> str:
+ """
+ Add curly quotes and smart dashes to a string.
+ """
+ # Undo some escaping from Mistune.
+ text = text.replace(""", '"')
+
+ attrs = (
+ # normal quotes (" and ') to curly ones
+ smartypants.Attr.q
+ |
+ # typewriter dashes (--) to en-dashes and dashes (---) to em-dashes
+ smartypants.Attr.D
+ |
+ # dashes (...) to ellipses
+ smartypants.Attr.e
+ |
+ # output Unicode chars instead of numeric character references
+ smartypants.Attr.u
+ )
+
+ return smartypants.smartypants(text, attrs)
tests/stubs/smartypants.pyi (0) → tests/stubs/smartypants.pyi (108)
diff --git a/tests/stubs/smartypants.pyi b/tests/stubs/smartypants.pyi
new file mode 100644
index 0000000..273b48b
--- /dev/null
+++ b/tests/stubs/smartypants.pyi
@@ -0,0 +1,7 @@
+class Attr:
+ q: int
+ D: int
+ e: int
+ u: int
+
+def smartypants(text: str, attrs: int) -> str: ...
tests/test_text.py (0) → tests/test_text.py (674)
diff --git a/tests/test_text.py b/tests/test_text.py
new file mode 100644
index 0000000..22e137e
--- /dev/null
+++ b/tests/test_text.py
@@ -0,0 +1,25 @@
+"""
+Tests for `chives.text`.
+"""
+
+import pytest
+
+from chives.text import smartify
+
+
+@pytest.mark.parametrize(
+ "text, expected",
+ [
+ ("Isn't it delightful -- she said", "Isn’t it delightful – she said"),
+ ("Are you ... sure?", "Are you … sure?"),
+ ("<h2>Isn't it delightful?</h2>", "<h2>Isn’t it delightful?</h2>"),
+ ("<li>Isn't it delightful?</li>", "<li>Isn’t it delightful?</li>"),
+ ("<p>"It's nice", he said</p>", "<p>“It’s nice”, he said</p>"),
+ ],
+)
+def test_smartify(text: str, expected: str) -> None:
+ """
+ Test smartify().
+ """
+ actual = smartify(text)
+ assert actual == expected