wordlevel 0.0.1a2__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.
Files changed (117) hide show
  1. wordlevel-0.0.1a2/.github/actions/detect-changed-lexicons/action.yaml +21 -0
  2. wordlevel-0.0.1a2/.github/actions/process-lexicons/action.yaml +18 -0
  3. wordlevel-0.0.1a2/.github/scripts/process_lexicons.py +74 -0
  4. wordlevel-0.0.1a2/.github/workflows/lexicons-deployment.yaml +79 -0
  5. wordlevel-0.0.1a2/.github/workflows/lexicons-pull-request.yaml +56 -0
  6. wordlevel-0.0.1a2/.github/workflows/pyright.yaml +39 -0
  7. wordlevel-0.0.1a2/.github/workflows/release.yaml +90 -0
  8. wordlevel-0.0.1a2/.github/workflows/ruff.yaml +25 -0
  9. wordlevel-0.0.1a2/.github/workflows/test.yaml +39 -0
  10. wordlevel-0.0.1a2/.gitignore +225 -0
  11. wordlevel-0.0.1a2/.pre-commit-config.yaml +16 -0
  12. wordlevel-0.0.1a2/LICENSE +7 -0
  13. wordlevel-0.0.1a2/PKG-INFO +17 -0
  14. wordlevel-0.0.1a2/README.md +3 -0
  15. wordlevel-0.0.1a2/docs/snippets/catalog/catalog.py +73 -0
  16. wordlevel-0.0.1a2/docs/snippets/catalog/plot.py +25 -0
  17. wordlevel-0.0.1a2/docs/snippets/comparison/comparison.py +12 -0
  18. wordlevel-0.0.1a2/docs/snippets/comparison/normalization.py +15 -0
  19. wordlevel-0.0.1a2/docs/snippets/comparison/score.py +50 -0
  20. wordlevel-0.0.1a2/docs/snippets/lexicons/lexicon.py +12 -0
  21. wordlevel-0.0.1a2/lexicons/SocialSent/LICENSE +127 -0
  22. wordlevel-0.0.1a2/lexicons/SocialSent/README.md +17 -0
  23. wordlevel-0.0.1a2/lexicons/SocialSent/__init__.py +0 -0
  24. wordlevel-0.0.1a2/lexicons/SocialSent/process.py +51 -0
  25. wordlevel-0.0.1a2/lexicons/__init__.py +0 -0
  26. wordlevel-0.0.1a2/lexicons/huggingface.py +122 -0
  27. wordlevel-0.0.1a2/lexicons/labMT/LICENSE +177 -0
  28. wordlevel-0.0.1a2/lexicons/labMT/README.md +20 -0
  29. wordlevel-0.0.1a2/lexicons/labMT/__init__.py +0 -0
  30. wordlevel-0.0.1a2/lexicons/labMT/process.py +61 -0
  31. wordlevel-0.0.1a2/lexicons/processor.py +82 -0
  32. wordlevel-0.0.1a2/pyproject.toml +70 -0
  33. wordlevel-0.0.1a2/src/wordlevel/__init__.py +11 -0
  34. wordlevel-0.0.1a2/src/wordlevel/_version.py +24 -0
  35. wordlevel-0.0.1a2/src/wordlevel/api/__init__.py +0 -0
  36. wordlevel-0.0.1a2/src/wordlevel/api/catalog/__init__.py +0 -0
  37. wordlevel-0.0.1a2/src/wordlevel/api/catalog/catalog.py +265 -0
  38. wordlevel-0.0.1a2/src/wordlevel/api/catalog/constructors.py +287 -0
  39. wordlevel-0.0.1a2/src/wordlevel/api/catalog/plot.py +516 -0
  40. wordlevel-0.0.1a2/src/wordlevel/api/comparison/__init__.py +0 -0
  41. wordlevel-0.0.1a2/src/wordlevel/api/comparison/comparison.py +386 -0
  42. wordlevel-0.0.1a2/src/wordlevel/api/comparison/normalization.py +53 -0
  43. wordlevel-0.0.1a2/src/wordlevel/api/comparison/score.py +1136 -0
  44. wordlevel-0.0.1a2/src/wordlevel/api/lexicons/__init__.py +0 -0
  45. wordlevel-0.0.1a2/src/wordlevel/api/lexicons/lexicon.py +91 -0
  46. wordlevel-0.0.1a2/src/wordlevel/api/lexicons/types/__init__.py +0 -0
  47. wordlevel-0.0.1a2/src/wordlevel/api/lexicons/types/labmt.py +15 -0
  48. wordlevel-0.0.1a2/src/wordlevel/api/lexicons/types/socialsent.py +21 -0
  49. wordlevel-0.0.1a2/src/wordlevel/configs.py +374 -0
  50. wordlevel-0.0.1a2/src/wordlevel/constants.py +26 -0
  51. wordlevel-0.0.1a2/src/wordlevel/core/__init__.py +0 -0
  52. wordlevel-0.0.1a2/src/wordlevel/core/catalog/__init__.py +0 -0
  53. wordlevel-0.0.1a2/src/wordlevel/core/catalog/plot/__init__.py +0 -0
  54. wordlevel-0.0.1a2/src/wordlevel/core/catalog/plot/shift/__init__.py +0 -0
  55. wordlevel-0.0.1a2/src/wordlevel/core/catalog/plot/shift/render/__init__.py +0 -0
  56. wordlevel-0.0.1a2/src/wordlevel/core/catalog/plot/shift/render/bars.py +434 -0
  57. wordlevel-0.0.1a2/src/wordlevel/core/catalog/plot/shift/render/border.py +167 -0
  58. wordlevel-0.0.1a2/src/wordlevel/core/catalog/plot/shift/render/labels.py +244 -0
  59. wordlevel-0.0.1a2/src/wordlevel/core/catalog/plot/shift/render/scales.py +147 -0
  60. wordlevel-0.0.1a2/src/wordlevel/core/catalog/plot/shift/render/shift.py +259 -0
  61. wordlevel-0.0.1a2/src/wordlevel/core/catalog/plot/shift/render/totals.py +364 -0
  62. wordlevel-0.0.1a2/src/wordlevel/core/catalog/plot/shift/render/zero_line.py +43 -0
  63. wordlevel-0.0.1a2/src/wordlevel/core/catalog/plot/shift/transform.py +103 -0
  64. wordlevel-0.0.1a2/src/wordlevel/core/catalog/plot/utils.py +11 -0
  65. wordlevel-0.0.1a2/src/wordlevel/core/comparison/__init__.py +0 -0
  66. wordlevel-0.0.1a2/src/wordlevel/core/comparison/normalization.py +59 -0
  67. wordlevel-0.0.1a2/src/wordlevel/core/comparison/score/__init__.py +0 -0
  68. wordlevel-0.0.1a2/src/wordlevel/core/comparison/score/attribution.py +134 -0
  69. wordlevel-0.0.1a2/src/wordlevel/core/comparison/score/components.py +285 -0
  70. wordlevel-0.0.1a2/src/wordlevel/core/comparison/score/lexicon.py +126 -0
  71. wordlevel-0.0.1a2/src/wordlevel/core/comparison/score/metrics/__init__.py +0 -0
  72. wordlevel-0.0.1a2/src/wordlevel/core/comparison/score/metrics/entropy.py +84 -0
  73. wordlevel-0.0.1a2/src/wordlevel/core/comparison/score/metrics/frequency.py +6 -0
  74. wordlevel-0.0.1a2/src/wordlevel/core/comparison/score/metrics/jensen_shannon_divergence.py +75 -0
  75. wordlevel-0.0.1a2/src/wordlevel/core/comparison/score/metrics/kullback_leibler_divergence.py +101 -0
  76. wordlevel-0.0.1a2/src/wordlevel/core/comparison/score/metrics/rank_divergence.py +92 -0
  77. wordlevel-0.0.1a2/src/wordlevel/core/comparison/score/plan.py +268 -0
  78. wordlevel-0.0.1a2/src/wordlevel/core/lexicons/__init__.py +0 -0
  79. wordlevel-0.0.1a2/src/wordlevel/core/lexicons/load.py +43 -0
  80. wordlevel-0.0.1a2/src/wordlevel/core/utils.py +61 -0
  81. wordlevel-0.0.1a2/src/wordlevel/datasets/__init__.py +0 -0
  82. wordlevel-0.0.1a2/src/wordlevel/datasets/data/presidental_speeches.parquet +0 -0
  83. wordlevel-0.0.1a2/src/wordlevel/datasets/dataset.py +71 -0
  84. wordlevel-0.0.1a2/tests/__init__.py +0 -0
  85. wordlevel-0.0.1a2/tests/docs/__init__.py +0 -0
  86. wordlevel-0.0.1a2/tests/docs/conftest.py +32 -0
  87. wordlevel-0.0.1a2/tests/docs/test_snippets.py +19 -0
  88. wordlevel-0.0.1a2/tests/lexicons/__init__.py +0 -0
  89. wordlevel-0.0.1a2/tests/lexicons/test_processors.py +41 -0
  90. wordlevel-0.0.1a2/tests/wordlevel/api/catalog/test_catalog.py +233 -0
  91. wordlevel-0.0.1a2/tests/wordlevel/api/catalog/test_constructors.py +395 -0
  92. wordlevel-0.0.1a2/tests/wordlevel/api/catalog/test_plot.py +340 -0
  93. wordlevel-0.0.1a2/tests/wordlevel/api/comparison/test_comparison.py +150 -0
  94. wordlevel-0.0.1a2/tests/wordlevel/api/comparison/test_comparison_result.py +502 -0
  95. wordlevel-0.0.1a2/tests/wordlevel/api/comparison/test_normalization.py +142 -0
  96. wordlevel-0.0.1a2/tests/wordlevel/api/comparison/test_score.py +564 -0
  97. wordlevel-0.0.1a2/tests/wordlevel/core/catalog/plot/shift/render/conftest.py +140 -0
  98. wordlevel-0.0.1a2/tests/wordlevel/core/catalog/plot/shift/render/test_bars.py +939 -0
  99. wordlevel-0.0.1a2/tests/wordlevel/core/catalog/plot/shift/render/test_border.py +170 -0
  100. wordlevel-0.0.1a2/tests/wordlevel/core/catalog/plot/shift/render/test_compose.py +296 -0
  101. wordlevel-0.0.1a2/tests/wordlevel/core/catalog/plot/shift/render/test_labels.py +370 -0
  102. wordlevel-0.0.1a2/tests/wordlevel/core/catalog/plot/shift/render/test_scales.py +254 -0
  103. wordlevel-0.0.1a2/tests/wordlevel/core/catalog/plot/shift/render/test_shift.py +200 -0
  104. wordlevel-0.0.1a2/tests/wordlevel/core/catalog/plot/shift/render/test_totals.py +372 -0
  105. wordlevel-0.0.1a2/tests/wordlevel/core/catalog/plot/shift/render/test_zero_line.py +95 -0
  106. wordlevel-0.0.1a2/tests/wordlevel/core/catalog/plot/shift/test_transform.py +221 -0
  107. wordlevel-0.0.1a2/tests/wordlevel/core/comparison/score/metrics/test_entropy.py +172 -0
  108. wordlevel-0.0.1a2/tests/wordlevel/core/comparison/score/metrics/test_jensen_shannon_divergence.py +190 -0
  109. wordlevel-0.0.1a2/tests/wordlevel/core/comparison/score/metrics/test_kullback_leibler_divergence.py +77 -0
  110. wordlevel-0.0.1a2/tests/wordlevel/core/comparison/score/metrics/test_rank_divergence.py +246 -0
  111. wordlevel-0.0.1a2/tests/wordlevel/core/comparison/score/test_attribution.py +160 -0
  112. wordlevel-0.0.1a2/tests/wordlevel/core/comparison/score/test_components.py +281 -0
  113. wordlevel-0.0.1a2/tests/wordlevel/core/comparison/score/test_lexicon.py +145 -0
  114. wordlevel-0.0.1a2/tests/wordlevel/core/comparison/score/test_plan.py +585 -0
  115. wordlevel-0.0.1a2/tests/wordlevel/core/comparison/test_normalization.py +61 -0
  116. wordlevel-0.0.1a2/tests/wordlevel/core/test_utils.py +90 -0
  117. wordlevel-0.0.1a2/tests/wordlevel/datasets/test_dataset.py +94 -0
@@ -0,0 +1,21 @@
1
+ name: 'Detect Changed Lexicons'
2
+ description: 'Determines which lexicons changed and how'
3
+
4
+ outputs:
5
+ lexicon_changes:
6
+ description: "A JSON array of lexicons and booleans indicating how they have changed"
7
+ value: ${{ steps.detect-lexicons.outputs.lexicon_changes }}
8
+
9
+ runs:
10
+ using: "composite"
11
+ steps:
12
+ - name: Get Changed Files
13
+ id: changed-files
14
+ uses: tj-actions/changed-files@v47.0.5
15
+ with:
16
+ files: lexicons/**
17
+
18
+ - name: Detected Changed Lexicon Files
19
+ id: detect-lexicons
20
+ shell: bash
21
+ run: python .github/scripts/process_lexicons.py "${{ steps.changed-files.outputs.all_changed_files }}"
@@ -0,0 +1,18 @@
1
+ name: 'Process Lexicon Data'
2
+ description: 'Downloads raw lexicon data from Hugging Face and processes it'
3
+
4
+ inputs:
5
+ lexicon:
6
+ description: 'The name of the lexicon'
7
+ required: true
8
+
9
+ runs:
10
+ using: "composite"
11
+ steps:
12
+ - name: Download Raw Lexicon Data
13
+ shell: bash
14
+ run: uv run python -m lexicons.huggingface --mode download --data raw --lexicon ${{ inputs.lexicon }}
15
+
16
+ - name: Process Lexicon
17
+ shell: bash
18
+ run: uv run python -m lexicons.${{ inputs.lexicon }}.process
@@ -0,0 +1,74 @@
1
+ import os
2
+ import argparse
3
+ import json
4
+ from pathlib import Path
5
+
6
+
7
+ def main():
8
+ """ """
9
+ parser = argparse.ArgumentParser(
10
+ description="Parse changed files to determine which lexicons need processing"
11
+ )
12
+ parser.add_argument(
13
+ "changed_files",
14
+ type=str,
15
+ help="A space-separated string of file paths changed in the commit",
16
+ )
17
+
18
+ get_changed_lexicons(parser.parse_args())
19
+
20
+
21
+ def get_changed_lexicons(args: argparse.Namespace) -> None:
22
+ """ """
23
+ if not args.changed_files.strip():
24
+ set_github_output("matrix_array", "[]")
25
+ return
26
+
27
+ lexicon_states = {}
28
+ changed_fs = args.changed_files.split()
29
+ for f_path in changed_fs:
30
+ # lexicons/{lexicon}/{changed_f}
31
+ path_parts = Path(f_path).parts
32
+
33
+ # The length check ensures it's a file in `lexicons/*/{lexicon}` and not just `lexicons/*`
34
+ if not (path_parts[0] == "lexicons" and len(path_parts) >= 3):
35
+ continue
36
+
37
+ lexicon = path_parts[1]
38
+ file_name = path_parts[2]
39
+
40
+ if lexicon not in lexicon_states:
41
+ lexicon_states[lexicon] = {
42
+ "name": lexicon,
43
+ "processor_changed": False,
44
+ "readme_changed": False,
45
+ "license_changed": False,
46
+ }
47
+
48
+ if file_name == "README.md":
49
+ lexicon_states[lexicon]["readme_changed"] = True
50
+ elif file_name == "LICENSE":
51
+ lexicon_states[lexicon]["license_changed"] = True
52
+ elif file_name == "process.py":
53
+ lexicon_states[lexicon]["processor_changed"] = True
54
+
55
+ # Output directly to the GitHub Actions $GITHUB_OUTPUT environment
56
+ set_github_output("lexicon_changes", json.dumps(list(lexicon_states.values())))
57
+
58
+
59
+ def set_github_output(key: str, value: str) -> None:
60
+ """
61
+ Writes a key-value pair to the GitHub Actions output file. If no such file exists in the
62
+ environment, just prints to the local standard out
63
+ """
64
+ github_output_path = os.environ.get("GITHUB_OUTPUT")
65
+
66
+ if github_output_path:
67
+ with open(github_output_path, "a") as outfile:
68
+ outfile.write(f"{key}={value}\n")
69
+ else:
70
+ print(f"{key}={value}")
71
+
72
+
73
+ if __name__ == "__main__":
74
+ main()
@@ -0,0 +1,79 @@
1
+ name: Deploy lexicons
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ paths:
8
+ # Only trigger this workflow if files inside the lexicons/ folder change
9
+ - 'lexicons/**'
10
+
11
+ jobs:
12
+ # ==========================================
13
+ # JOB 1: Detect and Categorize Changes
14
+ # ==========================================
15
+ changed-lexicons:
16
+ runs-on: ubuntu-latest
17
+ outputs:
18
+ lexicon_changes: ${{ steps.detect.outputs.lexicon_changes }}
19
+ steps:
20
+ - name: Checkout branch
21
+ uses: actions/checkout@v4
22
+ with:
23
+ fetch-depth: 0
24
+
25
+ - name: Detect Changes
26
+ id: detect
27
+ uses: ./.github/actions/detect-changed-lexicons
28
+
29
+
30
+ # ==========================================
31
+ # JOB 2: Process and Upload Lexicons
32
+ # ==========================================
33
+ deploy-lexicons:
34
+ needs: changed-lexicons
35
+ if: ${{ needs.changed-lexicons.outputs.lexicon_changes != '[]' && needs.changed-lexicons.outputs.lexicon_changes != '' }}
36
+ runs-on: ubuntu-latest
37
+ environment: huggingface
38
+ strategy:
39
+ matrix:
40
+ lexicon: ${{ fromJSON(needs.changed-lexicons.outputs.lexicon_changes) }}
41
+
42
+ steps:
43
+ - name: Checkout branch
44
+ uses: actions/checkout@v4
45
+
46
+ - name: Install uv
47
+ uses: astral-sh/setup-uv@v5
48
+ with:
49
+ enable-cache: true
50
+
51
+ - name: Install Python dependencies
52
+ run: uv sync --all-extras --dev
53
+
54
+ # --- Download and process lexicon ---
55
+ - name: Download and process lexicon
56
+ if: ${{ matrix.lexicon.processor_changed == true }}
57
+ uses: ./.github/actions/process-lexicons
58
+ with:
59
+ lexicon: ${{ matrix.lexicon.name }}
60
+
61
+ # --- Upload lexicon ---
62
+ - name: Upload lexicon
63
+ if: ${{ matrix.lexicon.processor_changed == true }}
64
+ env:
65
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
66
+ run: uv run python -m lexicons.huggingface --mode upload --data processed --lexicon ${{ matrix.lexicon.name }}
67
+
68
+ # --- Upload metadata ---
69
+ - name: Upload README.md
70
+ if: ${{ matrix.lexicon.readme_changed == true }}
71
+ env:
72
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
73
+ run: uv run python -m lexicons.huggingface --mode upload --lexicon ${{ matrix.lexicon.name }} --metadata README.md
74
+
75
+ - name: Upload LICENSE
76
+ if: ${{ matrix.lexicon.license_changed == true }}
77
+ env:
78
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
79
+ run: uv run python -m lexicons.huggingface --mode upload --lexicon ${{ matrix.lexicon.name }} --metadata LICENSE
@@ -0,0 +1,56 @@
1
+ name: Process lexicons
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - main
7
+ paths:
8
+ # Only trigger this workflow if files inside the lexicons/ folder change
9
+ - 'lexicons/**'
10
+
11
+ jobs:
12
+ # ==========================================
13
+ # JOB 1: Detect and Categorize Changes
14
+ # ==========================================
15
+ changed-lexicons:
16
+ runs-on: ubuntu-latest
17
+ outputs:
18
+ lexicon_changes: ${{ steps.detect.outputs.lexicon_changes }}
19
+ steps:
20
+ - name: Checkout branch
21
+ uses: actions/checkout@v4
22
+ with:
23
+ fetch-depth: 0
24
+
25
+ - name: Detect Changes
26
+ id: detect
27
+ uses: ./.github/actions/detect-changed-lexicons
28
+
29
+ # ==========================================
30
+ # JOB 2: Process Lexicons (No Uploading)
31
+ # ==========================================
32
+ process-lexicons:
33
+ needs: changed-lexicons
34
+ if: ${{ needs.changed-lexicons.outputs.lexicon_changes != '[]' && needs.changed-lexicons.outputs.lexicon_changes != '' }}
35
+ runs-on: ubuntu-latest
36
+ strategy:
37
+ matrix:
38
+ lexicon: ${{ fromJSON(needs.changed-lexicons.outputs.lexicon_changes) }}
39
+
40
+ steps:
41
+ - name: Checkout branch
42
+ uses: actions/checkout@v4
43
+
44
+ - name: Install uv
45
+ uses: astral-sh/setup-uv@v5
46
+ with:
47
+ enable-cache: true
48
+
49
+ - name: Install Python dependencies
50
+ run: uv sync --all-extras --dev
51
+
52
+ - name: Download and process lexicons
53
+ if: ${{ matrix.lexicon.processor_changed == true }}
54
+ uses: ./.github/actions/process-lexicons
55
+ with:
56
+ lexicon: ${{ matrix.lexicon.name }}
@@ -0,0 +1,39 @@
1
+ name: Pyright
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+ branches:
9
+ - main
10
+ workflow_call:
11
+ workflow_dispatch:
12
+
13
+ jobs:
14
+ type-check:
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ matrix:
18
+ python-version:
19
+ - "3.10"
20
+ - "3.14"
21
+ steps:
22
+ - name: Checkout branch
23
+ uses: actions/checkout@v4
24
+
25
+ - name: Enable uv caching
26
+ uses: astral-sh/setup-uv@v6
27
+ with:
28
+ enable-cache: true
29
+
30
+ - name: Install Python
31
+ uses: astral-sh/setup-uv@v6
32
+ with:
33
+ python-version: ${{ matrix.python-version }}
34
+
35
+ - name: Install dependencies
36
+ run: uv sync --group dev
37
+
38
+ - name: Run Pyright
39
+ run: uv run pyright
@@ -0,0 +1,90 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ test:
13
+ uses: ./.github/workflows/test.yaml
14
+
15
+ type-check:
16
+ uses: ./.github/workflows/pyright.yaml
17
+
18
+ lint:
19
+ uses: ./.github/workflows/ruff.yaml
20
+
21
+ build:
22
+ needs: [test, type-check, lint]
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - name: Checkout branch
26
+ uses: actions/checkout@v4
27
+ with:
28
+ fetch-depth: 0
29
+
30
+ - name: Install Python and enable uv caching
31
+ uses: astral-sh/setup-uv@v6
32
+ with:
33
+ python-version: "3.14"
34
+ enable-cache: true
35
+
36
+ - name: Build sdist and wheel
37
+ run: uv build
38
+
39
+ - name: Upload build artifacts
40
+ uses: actions/upload-artifact@v4
41
+ with:
42
+ name: dist
43
+ path: dist/
44
+
45
+ pypi:
46
+ needs: build
47
+ runs-on: ubuntu-latest
48
+ environment:
49
+ name: pypi
50
+ url: https://pypi.org/p/wordlevel
51
+ permissions:
52
+ id-token: write
53
+ steps:
54
+ - name: Download build artifacts
55
+ uses: actions/download-artifact@v4
56
+ with:
57
+ name: dist
58
+ path: dist/
59
+
60
+ - name: Publish to PyPI
61
+ uses: pypa/gh-action-pypi-publish@release/v1
62
+
63
+ github-release:
64
+ needs: [pypi]
65
+ runs-on: ubuntu-latest
66
+ permissions:
67
+ contents: write
68
+ steps:
69
+ - name: Download build artifacts
70
+ uses: actions/download-artifact@v4
71
+ with:
72
+ name: dist
73
+ path: dist/
74
+
75
+ - name: Determine if pre-release
76
+ id: prerelease
77
+ run: |
78
+ if [[ "${GITHUB_REF_NAME}" =~ (a[0-9]+|b[0-9]+|rc[0-9]+|dev[0-9]+)$ ]]; then
79
+ echo "flag=true" >> "$GITHUB_OUTPUT"
80
+ else
81
+ echo "flag=false" >> "$GITHUB_OUTPUT"
82
+ fi
83
+
84
+ - name: Create GitHub release
85
+ uses: softprops/action-gh-release@v2
86
+ with:
87
+ files: dist/*
88
+ generate_release_notes: true
89
+ prerelease: ${{ steps.prerelease.outputs.flag }}
90
+ draft: ${{ steps.prerelease.outputs.flag == 'false' }}
@@ -0,0 +1,25 @@
1
+ name: Ruff
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+ branches:
9
+ - main
10
+ workflow_call:
11
+
12
+ jobs:
13
+ lint:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - name: Checkout branch
17
+ uses: actions/checkout@v4
18
+
19
+ - name: Run Ruff check
20
+ uses: astral-sh/ruff-action@v3
21
+
22
+ - name: Run Ruff format
23
+ uses: astral-sh/ruff-action@v3
24
+ with:
25
+ args: "format --check --diff"
@@ -0,0 +1,39 @@
1
+ name: Pytest
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+ branches:
9
+ - main
10
+ workflow_call:
11
+ workflow_dispatch:
12
+
13
+ jobs:
14
+ test:
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ matrix:
18
+ python-version:
19
+ - "3.10"
20
+ - "3.14"
21
+ steps:
22
+ - name: Checkout branch
23
+ uses: actions/checkout@v4
24
+
25
+ - name: Enable uv caching
26
+ uses: astral-sh/setup-uv@v6
27
+ with:
28
+ enable-cache: true
29
+
30
+ - name: Install Python
31
+ uses: astral-sh/setup-uv@v6
32
+ with:
33
+ python-version: ${{ matrix.python-version }}
34
+
35
+ - name: Install dependencies
36
+ run: uv sync --group dev
37
+
38
+ - name: Run tests
39
+ run: uv run pytest
@@ -0,0 +1,225 @@
1
+ # Created by https://www.toptal.com/developers/gitignore/api/python,macos
2
+ # Edit at https://www.toptal.com/developers/gitignore?templates=python,macos
3
+
4
+ ### macOS ###
5
+ # General
6
+ .DS_Store
7
+ .AppleDouble
8
+ .LSOverride
9
+
10
+ # Icon must end with two \r
11
+ Icon
12
+
13
+
14
+ # Thumbnails
15
+ ._*
16
+
17
+ # Files that might appear in the root of a volume
18
+ .DocumentRevisions-V100
19
+ .fseventsd
20
+ .Spotlight-V100
21
+ .TemporaryItems
22
+ .Trashes
23
+ .VolumeIcon.icns
24
+ .com.apple.timemachine.donotpresent
25
+
26
+ # Directories potentially created on remote AFP share
27
+ .AppleDB
28
+ .AppleDesktop
29
+ Network Trash Folder
30
+ Temporary Items
31
+ .apdisk
32
+
33
+ ### macOS Patch ###
34
+ # iCloud generated files
35
+ *.icloud
36
+
37
+ ### Python ###
38
+ # Byte-compiled / optimized / DLL files
39
+ __pycache__/
40
+ *.py[cod]
41
+ *$py.class
42
+
43
+ # C extensions
44
+ *.so
45
+
46
+ # Distribution / packaging
47
+ .Python
48
+ build/
49
+ develop-eggs/
50
+ dist/
51
+ downloads/
52
+ eggs/
53
+ .eggs/
54
+ lib/
55
+ lib64/
56
+ parts/
57
+ sdist/
58
+ var/
59
+ wheels/
60
+ share/python-wheels/
61
+ *.egg-info/
62
+ .installed.cfg
63
+ *.egg
64
+ MANIFEST
65
+
66
+ # PyInstaller
67
+ # Usually these files are written by a python script from a template
68
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
69
+ *.manifest
70
+ *.spec
71
+
72
+ # Installer logs
73
+ pip-log.txt
74
+ pip-delete-this-directory.txt
75
+
76
+ # Unit test / coverage reports
77
+ htmlcov/
78
+ .tox/
79
+ .nox/
80
+ .coverage
81
+ .coverage.*
82
+ .cache
83
+ nosetests.xml
84
+ coverage.xml
85
+ *.cover
86
+ *.py,cover
87
+ .hypothesis/
88
+ .pytest_cache/
89
+ cover/
90
+
91
+ # Translations
92
+ *.mo
93
+ *.pot
94
+
95
+ # Django stuff:
96
+ *.log
97
+ local_settings.py
98
+ db.sqlite3
99
+ db.sqlite3-journal
100
+
101
+ # Flask stuff:
102
+ instance/
103
+ .webassets-cache
104
+
105
+ # Scrapy stuff:
106
+ .scrapy
107
+
108
+ # Sphinx documentation
109
+ docs/_build/
110
+
111
+ # PyBuilder
112
+ .pybuilder/
113
+ target/
114
+
115
+ # Jupyter Notebook
116
+ .ipynb_checkpoints
117
+
118
+ # IPython
119
+ profile_default/
120
+ ipython_config.py
121
+
122
+ # pyenv
123
+ # For a library or package, you might want to ignore these files since the code is
124
+ # intended to run in multiple environments; otherwise, check them in:
125
+ # .python-version
126
+
127
+ # pipenv
128
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
129
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
130
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
131
+ # install all needed dependencies.
132
+ #Pipfile.lock
133
+
134
+ # poetry
135
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
136
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
137
+ # commonly ignored for libraries.
138
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
139
+ #poetry.lock
140
+
141
+ # pdm
142
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
143
+ #pdm.lock
144
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
145
+ # in version control.
146
+ # https://pdm.fming.dev/#use-with-ide
147
+ .pdm.toml
148
+
149
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
150
+ __pypackages__/
151
+
152
+ # Celery stuff
153
+ celerybeat-schedule
154
+ celerybeat.pid
155
+
156
+ # SageMath parsed files
157
+ *.sage.py
158
+
159
+ # Environments
160
+ .env
161
+ .venv
162
+ env/
163
+ venv/
164
+ ENV/
165
+ env.bak/
166
+ venv.bak/
167
+
168
+ # Spyder project settings
169
+ .spyderproject
170
+ .spyproject
171
+
172
+ # Rope project settings
173
+ .ropeproject
174
+
175
+ # mkdocs documentation
176
+ /site
177
+
178
+ # mypy
179
+ .mypy_cache/
180
+ .dmypy.json
181
+ dmypy.json
182
+
183
+ # Pyre type checker
184
+ .pyre/
185
+
186
+ # pytype static type analyzer
187
+ .pytype/
188
+
189
+ # Cython debug symbols
190
+ cython_debug/
191
+
192
+ # PyCharm
193
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
194
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
195
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
196
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
197
+ #.idea/
198
+
199
+ ### Python Patch ###
200
+ # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
201
+ poetry.toml
202
+
203
+ # ruff
204
+ .ruff_cache/
205
+
206
+ # LSP config files
207
+ pyrightconfig.json
208
+
209
+ # AI automations
210
+ .claude/
211
+
212
+ # Version file (auto-generated by hatch-vcs)
213
+ src/wordlevel/_version.py
214
+
215
+ # Lexicon data
216
+ lexicons/*/data/
217
+
218
+ # Auto-generated documentation
219
+ /docs/documentation/api/
220
+ /zensical-build.toml
221
+
222
+ # Local drafting/scratch data
223
+ drafting/
224
+
225
+ # End of https://www.toptal.com/developers/gitignore/api/python,macos
@@ -0,0 +1,16 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.12.8
4
+ hooks:
5
+ - id: ruff-check
6
+ exclude: ^docs/snippets/
7
+ - id: ruff-format
8
+ exclude: ^docs/snippets/
9
+ - repo: local
10
+ hooks:
11
+ - id: pyright
12
+ name: pyright
13
+ entry: uv run pyright
14
+ language: system
15
+ types: [python]
16
+ pass_filenames: false
@@ -0,0 +1,7 @@
1
+ Copyright 2026 Ryan J. Gallagher
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.