touchstone-compute 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.
- touchstone_compute-0.1.0/LICENSE +21 -0
- touchstone_compute-0.1.0/NOTICE +5 -0
- touchstone_compute-0.1.0/PKG-INFO +95 -0
- touchstone_compute-0.1.0/README.md +67 -0
- touchstone_compute-0.1.0/pyproject.toml +49 -0
- touchstone_compute-0.1.0/setup.cfg +4 -0
- touchstone_compute-0.1.0/tests/test_citation.py +252 -0
- touchstone_compute-0.1.0/tests/test_proofreading.py +63 -0
- touchstone_compute-0.1.0/tests/test_readability.py +72 -0
- touchstone_compute-0.1.0/tests/test_syntax.py +66 -0
- touchstone_compute-0.1.0/tests/test_textdiff.py +161 -0
- touchstone_compute-0.1.0/tests/test_units.py +109 -0
- touchstone_compute-0.1.0/touchstone/__init__.py +19 -0
- touchstone_compute-0.1.0/touchstone/citation.py +532 -0
- touchstone_compute-0.1.0/touchstone/mcp_server.py +164 -0
- touchstone_compute-0.1.0/touchstone/proofreading.py +135 -0
- touchstone_compute-0.1.0/touchstone/readability.py +90 -0
- touchstone_compute-0.1.0/touchstone/syntax.py +100 -0
- touchstone_compute-0.1.0/touchstone/textdiff.py +144 -0
- touchstone_compute-0.1.0/touchstone/units.py +221 -0
- touchstone_compute-0.1.0/touchstone_compute.egg-info/PKG-INFO +95 -0
- touchstone_compute-0.1.0/touchstone_compute.egg-info/SOURCES.txt +24 -0
- touchstone_compute-0.1.0/touchstone_compute.egg-info/dependency_links.txt +1 -0
- touchstone_compute-0.1.0/touchstone_compute.egg-info/entry_points.txt +2 -0
- touchstone_compute-0.1.0/touchstone_compute.egg-info/requires.txt +10 -0
- touchstone_compute-0.1.0/touchstone_compute.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Iris
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: touchstone-compute
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Deterministic, verifiable text/code/measurement utilities for AI agents — citations, diffs, proofreading, readability, syntax checking and unit conversion, each reproducing a named authority byte-for-byte. Trust by re-execution, not reputation.
|
|
5
|
+
Author: Iris
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://touchstone.locomot.io
|
|
8
|
+
Project-URL: Source, https://github.com/savecharlie/touchstone
|
|
9
|
+
Keywords: citation,apa,mla,chicago,bibtex,diff,json-patch,rfc6902,proofreading,readability,flesch,syntax,unit-conversion,nist,deterministic,verifiable,agent-tools,llm-tools,mcp,model-context-protocol,x402
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Text Processing :: Linguistic
|
|
14
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Requires-Python: >=3.9
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
License-File: NOTICE
|
|
21
|
+
Requires-Dist: pyyaml>=6
|
|
22
|
+
Requires-Dist: tomli>=2; python_version < "3.11"
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
25
|
+
Provides-Extra: mcp
|
|
26
|
+
Requires-Dist: mcp>=1.2; extra == "mcp"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# Touchstone — verifiable compute for agents
|
|
30
|
+
|
|
31
|
+
`mcp-name: io.github.savecharlie/touchstone`
|
|
32
|
+
|
|
33
|
+
Deterministic, verifiable text/code/measurement utilities for AI agents. Each function
|
|
34
|
+
does the **exact** part of a task an LLM is asked to do all day — and reproduces a
|
|
35
|
+
**named published authority** byte-for-byte, so any answer can be re-executed and checked
|
|
36
|
+
rather than trusted.
|
|
37
|
+
|
|
38
|
+
> Trust by re-execution, not reputation. The proof is the property.
|
|
39
|
+
|
|
40
|
+
This is the open core. The hosted suite at **[touchstone.locomot.io](https://touchstone.locomot.io)**
|
|
41
|
+
adds a drand-anchored trust layer (provably-fair randomness, beacon timestamps, timelock
|
|
42
|
+
sealing, sealed-bid auctions) and signed pay-per-call receipts over [x402](https://www.x402.org).
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install touchstone-compute # the library
|
|
48
|
+
pip install "touchstone-compute[mcp]" # + the MCP server for AI agents
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## The tools
|
|
52
|
+
|
|
53
|
+
| Function | Does | Authority |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| `units.compute` | unit conversion (+ affine temperature) | NIST SP 811 exact factors |
|
|
56
|
+
| `citation.compute` | citation formatting, reference + in-text | APA 7 / MLA 9 / Chicago author-date / BibTeX |
|
|
57
|
+
| `textdiff.compute` | unified diff / RFC 6902 JSON Patch / inline words | RFC 6902 / 6901, POSIX unified diff |
|
|
58
|
+
| `proofreading.compute` | rule-based proofreading flags | deterministic style rules |
|
|
59
|
+
| `readability.compute` | reading-grade metrics | Flesch · Flesch-Kincaid · Gunning Fog · SMOG · Coleman-Liau · ARI |
|
|
60
|
+
| `syntax.compute` | does it parse? + error location | each language's reference parser |
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from touchstone import units, citation, textdiff
|
|
64
|
+
|
|
65
|
+
units.compute(1, "nautical_mile", "meter") # {'result': 1852.0, 'exact': True, ...}
|
|
66
|
+
|
|
67
|
+
citation.compute("apa", "book", authors="Anderson, Benedict",
|
|
68
|
+
title="Imagined communities", year=1983, publisher="Verso")
|
|
69
|
+
# "Anderson, B. (1983). *Imagined communities*. Verso."
|
|
70
|
+
|
|
71
|
+
textdiff.compute(a='{"v":1}', b='{"v":2}', mode="json")
|
|
72
|
+
# {'patch': [{'op': 'replace', 'path': '/v', 'value': 2}], 'op_count': 1, ...}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## As an MCP server
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pip install "touchstone-compute[mcp]"
|
|
79
|
+
touchstone-mcp # stdio transport, ready for any MCP client
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Exposes six tools — `convert_unit`, `format_citation`, `diff`, `proofread`,
|
|
83
|
+
`readability`, `check_syntax` — each documenting the authority it reproduces.
|
|
84
|
+
|
|
85
|
+
## Why verifiable, not just correct
|
|
86
|
+
|
|
87
|
+
The agent tool ecosystem is a lemons market: registries verify *who* published a tool,
|
|
88
|
+
not whether it's any good, and most reputation is forgeable. The one credential a
|
|
89
|
+
newcomer can supply that clears it is **re-executable proof** — and for deterministic
|
|
90
|
+
tools that proof is free, because the proof is the property. Don't trust these answers;
|
|
91
|
+
re-run them. ([the argument in full](https://touchstone.locomot.io/writing/the-assay))
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
MIT © Iris. Built by an autonomous AI.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Touchstone — verifiable compute for agents
|
|
2
|
+
|
|
3
|
+
`mcp-name: io.github.savecharlie/touchstone`
|
|
4
|
+
|
|
5
|
+
Deterministic, verifiable text/code/measurement utilities for AI agents. Each function
|
|
6
|
+
does the **exact** part of a task an LLM is asked to do all day — and reproduces a
|
|
7
|
+
**named published authority** byte-for-byte, so any answer can be re-executed and checked
|
|
8
|
+
rather than trusted.
|
|
9
|
+
|
|
10
|
+
> Trust by re-execution, not reputation. The proof is the property.
|
|
11
|
+
|
|
12
|
+
This is the open core. The hosted suite at **[touchstone.locomot.io](https://touchstone.locomot.io)**
|
|
13
|
+
adds a drand-anchored trust layer (provably-fair randomness, beacon timestamps, timelock
|
|
14
|
+
sealing, sealed-bid auctions) and signed pay-per-call receipts over [x402](https://www.x402.org).
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install touchstone-compute # the library
|
|
20
|
+
pip install "touchstone-compute[mcp]" # + the MCP server for AI agents
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## The tools
|
|
24
|
+
|
|
25
|
+
| Function | Does | Authority |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| `units.compute` | unit conversion (+ affine temperature) | NIST SP 811 exact factors |
|
|
28
|
+
| `citation.compute` | citation formatting, reference + in-text | APA 7 / MLA 9 / Chicago author-date / BibTeX |
|
|
29
|
+
| `textdiff.compute` | unified diff / RFC 6902 JSON Patch / inline words | RFC 6902 / 6901, POSIX unified diff |
|
|
30
|
+
| `proofreading.compute` | rule-based proofreading flags | deterministic style rules |
|
|
31
|
+
| `readability.compute` | reading-grade metrics | Flesch · Flesch-Kincaid · Gunning Fog · SMOG · Coleman-Liau · ARI |
|
|
32
|
+
| `syntax.compute` | does it parse? + error location | each language's reference parser |
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from touchstone import units, citation, textdiff
|
|
36
|
+
|
|
37
|
+
units.compute(1, "nautical_mile", "meter") # {'result': 1852.0, 'exact': True, ...}
|
|
38
|
+
|
|
39
|
+
citation.compute("apa", "book", authors="Anderson, Benedict",
|
|
40
|
+
title="Imagined communities", year=1983, publisher="Verso")
|
|
41
|
+
# "Anderson, B. (1983). *Imagined communities*. Verso."
|
|
42
|
+
|
|
43
|
+
textdiff.compute(a='{"v":1}', b='{"v":2}', mode="json")
|
|
44
|
+
# {'patch': [{'op': 'replace', 'path': '/v', 'value': 2}], 'op_count': 1, ...}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## As an MCP server
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install "touchstone-compute[mcp]"
|
|
51
|
+
touchstone-mcp # stdio transport, ready for any MCP client
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Exposes six tools — `convert_unit`, `format_citation`, `diff`, `proofread`,
|
|
55
|
+
`readability`, `check_syntax` — each documenting the authority it reproduces.
|
|
56
|
+
|
|
57
|
+
## Why verifiable, not just correct
|
|
58
|
+
|
|
59
|
+
The agent tool ecosystem is a lemons market: registries verify *who* published a tool,
|
|
60
|
+
not whether it's any good, and most reputation is forgeable. The one credential a
|
|
61
|
+
newcomer can supply that clears it is **re-executable proof** — and for deterministic
|
|
62
|
+
tools that proof is free, because the proof is the property. Don't trust these answers;
|
|
63
|
+
re-run them. ([the argument in full](https://touchstone.locomot.io/writing/the-assay))
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
|
|
67
|
+
MIT © Iris. Built by an autonomous AI.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "touchstone-compute"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Deterministic, verifiable text/code/measurement utilities for AI agents — citations, diffs, proofreading, readability, syntax checking and unit conversion, each reproducing a named authority byte-for-byte. Trust by re-execution, not reputation."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Iris" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"citation", "apa", "mla", "chicago", "bibtex", "diff", "json-patch", "rfc6902",
|
|
15
|
+
"proofreading", "readability", "flesch", "syntax", "unit-conversion", "nist",
|
|
16
|
+
"deterministic", "verifiable", "agent-tools", "llm-tools",
|
|
17
|
+
"mcp", "model-context-protocol", "x402",
|
|
18
|
+
]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Development Status :: 4 - Beta",
|
|
21
|
+
"License :: OSI Approved :: MIT License",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Topic :: Text Processing :: Linguistic",
|
|
24
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
25
|
+
"Topic :: Scientific/Engineering",
|
|
26
|
+
"Intended Audience :: Developers",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"pyyaml>=6", # syntax: YAML validation via PyYAML
|
|
30
|
+
"tomli>=2; python_version < '3.11'", # syntax: TOML validation (stdlib tomllib on 3.11+)
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
dev = ["pytest>=7"]
|
|
35
|
+
mcp = ["mcp>=1.2"] # exposes the cores as MCP tools: `pip install "touchstone-compute[mcp]"`
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
touchstone-mcp = "touchstone.mcp_server:main"
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Homepage = "https://touchstone.locomot.io"
|
|
42
|
+
Source = "https://github.com/savecharlie/touchstone"
|
|
43
|
+
|
|
44
|
+
[tool.setuptools]
|
|
45
|
+
packages = ["touchstone"]
|
|
46
|
+
|
|
47
|
+
[tool.pytest.ini_options]
|
|
48
|
+
pythonpath = ["."]
|
|
49
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""Authority tests for /cite — every target string is a published style-manual example.
|
|
2
|
+
|
|
3
|
+
Sources: APA Style / Scribbr (apa-examples), MLA Handbook 9 / SDSU LibGuide (cite/MLA/9th),
|
|
4
|
+
The Chicago Manual of Style 17 author-date / Scribbr (chicago-style/author-date).
|
|
5
|
+
Green = the assembly reproduces the authority byte-for-byte.
|
|
6
|
+
"""
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
|
11
|
+
from touchstone.citation import compute # noqa: E402
|
|
12
|
+
|
|
13
|
+
PASS = 0
|
|
14
|
+
FAIL = 0
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def chk(label, got, want):
|
|
18
|
+
global PASS, FAIL
|
|
19
|
+
if got == want:
|
|
20
|
+
PASS += 1
|
|
21
|
+
else:
|
|
22
|
+
FAIL += 1
|
|
23
|
+
print(f"FAIL {label}\n got: {got!r}\n want: {want!r}")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def md(r):
|
|
27
|
+
return r["reference"]["markdown"]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------- APA 7
|
|
31
|
+
r = compute("apa", "article",
|
|
32
|
+
authors=[{"family": "Mounier-Kuhn", "given": "Pierre"}],
|
|
33
|
+
title="Computer science in French universities: Early entrants and latecomers",
|
|
34
|
+
year=2012, container="Information & Culture: A Journal of History",
|
|
35
|
+
volume=47, issue=4, pages="414-456", doi="10.7560/IC47402")
|
|
36
|
+
chk("apa.article", md(r),
|
|
37
|
+
"Mounier-Kuhn, P. (2012). Computer science in French universities: Early entrants "
|
|
38
|
+
"and latecomers. *Information & Culture: A Journal of History*, *47*(4), 414–456. "
|
|
39
|
+
"https://doi.org/10.7560/IC47402")
|
|
40
|
+
chk("apa.article.intext", r["intext"], "(Mounier-Kuhn, 2012)")
|
|
41
|
+
|
|
42
|
+
r = compute("apa", "book",
|
|
43
|
+
authors=[{"family": "Anderson", "given": "Benedict"}],
|
|
44
|
+
title="Imagined communities: Reflections on the origins and spread of nationalism",
|
|
45
|
+
year=1983, publisher="Verso")
|
|
46
|
+
chk("apa.book", md(r),
|
|
47
|
+
"Anderson, B. (1983). *Imagined communities: Reflections on the origins and spread "
|
|
48
|
+
"of nationalism*. Verso.")
|
|
49
|
+
chk("apa.book.intext", r["intext"], "(Anderson, 1983)")
|
|
50
|
+
|
|
51
|
+
r = compute("apa", "chapter",
|
|
52
|
+
authors=[{"family": "Belsey", "given": "Catherine"}],
|
|
53
|
+
title="Poststructuralism", year=2006,
|
|
54
|
+
editors=[{"family": "Malpas", "given": "Simon"},
|
|
55
|
+
{"family": "Wake", "given": "Paul"}],
|
|
56
|
+
book_title="The Routledge companion to critical theory",
|
|
57
|
+
pages="51-61", publisher="Routledge")
|
|
58
|
+
chk("apa.chapter", md(r),
|
|
59
|
+
"Belsey, C. (2006). Poststructuralism. In S. Malpas & P. Wake (Eds.), "
|
|
60
|
+
"*The Routledge companion to critical theory* (pp. 51–61). Routledge.")
|
|
61
|
+
|
|
62
|
+
r = compute("apa", "web",
|
|
63
|
+
authors=[{"family": "Greenhouse", "given": "Steven"}],
|
|
64
|
+
title="The coronavirus pandemic has intensified systemic economic racism "
|
|
65
|
+
"against black Americans",
|
|
66
|
+
year=2020, month="July", day=30, container="The New Yorker",
|
|
67
|
+
url="https://www.newyorker.com/news/news-desk/the-pandemic-has-intensified-"
|
|
68
|
+
"systemic-economic-racism-against-black-americans")
|
|
69
|
+
chk("apa.web", md(r),
|
|
70
|
+
"Greenhouse, S. (2020, July 30). The coronavirus pandemic has intensified systemic "
|
|
71
|
+
"economic racism against black Americans. *The New Yorker*. "
|
|
72
|
+
"https://www.newyorker.com/news/news-desk/the-pandemic-has-intensified-systemic-"
|
|
73
|
+
"economic-racism-against-black-americans")
|
|
74
|
+
chk("apa.web.intext", r["intext"], "(Greenhouse, 2020)")
|
|
75
|
+
|
|
76
|
+
# APA 3-author -> in-text "et al."; reference uses & before last
|
|
77
|
+
r = compute("apa", "article",
|
|
78
|
+
authors=[{"family": "Burin", "given": "Dalila"},
|
|
79
|
+
{"family": "Kilteni", "given": "Konstantina"},
|
|
80
|
+
{"family": "Pia", "given": "Lorenzo"}],
|
|
81
|
+
title="Body ownership", year=2019, container="PLOS ONE",
|
|
82
|
+
volume=14, issue=1)
|
|
83
|
+
chk("apa.3auth.intext", r["intext"], "(Burin et al., 2019)")
|
|
84
|
+
assert "Burin, D., Kilteni, K., & Pia, L." in md(r), md(r)
|
|
85
|
+
PASS += 1
|
|
86
|
+
|
|
87
|
+
# ---------------------------------------------------------------- MLA 9
|
|
88
|
+
r = compute("mla", "article",
|
|
89
|
+
authors=[{"family": "Spangler", "given": "Lynn C."}],
|
|
90
|
+
title="Class on Television: Stuck in the Middle",
|
|
91
|
+
container="Journal of Popular Culture", volume=47, issue=3,
|
|
92
|
+
year=2014, pages="470-488", locator=480)
|
|
93
|
+
chk("mla.article", md(r),
|
|
94
|
+
'Spangler, Lynn C. "Class on Television: Stuck in the Middle." '
|
|
95
|
+
"*Journal of Popular Culture*, vol. 47, no. 3, 2014, pp. 470–488.")
|
|
96
|
+
chk("mla.article.intext", r["intext"], "(Spangler 480)")
|
|
97
|
+
|
|
98
|
+
r = compute("mla", "book",
|
|
99
|
+
authors=[{"family": "Thompson", "given": "Paul B."}],
|
|
100
|
+
title="From Field to Fork: Food Ethics for Everyone",
|
|
101
|
+
publisher="Oxford UP", year=2015, locator=14)
|
|
102
|
+
chk("mla.book", md(r),
|
|
103
|
+
"Thompson, Paul B. *From Field to Fork: Food Ethics for Everyone*. Oxford UP, 2015.")
|
|
104
|
+
chk("mla.book.intext", r["intext"], "(Thompson 14)")
|
|
105
|
+
|
|
106
|
+
r = compute("mla", "chapter",
|
|
107
|
+
authors=[{"family": "Houser", "given": "Heather"}],
|
|
108
|
+
title="Coming-of-Mind in Climate Narrative",
|
|
109
|
+
book_title="Infowhelm: Environmental Art and Literature in an Age of Data",
|
|
110
|
+
editors=[{"family": "Houser", "given": "Heather"}],
|
|
111
|
+
publisher="U of Columbia P", year=2020, pages="61-93")
|
|
112
|
+
chk("mla.chapter", md(r),
|
|
113
|
+
'Houser, Heather. "Coming-of-Mind in Climate Narrative." '
|
|
114
|
+
"*Infowhelm: Environmental Art and Literature in an Age of Data*, "
|
|
115
|
+
"edited by Heather Houser, U of Columbia P, 2020, pp. 61–93.")
|
|
116
|
+
|
|
117
|
+
r = compute("mla", "web",
|
|
118
|
+
authors=[{"family": "Rubinstein", "given": "Dana"}],
|
|
119
|
+
title="Sweeping Private-Sector Vaccine Mandate Goes Into Effect in N.Y.C.",
|
|
120
|
+
container="The New York Times", day=27, month="December", year=2021,
|
|
121
|
+
url="https://www.nytimes.com/2021/12/27/nyregion/nyc-vaccine-mandate.html")
|
|
122
|
+
chk("mla.web", md(r),
|
|
123
|
+
'Rubinstein, Dana. "Sweeping Private-Sector Vaccine Mandate Goes Into Effect in '
|
|
124
|
+
'N.Y.C." *The New York Times*, 27 Dec. 2021, '
|
|
125
|
+
"https://www.nytimes.com/2021/12/27/nyregion/nyc-vaccine-mandate.html.")
|
|
126
|
+
chk("mla.web.intext", r["intext"], "(Rubinstein)")
|
|
127
|
+
|
|
128
|
+
# MLA 2-author
|
|
129
|
+
r = compute("mla", "book",
|
|
130
|
+
authors=[{"family": "Grazer", "given": "Brian"},
|
|
131
|
+
{"family": "Fishman", "given": "Charles"}],
|
|
132
|
+
title="A Curious Mind", publisher="Simon & Schuster", year=2015)
|
|
133
|
+
assert md(r).startswith("Grazer, Brian, and Charles Fishman."), md(r)
|
|
134
|
+
PASS += 1
|
|
135
|
+
|
|
136
|
+
# ---------------------------------------------------------------- Chicago 17 author-date
|
|
137
|
+
r = compute("chicago", "article",
|
|
138
|
+
authors=[{"family": "Andreff", "given": "Wladimir"},
|
|
139
|
+
{"family": "Staudohar", "given": "Paul D."}],
|
|
140
|
+
title="The Evolving European Model of Professional Sports Finance",
|
|
141
|
+
year=2000, container="Journal of Sports Economics",
|
|
142
|
+
volume=1, issue=3, month="August", pages="257-276",
|
|
143
|
+
doi="10.1177/152700250000100304")
|
|
144
|
+
chk("chicago.article", md(r),
|
|
145
|
+
"Andreff, Wladimir, and Paul D. Staudohar. 2000. "
|
|
146
|
+
'"The Evolving European Model of Professional Sports Finance." '
|
|
147
|
+
"*Journal of Sports Economics* 1, no. 3 (August): 257–276. "
|
|
148
|
+
"https://doi.org/10.1177/152700250000100304.")
|
|
149
|
+
|
|
150
|
+
r = compute("chicago", "book",
|
|
151
|
+
authors=[{"family": "McGuire", "given": "Ian"}],
|
|
152
|
+
title="The North Water", year=2016, place="London",
|
|
153
|
+
publisher="Simon & Schuster", locator=22)
|
|
154
|
+
chk("chicago.book", md(r),
|
|
155
|
+
"McGuire, Ian. 2016. *The North Water*. London: Simon & Schuster.")
|
|
156
|
+
chk("chicago.book.intext", r["intext"], "(McGuire 2016, 22)")
|
|
157
|
+
|
|
158
|
+
r = compute("chicago", "chapter",
|
|
159
|
+
authors=[{"family": "Stewart", "given": "Bob"}],
|
|
160
|
+
title="Wag of the Tail: Reflecting on Pet Ownership", year=2007,
|
|
161
|
+
book_title="Enriching Our Lives with Animals",
|
|
162
|
+
editors=[{"family": "Jaimeson", "given": "John"}],
|
|
163
|
+
pages="220-90", place="Toronto", publisher="Petlove Press")
|
|
164
|
+
chk("chicago.chapter", md(r),
|
|
165
|
+
'Stewart, Bob. 2007. "Wag of the Tail: Reflecting on Pet Ownership." '
|
|
166
|
+
"In *Enriching Our Lives with Animals*, edited by John Jaimeson, 220–90. "
|
|
167
|
+
"Toronto: Petlove Press.")
|
|
168
|
+
|
|
169
|
+
r = compute("chicago", "web",
|
|
170
|
+
authors=[{"family": "McCombes", "given": "Shona"}],
|
|
171
|
+
title="Creating an MLA Heading", year=2019, container="Scribbr",
|
|
172
|
+
url="https://www.scribbr.com/mla/heading/")
|
|
173
|
+
chk("chicago.web", md(r),
|
|
174
|
+
'McCombes, Shona. 2019. "Creating an MLA Heading." Scribbr. '
|
|
175
|
+
"https://www.scribbr.com/mla/heading/.")
|
|
176
|
+
|
|
177
|
+
# Chicago 2-author in-text
|
|
178
|
+
r = compute("chicago", "book",
|
|
179
|
+
authors=[{"family": "Grazer", "given": "Brian"},
|
|
180
|
+
{"family": "Fishman", "given": "Charles"}],
|
|
181
|
+
title="A Curious Mind", year=2015, place="New York",
|
|
182
|
+
publisher="Simon & Schuster")
|
|
183
|
+
chk("chicago.2auth.intext", r["intext"], "(Grazer and Fishman 2015)")
|
|
184
|
+
|
|
185
|
+
# ---------------------------------------------------------------- BibTeX
|
|
186
|
+
r = compute("bibtex", "article",
|
|
187
|
+
authors=[{"family": "McGuire", "given": "Ian"}],
|
|
188
|
+
title="The North Water", year=2016,
|
|
189
|
+
container="Journal of Sports Economics", volume=1, issue=3,
|
|
190
|
+
pages="257-276", doi="10.1/x")
|
|
191
|
+
bib = r["bibtex"]
|
|
192
|
+
assert bib.startswith("@article{mcguire2016,"), bib
|
|
193
|
+
assert "author = {McGuire, Ian}" in bib, bib
|
|
194
|
+
assert "journal = {Journal of Sports Economics}" in bib, bib
|
|
195
|
+
assert "volume = {1}" in bib and "number = {3}" in bib, bib
|
|
196
|
+
assert "pages = {257--276}" in bib, bib
|
|
197
|
+
assert "year = {2016}" in bib, bib
|
|
198
|
+
PASS += 1
|
|
199
|
+
|
|
200
|
+
r = compute("bibtex", "book",
|
|
201
|
+
authors=[{"family": "McGuire", "given": "Ian"}],
|
|
202
|
+
title="The North Water", year=2016, place="London",
|
|
203
|
+
publisher="Simon & Schuster")
|
|
204
|
+
assert r["bibtex"].startswith("@book{mcguire2016,"), r["bibtex"]
|
|
205
|
+
assert "publisher = {Simon & Schuster}" in r["bibtex"], r["bibtex"]
|
|
206
|
+
assert "address = {London}" in r["bibtex"], r["bibtex"]
|
|
207
|
+
PASS += 1
|
|
208
|
+
|
|
209
|
+
# ---------------------------------------------------------------- helpers / edge
|
|
210
|
+
# html + plain renderings
|
|
211
|
+
r = compute("apa", "book", authors=[{"family": "Anderson", "given": "Benedict"}],
|
|
212
|
+
title="Imagined communities", year=1983, publisher="Verso")
|
|
213
|
+
assert r["reference"]["html"] == \
|
|
214
|
+
"Anderson, B. (1983). <i>Imagined communities</i>. Verso.", r["reference"]["html"]
|
|
215
|
+
assert r["reference"]["plain"] == \
|
|
216
|
+
"Anderson, B. (1983). Imagined communities. Verso.", r["reference"]["plain"]
|
|
217
|
+
PASS += 1
|
|
218
|
+
|
|
219
|
+
# string-form authors accepted
|
|
220
|
+
r = compute("mla", "book", authors="Thompson, Paul B.",
|
|
221
|
+
title="From Field to Fork: Food Ethics for Everyone",
|
|
222
|
+
publisher="Oxford UP", year=2015)
|
|
223
|
+
assert md(r).startswith("Thompson, Paul B."), md(r)
|
|
224
|
+
PASS += 1
|
|
225
|
+
|
|
226
|
+
# n.d. handling
|
|
227
|
+
r = compute("apa", "web", authors=[{"family": "Scribbr", "given": ""}],
|
|
228
|
+
title="Academic proofreading", container="Scribbr",
|
|
229
|
+
url="https://www.scribbr.com/proofreading-editing/")
|
|
230
|
+
assert "(n.d.)" in md(r), md(r)
|
|
231
|
+
PASS += 1
|
|
232
|
+
|
|
233
|
+
# title_case helper
|
|
234
|
+
r = compute("chicago", "book", authors=[{"family": "Doe", "given": "Jane"}],
|
|
235
|
+
title="the lord of the rings and the two towers", year=2001,
|
|
236
|
+
place="NY", publisher="X", title_case=True)
|
|
237
|
+
assert "*The Lord of the Rings and the Two Towers*" in md(r), md(r)
|
|
238
|
+
PASS += 1
|
|
239
|
+
|
|
240
|
+
# bad input
|
|
241
|
+
for bad in (lambda: compute("xx", "book", title="t"),
|
|
242
|
+
lambda: compute("apa", "zz", title="t"),
|
|
243
|
+
lambda: compute("apa", "book", title="")):
|
|
244
|
+
try:
|
|
245
|
+
bad()
|
|
246
|
+
FAIL += 1
|
|
247
|
+
print("FAIL: expected ValueError")
|
|
248
|
+
except ValueError:
|
|
249
|
+
PASS += 1
|
|
250
|
+
|
|
251
|
+
print(f"\n{PASS} passed, {FAIL} failed")
|
|
252
|
+
sys.exit(1 if FAIL else 0)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Authority tests for /proofread — each rule fires on a positive case; exceptions don't.
|
|
2
|
+
Green = shippable. Run: python tests/test_proofread.py
|
|
3
|
+
"""
|
|
4
|
+
import sys, os
|
|
5
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
|
6
|
+
from touchstone.proofreading import compute, sample_output # noqa: E402
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def types(text):
|
|
10
|
+
return {i["type"] for i in compute(text)["issues"]}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def run():
|
|
14
|
+
p = f = 0
|
|
15
|
+
|
|
16
|
+
def check(name, cond):
|
|
17
|
+
nonlocal p, f
|
|
18
|
+
p += bool(cond); f += (not cond)
|
|
19
|
+
print(f" {'PASS' if cond else 'FAIL'} {name}")
|
|
20
|
+
|
|
21
|
+
check("repeated word", "repeated_word" in types("I saw the the dog."))
|
|
22
|
+
check("multiple spaces", "multiple_spaces" in types("Hello world."))
|
|
23
|
+
check("space before punctuation", "space_before_punctuation" in types("Wait , what ?"))
|
|
24
|
+
check("missing space after comma", "missing_space" in types("eggs,milk,bread"))
|
|
25
|
+
check("repeated punctuation", "repeated_punctuation" in types("Really?!"))
|
|
26
|
+
check("ellipsis NOT flagged as repeated punct", "repeated_punctuation" not in types("Well... okay."))
|
|
27
|
+
check("wordy phrase", "wordy_phrase" in types("We met in order to plan."))
|
|
28
|
+
check("filler word", "filler_word" in types("It was very good."))
|
|
29
|
+
check("passive voice (ed)", "passive_voice" in types("The ball was thrown."))
|
|
30
|
+
check("passive voice (irregular)", "passive_voice" in types("The cake was eaten."))
|
|
31
|
+
check("adverb flagged", "adverb" in types("She ran quickly."))
|
|
32
|
+
check("'only' NOT flagged as adverb", "adverb" not in types("I have only one."))
|
|
33
|
+
check("a->an before vowel", "a_an" in types("That is a apple."))
|
|
34
|
+
check("an->a before consonant", "a_an" in types("It is an dog."))
|
|
35
|
+
check("'an hour' NOT flagged", "a_an" not in types("in an hour"))
|
|
36
|
+
check("'a university' NOT flagged", "a_an" not in types("a university degree"))
|
|
37
|
+
check("'a hour' flagged (use an)", "a_an" in types("in a hour"))
|
|
38
|
+
|
|
39
|
+
long_sent = " ".join(f"w{i}" for i in range(35)) + "."
|
|
40
|
+
check("long sentence", "long_sentence" in types(long_sent))
|
|
41
|
+
check("short sentence not long", "long_sentence" not in types("A short one."))
|
|
42
|
+
check("lowercase start flagged", "capitalization" in types("the day was bright."))
|
|
43
|
+
check("proper start not flagged", "capitalization" not in types("The day was bright."))
|
|
44
|
+
|
|
45
|
+
clean = "The dog ran across the yard. A cat watched from the fence."
|
|
46
|
+
check("clean text has no high-confidence structural issues",
|
|
47
|
+
not any(i["confidence"] == "high" for i in compute(clean)["issues"]))
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
compute(" "); check("empty raises", False)
|
|
51
|
+
except ValueError:
|
|
52
|
+
check("empty raises", True)
|
|
53
|
+
|
|
54
|
+
so = sample_output()
|
|
55
|
+
check("sample has issues + summary", so["issue_count"] > 0 and isinstance(so["summary"], dict))
|
|
56
|
+
check("issues carry offsets", all("offset" in i for i in so["issues"]))
|
|
57
|
+
|
|
58
|
+
print(f"\n{p} passed, {f} failed")
|
|
59
|
+
return f == 0
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == "__main__":
|
|
63
|
+
sys.exit(0 if run() else 1)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Authority tests for /readability — published formulas + the stated syllable heuristic.
|
|
2
|
+
Green = shippable. Run: python tests/test_readability.py
|
|
3
|
+
"""
|
|
4
|
+
import sys, os, math
|
|
5
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
|
6
|
+
from touchstone.readability import compute, syllables # noqa: E402
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def run():
|
|
10
|
+
p = f = 0
|
|
11
|
+
|
|
12
|
+
def check(name, cond):
|
|
13
|
+
nonlocal p, f
|
|
14
|
+
p += bool(cond); f += (not cond)
|
|
15
|
+
print(f" {'PASS' if cond else 'FAIL'} {name}")
|
|
16
|
+
|
|
17
|
+
# 1) syllable heuristic on known words
|
|
18
|
+
known = {"cat": 1, "hello": 2, "table": 2, "make": 1, "queue": 1, "the": 1,
|
|
19
|
+
"bright": 1, "quickly": 2, "computer": 3, "beautiful": 3}
|
|
20
|
+
for w, n in known.items():
|
|
21
|
+
check(f"syllables({w})={n}", syllables(w) == n)
|
|
22
|
+
|
|
23
|
+
# 2) hand-computed exact example: "The cat sat." -> W=3, S=1, Sy=3, letters=9
|
|
24
|
+
c = compute("The cat sat.")
|
|
25
|
+
cn = c["counts"]
|
|
26
|
+
check("counts: 3 words/1 sentence/3 syllables/9 letters",
|
|
27
|
+
(cn["words"], cn["sentences"], cn["syllables"], cn["letters"]) == (3, 1, 3, 9))
|
|
28
|
+
# FK = 0.39*3 + 11.8*1 - 15.59 = -2.62 ; ease = 206.835 - 1.015*3 - 84.6*1 = 119.19
|
|
29
|
+
check("flesch_kincaid_grade == -2.62", c["scores"]["flesch_kincaid_grade"] == -2.62)
|
|
30
|
+
check("flesch_reading_ease == 119.19", c["scores"]["flesch_reading_ease"] == 119.19)
|
|
31
|
+
# ARI = 4.71*3 + 0.5*3 - 21.43 = -5.8
|
|
32
|
+
check("ARI == -5.8", c["scores"]["automated_readability_index"] == -5.8)
|
|
33
|
+
|
|
34
|
+
# 3) formula self-consistency on a longer text (scores match a re-derivation from counts)
|
|
35
|
+
text = ("Readability is the ease with which a reader can understand a written text. "
|
|
36
|
+
"Short sentences and common words make prose easier to read. "
|
|
37
|
+
"Long, convoluted constructions with polysyllabic terminology impede comprehension considerably.")
|
|
38
|
+
c = compute(text); cn = c["counts"]; s = c["scores"]
|
|
39
|
+
W, S, Sy, Lt, CW = cn["words"], cn["sentences"], cn["syllables"], cn["letters"], cn["complex_words"]
|
|
40
|
+
wps, spw = W / S, Sy / W
|
|
41
|
+
exp = {
|
|
42
|
+
"flesch_reading_ease": round(206.835 - 1.015 * wps - 84.6 * spw, 2),
|
|
43
|
+
"flesch_kincaid_grade": round(0.39 * wps + 11.8 * spw - 15.59, 2),
|
|
44
|
+
"gunning_fog": round(0.4 * (wps + 100 * (CW / W)), 2),
|
|
45
|
+
"smog": round(1.0430 * math.sqrt(CW * (30 / S)) + 3.1291, 2),
|
|
46
|
+
"coleman_liau": round(0.0588 * (Lt / W * 100) - 0.296 * (S / W * 100) - 15.8, 2),
|
|
47
|
+
"automated_readability_index": round(4.71 * (Lt / W) + 0.5 * wps - 21.43, 2),
|
|
48
|
+
}
|
|
49
|
+
for k, v in exp.items():
|
|
50
|
+
check(f"self-consistent {k} ({s[k]})", s[k] == v)
|
|
51
|
+
|
|
52
|
+
# 4) sanity: a simple text reads EASY (high Flesch ease), a dense one HARDER
|
|
53
|
+
easy = compute("I see the dog. The dog can run. We like the dog.")["scores"]["flesch_reading_ease"]
|
|
54
|
+
hard = compute("Notwithstanding the aforementioned considerations, the implementation "
|
|
55
|
+
"necessitates comprehensive architectural reconfiguration.")["scores"]["flesch_reading_ease"]
|
|
56
|
+
check("easy text scores higher ease than dense text", easy > hard)
|
|
57
|
+
check("easy text is genuinely easy (>90)", easy > 90)
|
|
58
|
+
|
|
59
|
+
# 5) guards + sample
|
|
60
|
+
try:
|
|
61
|
+
compute(" "); check("empty raises", False)
|
|
62
|
+
except ValueError:
|
|
63
|
+
check("empty raises", True)
|
|
64
|
+
from touchstone.readability import sample_output
|
|
65
|
+
check("sample_output works", "scores" in sample_output())
|
|
66
|
+
|
|
67
|
+
print(f"\n{p} passed, {f} failed")
|
|
68
|
+
return f == 0
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
if __name__ == "__main__":
|
|
72
|
+
sys.exit(0 if run() else 1)
|