throttle-controller 0.0.5__tar.gz → 0.0.8__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 (55) hide show
  1. throttle_controller-0.0.8/.coveragerc +4 -0
  2. throttle_controller-0.0.8/.editorconfig +10 -0
  3. throttle-controller-0.0.5/renovate.json → throttle_controller-0.0.8/.github/renovate.json5 +1 -1
  4. throttle_controller-0.0.8/.github/workflows/gitignore-in.yml +16 -0
  5. throttle_controller-0.0.8/.github/workflows/happy.yml +18 -0
  6. {throttle-controller-0.0.5 → throttle_controller-0.0.8}/.github/workflows/pypi-publish.yml +15 -15
  7. throttle_controller-0.0.8/.github/workflows/python-test.yml +63 -0
  8. throttle_controller-0.0.8/.github/workflows/spellcheck.yml +16 -0
  9. {throttle-controller-0.0.5 → throttle_controller-0.0.8}/.gitignore +17 -12
  10. throttle_controller-0.0.8/.gitignore.in +5 -0
  11. {throttle-controller-0.0.5 → throttle_controller-0.0.8}/LICENSE +1 -1
  12. throttle_controller-0.0.8/PKG-INFO +81 -0
  13. throttle_controller-0.0.8/README.md +50 -0
  14. throttle_controller-0.0.8/pyproject.toml +66 -0
  15. throttle_controller-0.0.8/setup.cfg +4 -0
  16. throttle_controller-0.0.8/template_analysis/__init__.py +18 -0
  17. throttle_controller-0.0.8/template_analysis/_version.py +16 -0
  18. throttle_controller-0.0.8/template_analysis/analyzer.py +162 -0
  19. throttle_controller-0.0.8/template_analysis/symbol.py +84 -0
  20. throttle_controller-0.0.8/template_analysis/template.py +56 -0
  21. throttle_controller-0.0.8/tests/test_analyzer.py +54 -0
  22. throttle_controller-0.0.8/tests/test_symbol.py +60 -0
  23. throttle_controller-0.0.8/tests/test_template.py +16 -0
  24. throttle_controller-0.0.8/throttle_controller.egg-info/PKG-INFO +81 -0
  25. throttle_controller-0.0.8/throttle_controller.egg-info/SOURCES.txt +27 -0
  26. throttle_controller-0.0.8/throttle_controller.egg-info/top_level.txt +1 -0
  27. throttle_controller-0.0.8/uv.lock +403 -0
  28. throttle-controller-0.0.5/.coveragerc +0 -7
  29. throttle-controller-0.0.5/.github/workflows/python-test.yml +0 -74
  30. throttle-controller-0.0.5/.gitignore.in +0 -10
  31. throttle-controller-0.0.5/Makefile +0 -48
  32. throttle-controller-0.0.5/PKG-INFO +0 -60
  33. throttle-controller-0.0.5/Pipfile +0 -26
  34. throttle-controller-0.0.5/Pipfile.lock +0 -305
  35. throttle-controller-0.0.5/README.md +0 -39
  36. throttle-controller-0.0.5/mypy.ini +0 -36
  37. throttle-controller-0.0.5/pyproject.toml +0 -5
  38. throttle-controller-0.0.5/setup.cfg +0 -46
  39. throttle-controller-0.0.5/setup.py +0 -4
  40. throttle-controller-0.0.5/tests/test_simple.py +0 -82
  41. throttle-controller-0.0.5/tests/test_utils_interval.py +0 -12
  42. throttle-controller-0.0.5/throttle_controller/__init__.py +0 -2
  43. throttle-controller-0.0.5/throttle_controller/_version.py +0 -4
  44. throttle-controller-0.0.5/throttle_controller/protocol.py +0 -31
  45. throttle-controller-0.0.5/throttle_controller/py.typed +0 -0
  46. throttle-controller-0.0.5/throttle_controller/simple.py +0 -52
  47. throttle-controller-0.0.5/throttle_controller/utils/__init__.py +0 -0
  48. throttle-controller-0.0.5/throttle_controller/utils/interval.py +0 -14
  49. throttle-controller-0.0.5/throttle_controller/version.py +0 -5
  50. throttle-controller-0.0.5/throttle_controller.egg-info/PKG-INFO +0 -60
  51. throttle-controller-0.0.5/throttle_controller.egg-info/SOURCES.txt +0 -31
  52. throttle-controller-0.0.5/throttle_controller.egg-info/requires.txt +0 -7
  53. throttle-controller-0.0.5/throttle_controller.egg-info/top_level.txt +0 -2
  54. {throttle-controller-0.0.5 → throttle_controller-0.0.8}/tests/__init__.py +0 -0
  55. {throttle-controller-0.0.5 → throttle_controller-0.0.8}/throttle_controller.egg-info/dependency_links.txt +0 -0
@@ -0,0 +1,4 @@
1
+ [report]
2
+ exclude_lines =
3
+ @overload
4
+ pragma: no cover
@@ -0,0 +1,10 @@
1
+ root = true
2
+
3
+ [*]
4
+ end_of_line = lf
5
+ insert_final_newline = true
6
+ trim_trailing_whitespace = true
7
+ charset = utf-8
8
+
9
+ [{Makefile,**.mk}]
10
+ indent_style = tab
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
3
  "extends": [
4
- "github>kitsuyui/renovate-config"
4
+ "local>kitsuyui/renovate-config"
5
5
  ]
6
6
  }
@@ -0,0 +1,16 @@
1
+ name: Update gitignore file
2
+
3
+ on:
4
+ schedule:
5
+ # daily at 00:00
6
+ - cron: '0 0 * * *'
7
+
8
+ permissions:
9
+ contents: write
10
+ pull-requests: write
11
+
12
+ jobs:
13
+ update-gitignore:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: gitignore-in/gh-action@v0.2.2
@@ -0,0 +1,18 @@
1
+ name: happy-commit
2
+ on:
3
+ - pull_request
4
+
5
+ permissions:
6
+ contents: read
7
+ pull-requests: write
8
+ issues: write
9
+
10
+ jobs:
11
+ happy:
12
+ runs-on: ubuntu-latest
13
+ name: happy
14
+ continue-on-error: true
15
+ steps:
16
+ - uses: kitsuyui/happy-commit@v0.9
17
+ with:
18
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -1,6 +1,8 @@
1
- name: Upload PyPI package release
1
+ name: Packaging
2
2
 
3
3
  on:
4
+ pull_request:
5
+ types: [opened, synchronize, reopened]
4
6
  release:
5
7
  # "released" events are emitted either when directly be released or be edited from pre-released.
6
8
  types: [prereleased, released]
@@ -9,36 +11,34 @@ jobs:
9
11
  deploy:
10
12
  runs-on: ubuntu-latest
11
13
  steps:
12
- - uses: actions/checkout@v3
13
-
14
- - name: Install pipenv
15
- run: pipx install pipenv
14
+ - uses: actions/checkout@v4
16
15
 
17
16
  - name: Set up Python
18
- uses: actions/setup-python@v4
17
+ id: setup-python
18
+ uses: actions/setup-python@v5
19
19
  with:
20
- python-version: "3.x"
21
- cache: "pipenv"
22
- architecture: "x64"
20
+ python-version: "3.13"
21
+
22
+ - name: Set up uv
23
+ uses: astral-sh/setup-uv@v5
23
24
 
24
25
  - name: Install dependencies
25
- run: pipenv sync --dev
26
+ run: uv sync
26
27
 
27
28
  - name: Build
28
- run: |
29
- pipenv run python -m build --sdist --wheel --outdir dist/ .
29
+ run: uv build
30
30
 
31
31
  - name: Publish distribution to Test PyPI
32
- uses: pypa/gh-action-pypi-publish@master
32
+ uses: pypa/gh-action-pypi-publish@release/v1
33
33
  if: github.event_name == 'release' && github.event.release.prerelease
34
34
  with:
35
35
  user: __token__
36
36
  password: ${{ secrets.TEST_PYPI_API_TOKEN }}
37
- repository_url: https://test.pypi.org/legacy/
37
+ repository-url: https://test.pypi.org/legacy/
38
38
 
39
39
  - name: Publish distribution to PyPI
40
40
  if: github.event_name == 'release' && !github.event.release.prerelease
41
- uses: pypa/gh-action-pypi-publish@master
41
+ uses: pypa/gh-action-pypi-publish@release/v1
42
42
  with:
43
43
  user: __token__
44
44
  password: ${{ secrets.PYPI_API_TOKEN }}
@@ -0,0 +1,63 @@
1
+ name: Test
2
+ on:
3
+ # pull-request events are not triggered when a PR is merged
4
+ # push events are not triggered when a PR created from a fork repository
5
+ # So we need both to run tests on every PR and after merging
6
+ pull_request:
7
+ push:
8
+ branches:
9
+ - main
10
+ jobs:
11
+ test:
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ python: ["3.9", "3.10", "3.11", "3.12", "3.13"]
17
+
18
+ steps:
19
+ - name: Checkout
20
+ uses: actions/checkout@v4
21
+
22
+ - name: Set up Python
23
+ id: setup-python
24
+ uses: actions/setup-python@v5
25
+ with:
26
+ python-version: "${{ matrix.python }}"
27
+
28
+ - name: Set up uv
29
+ uses: astral-sh/setup-uv@v5
30
+
31
+ - name: Install dependencies
32
+ run: |
33
+ uv python pin ${{ matrix.python }}
34
+ uv sync
35
+
36
+ - name: Lint
37
+ run: uv run poe check
38
+
39
+ - name: Check Python version
40
+ run: |
41
+ actual_version=$(uv run python --version)
42
+ expected_version="Python ${{ matrix.python }}"
43
+ if [ "$actual_version" == *"$expected_version"* ]; then
44
+ echo "Expected $expected_version, but got $actual_version"
45
+ exit 1
46
+ fi
47
+
48
+ - name: Test
49
+ run: uv run poe test
50
+
51
+ - name: Coverage
52
+ run: uv run poe coverage-xml
53
+
54
+ - name: Upload coverage report to Codecov
55
+ if: matrix.python == '3.13'
56
+ uses: codecov/codecov-action@v5
57
+ with:
58
+ token: ${{ secrets.CODECOV_TOKEN }}
59
+ files: ./coverage.xml
60
+ flags: unittests
61
+ name: codecov-umbrella
62
+ fail_ci_if_error: true
63
+ verbose: true
@@ -0,0 +1,16 @@
1
+ name: Spellcheck
2
+ on:
3
+ - pull_request
4
+ permissions:
5
+ contents: read
6
+ jobs:
7
+ check:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - name: Checkout
11
+ uses: actions/checkout@v4
12
+
13
+ - name: Check spelling
14
+ uses: crate-ci/typos@master
15
+ # typos is a Source code spell checker
16
+ # https://github.com/crate-ci/typos
@@ -1,5 +1,11 @@
1
+ # DO NOT EDIT THIS FILE
2
+ # Generated by gitignore.in
3
+ # Edit .gitignore.in instead of this file
4
+ # Run `gitignore.in` to build .gitignore
5
+ # -----------------------------------------------------------------------------
6
+ # gibo dump Linux
1
7
  ### Generated by gibo (https://github.com/simonwhitaker/gibo)
2
- ### https://raw.github.com/github/gitignore/e5323759e387ba347a9d50f8b0ddd16502eb71d4/Global/Linux.gitignore
8
+ ### https://raw.github.com/github/gitignore/4488915eec0b3a45b5c63ead28f286819c0917de/Global/Linux.gitignore
3
9
 
4
10
  *~
5
11
 
@@ -14,10 +20,10 @@
14
20
 
15
21
  # .nfs files are created when an open file is removed but is still being accessed
16
22
  .nfs*
17
-
18
-
23
+ # -----------------------------------------------------------------------------
24
+ # gibo dump Windows
19
25
  ### Generated by gibo (https://github.com/simonwhitaker/gibo)
20
- ### https://raw.github.com/github/gitignore/e5323759e387ba347a9d50f8b0ddd16502eb71d4/Global/Windows.gitignore
26
+ ### https://raw.github.com/github/gitignore/4488915eec0b3a45b5c63ead28f286819c0917de/Global/Windows.gitignore
21
27
 
22
28
  # Windows thumbnail cache files
23
29
  Thumbs.db
@@ -43,10 +49,10 @@ $RECYCLE.BIN/
43
49
 
44
50
  # Windows shortcuts
45
51
  *.lnk
46
-
47
-
52
+ # -----------------------------------------------------------------------------
53
+ # gibo dump macOS
48
54
  ### Generated by gibo (https://github.com/simonwhitaker/gibo)
49
- ### https://raw.github.com/github/gitignore/e5323759e387ba347a9d50f8b0ddd16502eb71d4/Global/macOS.gitignore
55
+ ### https://raw.github.com/github/gitignore/4488915eec0b3a45b5c63ead28f286819c0917de/Global/macOS.gitignore
50
56
 
51
57
  # General
52
58
  .DS_Store
@@ -74,10 +80,10 @@ Icon
74
80
  Network Trash Folder
75
81
  Temporary Items
76
82
  .apdisk
77
-
78
-
83
+ # -----------------------------------------------------------------------------
84
+ # gibo dump Python
79
85
  ### Generated by gibo (https://github.com/simonwhitaker/gibo)
80
- ### https://raw.github.com/github/gitignore/e5323759e387ba347a9d50f8b0ddd16502eb71d4/Python.gitignore
86
+ ### https://raw.github.com/github/gitignore/4488915eec0b3a45b5c63ead28f286819c0917de/Python.gitignore
81
87
 
82
88
  # Byte-compiled / optimized / DLL files
83
89
  __pycache__/
@@ -239,6 +245,5 @@ cython_debug/
239
245
  # and can be added to the global gitignore or merged into this file. For a more nuclear
240
246
  # option (not recommended) you can uncomment the following to ignore the entire idea folder.
241
247
  #.idea/
242
-
243
-
248
+ # -----------------------------------------------------------------------------
244
249
  _version.py
@@ -0,0 +1,5 @@
1
+ gibo dump Linux
2
+ gibo dump Windows
3
+ gibo dump macOS
4
+ gibo dump Python
5
+ echo _version.py
@@ -1,4 +1,4 @@
1
- Copyright 2022 Kitsu Yui
1
+ Copyright 2022-2025 Kitsu Yui
2
2
 
3
3
  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4
4
 
@@ -0,0 +1,81 @@
1
+ Metadata-Version: 2.2
2
+ Name: throttle-controller
3
+ Version: 0.0.8
4
+ Summary: Create template from strings (reverse template engine)
5
+ Author-email: Yui KITSU <kitsuyui+github@kitsuyui.com>
6
+ License: Copyright 2022-2025 Kitsu Yui
7
+
8
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
9
+
10
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
13
+
14
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
17
+
18
+ Project-URL: Homepage, https://github.com/kitsuyui/python-template-analysis
19
+ Classifier: Development Status :: 3 - Alpha
20
+ Classifier: License :: OSI Approved :: BSD License
21
+ Classifier: Programming Language :: Python :: 3.9
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Programming Language :: Python :: 3.13
26
+ Classifier: Intended Audience :: Developers
27
+ Classifier: Topic :: Software Development :: Libraries
28
+ Requires-Python: >=3.9
29
+ Description-Content-Type: text/markdown
30
+ License-File: LICENSE
31
+
32
+ # template-analysis
33
+
34
+ [![Python](https://img.shields.io/pypi/pyversions/template-analysis.svg)](https://badge.fury.io/py/template-analysis)
35
+ [![PyPI version](https://img.shields.io/pypi/v/template-analysis.svg)](https://pypi.python.org/pypi/template-analysis/)
36
+ [![codecov](https://codecov.io/gh/kitsuyui/python-template-analysis/branch/main/graph/badge.svg?token=AMEP8YUYXA)](https://codecov.io/gh/kitsuyui/python-template-analysis)
37
+ [![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause)
38
+
39
+ Generate template and extract variables from texts.
40
+ In other words, template-analysis makes reverse template (untemplate).
41
+
42
+ ## Usage
43
+
44
+ Currently, this supports to analyze only two texts.
45
+
46
+ ```python
47
+ from template_analysis import analyze
48
+ result = analyze([
49
+ "A dog is a good pet",
50
+ "A cat is a good pet",
51
+ "A cat is a pretty pet",
52
+ "A bird is a great pet",
53
+ ])
54
+ result.to_format_string() # => "A {} is a {} pet"
55
+ result.args[0] # => ["dog", "good"]
56
+ result.args[1] # => ["cat", "good"]
57
+ result.args[2] # => ["cat", "pretty"]
58
+ result.args[3] # => ["bird", "great"]
59
+ ```
60
+
61
+ ## Concepts / Future plans
62
+
63
+ ### Development plans
64
+
65
+ - [x] 1. Untemplate two texts.
66
+ - [x] 2. Untemplate multiple / complex texts.
67
+ - [ ] 3. Untemplate nested / tree-structured texts.
68
+ - [ ] 4. Support several features for scraping.
69
+ - [ ] 5. Implement a more efficient algorithm.
70
+
71
+ ### Image boards
72
+
73
+ ![1-templating](https://user-images.githubusercontent.com/2596972/73120667-7bafbf80-3fb4-11ea-823f-263c0010e0e9.png)
74
+ ![2-untemplating](https://user-images.githubusercontent.com/2596972/73120668-7bafbf80-3fb4-11ea-9426-5471fcf2e601.png)
75
+ ![3-template-deriving](https://user-images.githubusercontent.com/2596972/73120669-7bafbf80-3fb4-11ea-8236-1ab68f75ce60.png)
76
+ ![4-template-deriving-2](https://user-images.githubusercontent.com/2596972/73120670-7c485600-3fb4-11ea-9eba-01aaafd08e4e.png)
77
+ ![4-automated-scraping](https://user-images.githubusercontent.com/2596972/73120671-7c485600-3fb4-11ea-8ed6-56b93ee99b3a.png)
78
+
79
+ ## License
80
+
81
+ The 3-Clause BSD License. See also LICENSE file.
@@ -0,0 +1,50 @@
1
+ # template-analysis
2
+
3
+ [![Python](https://img.shields.io/pypi/pyversions/template-analysis.svg)](https://badge.fury.io/py/template-analysis)
4
+ [![PyPI version](https://img.shields.io/pypi/v/template-analysis.svg)](https://pypi.python.org/pypi/template-analysis/)
5
+ [![codecov](https://codecov.io/gh/kitsuyui/python-template-analysis/branch/main/graph/badge.svg?token=AMEP8YUYXA)](https://codecov.io/gh/kitsuyui/python-template-analysis)
6
+ [![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause)
7
+
8
+ Generate template and extract variables from texts.
9
+ In other words, template-analysis makes reverse template (untemplate).
10
+
11
+ ## Usage
12
+
13
+ Currently, this supports to analyze only two texts.
14
+
15
+ ```python
16
+ from template_analysis import analyze
17
+ result = analyze([
18
+ "A dog is a good pet",
19
+ "A cat is a good pet",
20
+ "A cat is a pretty pet",
21
+ "A bird is a great pet",
22
+ ])
23
+ result.to_format_string() # => "A {} is a {} pet"
24
+ result.args[0] # => ["dog", "good"]
25
+ result.args[1] # => ["cat", "good"]
26
+ result.args[2] # => ["cat", "pretty"]
27
+ result.args[3] # => ["bird", "great"]
28
+ ```
29
+
30
+ ## Concepts / Future plans
31
+
32
+ ### Development plans
33
+
34
+ - [x] 1. Untemplate two texts.
35
+ - [x] 2. Untemplate multiple / complex texts.
36
+ - [ ] 3. Untemplate nested / tree-structured texts.
37
+ - [ ] 4. Support several features for scraping.
38
+ - [ ] 5. Implement a more efficient algorithm.
39
+
40
+ ### Image boards
41
+
42
+ ![1-templating](https://user-images.githubusercontent.com/2596972/73120667-7bafbf80-3fb4-11ea-823f-263c0010e0e9.png)
43
+ ![2-untemplating](https://user-images.githubusercontent.com/2596972/73120668-7bafbf80-3fb4-11ea-9426-5471fcf2e601.png)
44
+ ![3-template-deriving](https://user-images.githubusercontent.com/2596972/73120669-7bafbf80-3fb4-11ea-8236-1ab68f75ce60.png)
45
+ ![4-template-deriving-2](https://user-images.githubusercontent.com/2596972/73120670-7c485600-3fb4-11ea-9eba-01aaafd08e4e.png)
46
+ ![4-automated-scraping](https://user-images.githubusercontent.com/2596972/73120671-7c485600-3fb4-11ea-8ed6-56b93ee99b3a.png)
47
+
48
+ ## License
49
+
50
+ The 3-Clause BSD License. See also LICENSE file.
@@ -0,0 +1,66 @@
1
+ [project]
2
+ name = "throttle-controller"
3
+ dynamic = ["version"]
4
+ requires-python = ">=3.9"
5
+ dependencies = []
6
+ description = "Create template from strings (reverse template engine)"
7
+ readme = {file = "README.md", content-type = "text/markdown"}
8
+ license = { file = "LICENSE" }
9
+ authors = [
10
+ {name = "Yui KITSU", email = "kitsuyui+github@kitsuyui.com"}
11
+ ]
12
+ classifiers = [
13
+ "Development Status :: 3 - Alpha",
14
+ "License :: OSI Approved :: BSD License",
15
+ "Programming Language :: Python :: 3.9",
16
+ "Programming Language :: Python :: 3.10",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.13",
20
+ "Intended Audience :: Developers",
21
+ "Topic :: Software Development :: Libraries",
22
+ ]
23
+
24
+ [project.urls]
25
+ Homepage = "https://github.com/kitsuyui/python-template-analysis"
26
+
27
+ [tool.setuptools]
28
+ package-data = { "template_analysis" = ["py.typed"], "*" = ["README.md, LICENSE"] }
29
+ package-dir = { "template_analysis" = "template_analysis" }
30
+
31
+ [tool.uv]
32
+ dev-dependencies = [
33
+ "pytest",
34
+ "pytest-cov",
35
+ "poethepoet",
36
+ "setuptools-scm",
37
+ "ruff",
38
+ "mypy",
39
+ ]
40
+
41
+ [build-system]
42
+ requires = ["setuptools", "setuptools_scm"]
43
+ build-backend = "setuptools.build_meta"
44
+
45
+ [tool.setuptools_scm]
46
+ write_to = "template_analysis/_version.py"
47
+ version_scheme = "only-version"
48
+ local_scheme = "no-local-version"
49
+
50
+ [tool.poe.tasks]
51
+ test = "pytest"
52
+ coverage-xml = "pytest --cov=template_analysis --doctest-modules --cov-report=xml"
53
+ format = "ruff format template_analysis"
54
+ check = [
55
+ { cmd = "ruff check template_analysis" },
56
+ { cmd = "mypy template_analysis" },
57
+ ]
58
+ build = [{ cmd = "python -m build" }]
59
+
60
+ [tool.mypy]
61
+ # https://mypy.readthedocs.io/en/latest/config_file.html
62
+ strict = true
63
+ ignore_missing_imports = false
64
+
65
+ [tool.ruff]
66
+ line-length = 79
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,18 @@
1
+ """Template analysis.
2
+
3
+ This library does the opposite of template engines.
4
+ Template engines take a template and data, and return a formatted string.
5
+ This library takes a formatted string and returns a template and data."""
6
+
7
+ from .analyzer import Analyzer, AnalyzerResult, analyze # noqa: F401
8
+
9
+ # https://packaging-guide.openastronomy.org/en/latest/advanced/versioning.html
10
+ from ._version import __version__
11
+
12
+
13
+ __all__ = [
14
+ "Analyzer",
15
+ "AnalyzerResult",
16
+ "analyze",
17
+ "__version__",
18
+ ]
@@ -0,0 +1,16 @@
1
+ # file generated by setuptools_scm
2
+ # don't change, don't track in version control
3
+ TYPE_CHECKING = False
4
+ if TYPE_CHECKING:
5
+ from typing import Tuple, Union
6
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
7
+ else:
8
+ VERSION_TUPLE = object
9
+
10
+ version: str
11
+ __version__: str
12
+ __version_tuple__: VERSION_TUPLE
13
+ version_tuple: VERSION_TUPLE
14
+
15
+ __version__ = version = '0.0.8'
16
+ __version_tuple__ = version_tuple = (0, 0, 8)
@@ -0,0 +1,162 @@
1
+ from __future__ import annotations
2
+
3
+ import difflib
4
+ from dataclasses import dataclass
5
+ from typing import Iterator
6
+
7
+ from .symbol import (
8
+ Chunks,
9
+ Symbol,
10
+ SymbolChunk,
11
+ SymbolChunks,
12
+ SymbolString,
13
+ SymbolTable,
14
+ SymbolTemplate,
15
+ to_symbol_chunks,
16
+ )
17
+ from .template import Template
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class AnalyzerResult:
22
+ text: SymbolString
23
+ tables: list[SymbolTable]
24
+
25
+ @property
26
+ def template(self) -> Template:
27
+ return Template.from_symbol_template(
28
+ SymbolTemplate(self.text[:], SymbolTable.create())
29
+ )
30
+
31
+ @property
32
+ def args(self) -> list[Chunks]:
33
+ return [
34
+ SymbolTemplate(self.text[:], table).args() for table in self.tables
35
+ ]
36
+
37
+ def to_format_string(self) -> str:
38
+ return self.template.to_format_string()
39
+
40
+ @classmethod
41
+ def from_text(cls, text: str) -> AnalyzerResult:
42
+ return AnalyzerResult(
43
+ text=list(text),
44
+ tables=[SymbolTable.create()],
45
+ )
46
+
47
+
48
+ @dataclass
49
+ class Analyzer:
50
+ text: SymbolString
51
+ pos: int
52
+ parsed: SymbolChunks
53
+ table: SymbolTable
54
+
55
+ @classmethod
56
+ def create(cls, text: str | SymbolString) -> Analyzer:
57
+ return cls(
58
+ list(text),
59
+ pos=0,
60
+ parsed=[],
61
+ table=SymbolTable.create(),
62
+ )
63
+
64
+ def proceed(self, size: int) -> None:
65
+ self.pos += size
66
+
67
+ @property
68
+ def parsed_text(self) -> SymbolString:
69
+ chunks: SymbolString = []
70
+ for chunk in self.parsed:
71
+ if isinstance(chunk, Symbol):
72
+ chunks.append(chunk)
73
+ else:
74
+ for char in chunk:
75
+ chunks.append(char)
76
+ return chunks
77
+
78
+ def __read_n_tokens(self, size: int) -> Iterator[SymbolChunk]:
79
+ start = self.pos
80
+ stop = self.pos + size
81
+ token: SymbolString = self.text[start:stop]
82
+ for s in to_symbol_chunks(token):
83
+ if isinstance(s, Symbol):
84
+ self.proceed(1)
85
+ else:
86
+ self.proceed(len(s))
87
+ yield s
88
+
89
+ def append_match(self, size: int) -> None:
90
+ for s in self.__read_n_tokens(size):
91
+ self.parsed.append(s)
92
+
93
+ def append_unique(self, size: int, symbol: Symbol) -> None:
94
+ for s in self.__read_n_tokens(size):
95
+ self.parsed.append(symbol)
96
+ self.table.add(symbol, s)
97
+
98
+ def advance(self, pos: int, size: int, symbol: Symbol) -> None:
99
+ while (unmatch_length := pos - self.pos) > 0:
100
+ self.append_unique(unmatch_length, symbol)
101
+ self.append_match(size)
102
+
103
+ @classmethod
104
+ def analyze_two_symbol_strings(
105
+ cls, seq1: SymbolString, seq2: SymbolString
106
+ ) -> tuple[Analyzer, Analyzer]:
107
+ matcher = difflib.SequenceMatcher(None, seq1, seq2)
108
+ blocks = matcher.get_matching_blocks()
109
+ analyzer_a = cls.create(seq1)
110
+ analyzer_b = cls.create(seq2)
111
+
112
+ for block in blocks:
113
+ symbol = Symbol.create()
114
+ analyzer_a.advance(block.a, block.size, symbol)
115
+ analyzer_b.advance(block.b, block.size, symbol)
116
+
117
+ return analyzer_a, analyzer_b
118
+
119
+ @classmethod
120
+ def analyze(cls, texts: list[str]) -> AnalyzerResult:
121
+ return cls.analyze_texts(texts)
122
+
123
+ @classmethod
124
+ def analyze_two_result(
125
+ cls, result1: AnalyzerResult, result2: AnalyzerResult
126
+ ) -> AnalyzerResult:
127
+ analyzer_a, analyzer_b = cls.analyze_two_symbol_strings(
128
+ result1.text, result2.text
129
+ )
130
+ assert analyzer_a.parsed_text == analyzer_b.parsed_text
131
+ return AnalyzerResult(
132
+ analyzer_a.parsed_text,
133
+ [
134
+ *[
135
+ analyzer_a.table.combined(table)
136
+ for table in result1.tables
137
+ ],
138
+ *[
139
+ analyzer_b.table.combined(table)
140
+ for table in result2.tables
141
+ ],
142
+ ],
143
+ )
144
+
145
+ @classmethod
146
+ def analyze_texts(cls, texts: list[str]) -> AnalyzerResult:
147
+ texts = texts[:]
148
+
149
+ if not texts:
150
+ raise ValueError("texts are empty.")
151
+
152
+ text = texts.pop(0)
153
+ acc = AnalyzerResult.from_text(text)
154
+ while texts:
155
+ text = texts.pop(0)
156
+ curr = AnalyzerResult.from_text(text)
157
+ acc = cls.analyze_two_result(acc, curr)
158
+
159
+ return acc
160
+
161
+
162
+ analyze = Analyzer.analyze