sloplint 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.
- sloplint-0.1.0/.github/workflows/deploy.yml +44 -0
- sloplint-0.1.0/.github/workflows/lint.yml +15 -0
- sloplint-0.1.0/.github/workflows/pypi.yml +24 -0
- sloplint-0.1.0/.github/workflows/release.yml +28 -0
- sloplint-0.1.0/.github/workflows/test.yml +15 -0
- sloplint-0.1.0/.gitignore +2 -0
- sloplint-0.1.0/PKG-INFO +40 -0
- sloplint-0.1.0/makefile +13 -0
- sloplint-0.1.0/pyproject.toml +25 -0
- sloplint-0.1.0/readme.md +29 -0
- sloplint-0.1.0/sloplint/__init__.py +3 -0
- sloplint-0.1.0/sloplint/__main__.py +4 -0
- sloplint-0.1.0/sloplint/cli.py +43 -0
- sloplint-0.1.0/sloplint/lint.py +35 -0
- sloplint-0.1.0/tests/test_sloplint.py +41 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Deploy
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: ['main']
|
|
5
|
+
workflow_dispatch:
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
pages: write
|
|
10
|
+
id-token: write
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: 'pages'
|
|
14
|
+
cancel-in-progress: false
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
deploy:
|
|
18
|
+
environment:
|
|
19
|
+
name: github-pages
|
|
20
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
21
|
+
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
|
|
24
|
+
steps:
|
|
25
|
+
- name: Checkout
|
|
26
|
+
uses: actions/checkout@v5
|
|
27
|
+
|
|
28
|
+
- name: Setup uv
|
|
29
|
+
uses: astral-sh/setup-uv@v5
|
|
30
|
+
|
|
31
|
+
- name: Setup Pages
|
|
32
|
+
uses: actions/configure-pages@v5
|
|
33
|
+
|
|
34
|
+
- name: Build Site
|
|
35
|
+
run: make
|
|
36
|
+
|
|
37
|
+
- name: Upload artifact
|
|
38
|
+
uses: actions/upload-pages-artifact@v3
|
|
39
|
+
with:
|
|
40
|
+
path: '.'
|
|
41
|
+
|
|
42
|
+
- name: Deploy to GitHub Pages
|
|
43
|
+
id: deployment
|
|
44
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*.*.*'
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
PyPI:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write
|
|
13
|
+
steps:
|
|
14
|
+
- name: Checkout
|
|
15
|
+
uses: actions/checkout@v5
|
|
16
|
+
|
|
17
|
+
- name: Setup uv
|
|
18
|
+
uses: astral-sh/setup-uv@v5
|
|
19
|
+
|
|
20
|
+
- name: Build
|
|
21
|
+
run: uv build
|
|
22
|
+
|
|
23
|
+
- name: Publish
|
|
24
|
+
run: uv publish
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*.*.*'
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
Release:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: write
|
|
13
|
+
steps:
|
|
14
|
+
- name: Checkout
|
|
15
|
+
uses: actions/checkout@v5
|
|
16
|
+
|
|
17
|
+
- name: Setup uv
|
|
18
|
+
uses: astral-sh/setup-uv@v5
|
|
19
|
+
|
|
20
|
+
- name: Build distribution
|
|
21
|
+
run: uv build
|
|
22
|
+
|
|
23
|
+
- name: Release
|
|
24
|
+
uses: softprops/action-gh-release@v2
|
|
25
|
+
with:
|
|
26
|
+
files: |
|
|
27
|
+
dist/*.tar.gz
|
|
28
|
+
dist/*.whl
|
sloplint-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sloplint
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A linter to detect AI-generated markdown prose
|
|
5
|
+
Author-email: Jon Craton <jncraton@gmail.com>
|
|
6
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
7
|
+
Classifier: Operating System :: OS Independent
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# sloplint
|
|
13
|
+
|
|
14
|
+
A linter to detect AI-generated markdown prose
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
pip install sloplint
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
sloplint myfile.md
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or run without installation as:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
uxv sloplint readme.md
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
If AI-generated text issues are identified, sloplint will exit with a failure code reporting each issue it discovered just like a linter would.
|
|
35
|
+
|
|
36
|
+
## Triggers
|
|
37
|
+
|
|
38
|
+
- em-dash
|
|
39
|
+
- emojis
|
|
40
|
+
- bold text
|
sloplint-0.1.0/makefile
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
all: test
|
|
2
|
+
|
|
3
|
+
lint:
|
|
4
|
+
uv run --with black==24.1.0 python -m black --check sloplint tests
|
|
5
|
+
|
|
6
|
+
format:
|
|
7
|
+
uv run --with black==24.1.0 python -m black sloplint tests
|
|
8
|
+
|
|
9
|
+
test:
|
|
10
|
+
uv run --with pytest==7.4.0 python -m pytest
|
|
11
|
+
|
|
12
|
+
clean:
|
|
13
|
+
rm -rf .pytest_cache **/__pycache__ build dist *.egg-info
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sloplint"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A linter to detect AI-generated markdown prose"
|
|
9
|
+
readme = "readme.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name = "Jon Craton", email = "jncraton@gmail.com" }
|
|
12
|
+
]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
]
|
|
18
|
+
requires-python = ">=3.10"
|
|
19
|
+
dependencies = []
|
|
20
|
+
|
|
21
|
+
[project.scripts]
|
|
22
|
+
sloplint = "sloplint.cli:main"
|
|
23
|
+
|
|
24
|
+
[tool.hatch.build.targets.wheel]
|
|
25
|
+
packages = ["sloplint"]
|
sloplint-0.1.0/readme.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# sloplint
|
|
2
|
+
|
|
3
|
+
A linter to detect AI-generated markdown prose
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pip install sloplint
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
sloplint myfile.md
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or run without installation as:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
uxv sloplint readme.md
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
If AI-generated text issues are identified, sloplint will exit with a failure code reporting each issue it discovered just like a linter would.
|
|
24
|
+
|
|
25
|
+
## Triggers
|
|
26
|
+
|
|
27
|
+
- em-dash
|
|
28
|
+
- emojis
|
|
29
|
+
- bold text
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Sequence
|
|
4
|
+
|
|
5
|
+
from .lint import find_markdown_issues
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _parse_args(args: Sequence[str] | None = None) -> argparse.Namespace:
|
|
9
|
+
parser = argparse.ArgumentParser(
|
|
10
|
+
prog="sloplint",
|
|
11
|
+
description="A linter to detect AI-generated markdown prose",
|
|
12
|
+
)
|
|
13
|
+
parser.add_argument("paths", nargs="+", help="Markdown file paths to lint")
|
|
14
|
+
return parser.parse_args(args)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
18
|
+
args = _parse_args(argv)
|
|
19
|
+
messages: list[str] = []
|
|
20
|
+
|
|
21
|
+
for path_text in args.paths:
|
|
22
|
+
path = Path(path_text)
|
|
23
|
+
if not path.exists():
|
|
24
|
+
print(f"{path}: file not found")
|
|
25
|
+
return 2
|
|
26
|
+
if not path.is_file():
|
|
27
|
+
print(f"{path}: not a file")
|
|
28
|
+
return 2
|
|
29
|
+
|
|
30
|
+
content = path.read_text(encoding="utf-8")
|
|
31
|
+
issues = find_markdown_issues(content)
|
|
32
|
+
for issue in issues:
|
|
33
|
+
messages.append(f"{path}:{issue}")
|
|
34
|
+
|
|
35
|
+
if messages:
|
|
36
|
+
print("\n".join(messages))
|
|
37
|
+
return 1
|
|
38
|
+
|
|
39
|
+
return 0
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
if __name__ == "__main__":
|
|
43
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Iterable
|
|
3
|
+
|
|
4
|
+
EM_DASH_RE = re.compile(r"—")
|
|
5
|
+
BOLD_RE = re.compile(r"(?:\*\*[^*\n]+\*\*|__[^_\n]+__)")
|
|
6
|
+
EMOJI_RE = re.compile(
|
|
7
|
+
r"["
|
|
8
|
+
r"\U0001F300-\U0001F5FF"
|
|
9
|
+
r"\U0001F600-\U0001F64F"
|
|
10
|
+
r"\U0001F680-\U0001F6FF"
|
|
11
|
+
r"\U0001F700-\U0001F77F"
|
|
12
|
+
r"\U0001F780-\U0001F7FF"
|
|
13
|
+
r"\U0001F800-\U0001F8FF"
|
|
14
|
+
r"\U0001F900-\U0001F9FF"
|
|
15
|
+
r"\U0001FA00-\U0001FA6F"
|
|
16
|
+
r"\U0001FA70-\U0001FAFF"
|
|
17
|
+
r"\u2600-\u26FF"
|
|
18
|
+
r"\u2700-\u27BF"
|
|
19
|
+
r"]"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def find_markdown_issues(content: str) -> list[str]:
|
|
24
|
+
"""Return a list of markdown issues detected in the content."""
|
|
25
|
+
issues: list[str] = []
|
|
26
|
+
|
|
27
|
+
for line_number, line in enumerate(content.splitlines(), start=1):
|
|
28
|
+
if EM_DASH_RE.search(line):
|
|
29
|
+
issues.append(f"{line_number}: em-dash detected")
|
|
30
|
+
if EMOJI_RE.search(line):
|
|
31
|
+
issues.append(f"{line_number}: emoji detected")
|
|
32
|
+
if BOLD_RE.search(line):
|
|
33
|
+
issues.append(f"{line_number}: bold markdown detected")
|
|
34
|
+
|
|
35
|
+
return issues
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from sloplint.cli import main
|
|
2
|
+
from sloplint.lint import find_markdown_issues
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_find_markdown_issues():
|
|
6
|
+
content = "First line — with em dash\nSecond line **bold**\nThird line 😊\n"
|
|
7
|
+
issues = find_markdown_issues(content)
|
|
8
|
+
|
|
9
|
+
assert "1: em-dash detected" in issues
|
|
10
|
+
assert "2: bold markdown detected" in issues
|
|
11
|
+
assert "3: emoji detected" in issues
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_main_returns_non_zero_for_issues(tmp_path, capsys):
|
|
15
|
+
path = tmp_path / "sample.md"
|
|
16
|
+
path.write_text("This is **bold**\n", encoding="utf-8")
|
|
17
|
+
|
|
18
|
+
exit_code = main([str(path)])
|
|
19
|
+
captured = capsys.readouterr()
|
|
20
|
+
|
|
21
|
+
assert exit_code == 1
|
|
22
|
+
assert f"{path}:1: bold markdown detected" in captured.out
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_main_returns_zero_for_clean_file(tmp_path):
|
|
26
|
+
path = tmp_path / "clean.md"
|
|
27
|
+
path.write_text("This is plain markdown\n", encoding="utf-8")
|
|
28
|
+
|
|
29
|
+
exit_code = main([str(path)])
|
|
30
|
+
|
|
31
|
+
assert exit_code == 0
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_main_reports_missing_file(tmp_path, capsys):
|
|
35
|
+
path = tmp_path / "missing.md"
|
|
36
|
+
|
|
37
|
+
exit_code = main([str(path)])
|
|
38
|
+
captured = capsys.readouterr()
|
|
39
|
+
|
|
40
|
+
assert exit_code == 2
|
|
41
|
+
assert "file not found" in captured.out
|