latexwalker 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.
- latexwalker-0.1.0/.github/workflows/ci.yml +34 -0
- latexwalker-0.1.0/.github/workflows/docs.yml +52 -0
- latexwalker-0.1.0/.github/workflows/publish.yml +28 -0
- latexwalker-0.1.0/.gitignore +21 -0
- latexwalker-0.1.0/.opencode/opencode.jsonc +9 -0
- latexwalker-0.1.0/.opencode/skills/context7-usage/SKILL.md +129 -0
- latexwalker-0.1.0/.pre-commit-config.yaml +38 -0
- latexwalker-0.1.0/.python-version +1 -0
- latexwalker-0.1.0/AGENTS.md +165 -0
- latexwalker-0.1.0/ARCHITECTURE.md +108 -0
- latexwalker-0.1.0/CONTRIBUTING.md +124 -0
- latexwalker-0.1.0/LICENSE +7 -0
- latexwalker-0.1.0/PKG-INFO +66 -0
- latexwalker-0.1.0/README.md +54 -0
- latexwalker-0.1.0/docs/api.rst +103 -0
- latexwalker-0.1.0/docs/conf.py +45 -0
- latexwalker-0.1.0/docs/index.rst +56 -0
- latexwalker-0.1.0/justfile +49 -0
- latexwalker-0.1.0/pyproject.toml +67 -0
- latexwalker-0.1.0/src/latexwalker/__init__.py +72 -0
- latexwalker-0.1.0/src/latexwalker/_version.py +24 -0
- latexwalker-0.1.0/src/latexwalker/errors.py +24 -0
- latexwalker-0.1.0/src/latexwalker/lexer.py +129 -0
- latexwalker-0.1.0/src/latexwalker/nodes.py +123 -0
- latexwalker-0.1.0/src/latexwalker/normalize.py +46 -0
- latexwalker-0.1.0/src/latexwalker/parser.py +288 -0
- latexwalker-0.1.0/src/latexwalker/resources/rules.yaml +49 -0
- latexwalker-0.1.0/src/latexwalker/rewrite.py +196 -0
- latexwalker-0.1.0/src/latexwalker/rules.py +54 -0
- latexwalker-0.1.0/src/latexwalker/source.py +138 -0
- latexwalker-0.1.0/src/latexwalker/tokens.py +41 -0
- latexwalker-0.1.0/src/latexwalker/traverse.py +38 -0
- latexwalker-0.1.0/src/latexwalker/zones.py +78 -0
- latexwalker-0.1.0/tests/__init__.py +0 -0
- latexwalker-0.1.0/tests/test_api.py +86 -0
- latexwalker-0.1.0/tests/test_lexer.py +163 -0
- latexwalker-0.1.0/tests/test_nodes.py +110 -0
- latexwalker-0.1.0/tests/test_normalize.py +102 -0
- latexwalker-0.1.0/tests/test_parser.py +186 -0
- latexwalker-0.1.0/tests/test_rewrite.py +153 -0
- latexwalker-0.1.0/tests/test_rules.py +79 -0
- latexwalker-0.1.0/tests/test_source.py +161 -0
- latexwalker-0.1.0/tests/test_zones.py +135 -0
- latexwalker-0.1.0/uv.lock +1099 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
quality:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- uses: astral-sh/setup-uv@v6
|
|
16
|
+
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.12"
|
|
20
|
+
|
|
21
|
+
- name: Install deps
|
|
22
|
+
run: uv sync --dev --locked
|
|
23
|
+
|
|
24
|
+
- name: Ruff lint
|
|
25
|
+
run: uv run ruff check . --output-format=github
|
|
26
|
+
|
|
27
|
+
- name: Ruff format
|
|
28
|
+
run: uv run ruff format . --check
|
|
29
|
+
|
|
30
|
+
- name: basedpyright
|
|
31
|
+
run: uv run basedpyright
|
|
32
|
+
|
|
33
|
+
- name: Pytest
|
|
34
|
+
run: uv run pytest -v
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: Deploy documentation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
pages: write
|
|
12
|
+
id-token: write
|
|
13
|
+
|
|
14
|
+
concurrency:
|
|
15
|
+
group: "pages"
|
|
16
|
+
cancel-in-progress: false
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
build:
|
|
20
|
+
name: Build docs
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
- uses: astral-sh/setup-uv@v6
|
|
26
|
+
- uses: actions/setup-python@v5
|
|
27
|
+
with:
|
|
28
|
+
python-version: "3.12"
|
|
29
|
+
|
|
30
|
+
- name: Install deps
|
|
31
|
+
run: uv sync --no-dev --group docs --locked
|
|
32
|
+
|
|
33
|
+
- name: Build site
|
|
34
|
+
run: uv run sphinx-build -W --keep-going -b html docs docs/_build/html
|
|
35
|
+
|
|
36
|
+
- uses: actions/configure-pages@v5
|
|
37
|
+
- uses: actions/upload-pages-artifact@v3
|
|
38
|
+
with:
|
|
39
|
+
path: docs/_build/html
|
|
40
|
+
|
|
41
|
+
deploy:
|
|
42
|
+
name: Deploy docs
|
|
43
|
+
needs: build
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
environment:
|
|
46
|
+
name: github-pages
|
|
47
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
48
|
+
|
|
49
|
+
steps:
|
|
50
|
+
- name: Deploy to GitHub Pages
|
|
51
|
+
id: deployment
|
|
52
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
publish:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
permissions:
|
|
13
|
+
id-token: write
|
|
14
|
+
contents: read
|
|
15
|
+
environment: pypi
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: astral-sh/setup-uv@v6
|
|
20
|
+
- uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.12"
|
|
23
|
+
|
|
24
|
+
- name: Build package
|
|
25
|
+
run: uv build
|
|
26
|
+
|
|
27
|
+
- name: Publish package
|
|
28
|
+
run: uv publish
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
src/*/_version.py
|
|
9
|
+
|
|
10
|
+
# Virtual environments
|
|
11
|
+
.venv
|
|
12
|
+
|
|
13
|
+
# Cache
|
|
14
|
+
.pytest_cache/
|
|
15
|
+
.ruff_cache/
|
|
16
|
+
|
|
17
|
+
# Docs
|
|
18
|
+
docs/_build/
|
|
19
|
+
|
|
20
|
+
# Local
|
|
21
|
+
.vscode/
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: context7-usage
|
|
3
|
+
description: Use Context7 to retrieve version-specific official documentation and examples for external libraries before implementing unfamiliar, version-sensitive, or API-dependent code.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Context7 usage
|
|
7
|
+
|
|
8
|
+
Use Context7 to verify third-party library APIs, configuration, migration paths,
|
|
9
|
+
and version-specific behavior. Prefer official documentation and primary project
|
|
10
|
+
sources over memory, search snippets, or guessed APIs.
|
|
11
|
+
|
|
12
|
+
## When to use
|
|
13
|
+
|
|
14
|
+
Use Context7 when a task depends on:
|
|
15
|
+
|
|
16
|
+
- An unfamiliar library, framework, tool, or package API.
|
|
17
|
+
- Version-sensitive behavior, deprecations, or migration guidance.
|
|
18
|
+
- Exact constructor arguments, configuration keys, return types, or exception
|
|
19
|
+
behavior.
|
|
20
|
+
- Integration patterns for external dependencies such as msgspec, pytest, or
|
|
21
|
+
colorlog.
|
|
22
|
+
- A dependency addition or upgrade being considered.
|
|
23
|
+
- An implementation detail that cannot be established from this repository's code,
|
|
24
|
+
tests, type information, or lockfile.
|
|
25
|
+
|
|
26
|
+
Do not use Context7 when:
|
|
27
|
+
|
|
28
|
+
- The answer is already established by local code, tests, type stubs, or project
|
|
29
|
+
documentation.
|
|
30
|
+
- The task is internal refactoring with no external API dependency.
|
|
31
|
+
- The task is about repository conventions; follow `AGENTS.md`,
|
|
32
|
+
`ARCHITECTURE.md`, and `CONTRIBUTING.md` instead.
|
|
33
|
+
- You need current public information unrelated to a software-library API.
|
|
34
|
+
|
|
35
|
+
## Workflow
|
|
36
|
+
|
|
37
|
+
1. Read local usage first.
|
|
38
|
+
- Inspect imports, wrapper abstractions, configuration, and tests.
|
|
39
|
+
- Identify the installed package name and locked version before researching.
|
|
40
|
+
- Reuse an established project pattern when one exists.
|
|
41
|
+
|
|
42
|
+
2. Resolve the library.
|
|
43
|
+
- Search Context7 using the package or project name.
|
|
44
|
+
- Select the official or canonical documentation source.
|
|
45
|
+
- Prefer a source matching the installed major and minor version.
|
|
46
|
+
|
|
47
|
+
3. Query narrowly.
|
|
48
|
+
- Ask about one API surface at a time.
|
|
49
|
+
- Include the exact symbol, feature, or behavior being implemented.
|
|
50
|
+
- Request examples only when they clarify the required integration.
|
|
51
|
+
|
|
52
|
+
4. Apply the result conservatively.
|
|
53
|
+
- Adapt documented patterns to local abstractions and architecture.
|
|
54
|
+
- Do not copy examples blindly; preserve local types, error handling, and test
|
|
55
|
+
conventions.
|
|
56
|
+
- Do not introduce a dependency or upgrade solely because an example uses it.
|
|
57
|
+
|
|
58
|
+
5. Verify locally.
|
|
59
|
+
- Confirm the result with type checking and focused tests.
|
|
60
|
+
- Run the required project checks from `AGENTS.md` before completion.
|
|
61
|
+
|
|
62
|
+
## Query patterns
|
|
63
|
+
|
|
64
|
+
Use concise, concrete queries.
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
msgspec Struct tagged union decode validation
|
|
68
|
+
pytest anyio async fixture scope
|
|
69
|
+
colorlog ColoredFormatter format reset log_color
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
For version-sensitive behavior, include the known version:
|
|
73
|
+
|
|
74
|
+
```text
|
|
75
|
+
msgspec 0.x Struct omit_defaults JSON schema
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Avoid broad queries such as:
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
how does msgspec work
|
|
82
|
+
best Python serialization library
|
|
83
|
+
show me all msgspec features
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Source selection
|
|
87
|
+
|
|
88
|
+
- Prefer official project documentation, official API references, and maintained
|
|
89
|
+
upstream repositories.
|
|
90
|
+
- Prefer the package's canonical source over third-party tutorials or blog posts.
|
|
91
|
+
- If Context7 returns multiple candidate libraries, verify the package identity
|
|
92
|
+
against the repository dependency manifest and imports before using one.
|
|
93
|
+
- Treat documentation for a different major version as potentially incompatible.
|
|
94
|
+
- If the installed version cannot be identified, state that uncertainty and avoid
|
|
95
|
+
relying on version-specific claims.
|
|
96
|
+
|
|
97
|
+
## Dependency decisions
|
|
98
|
+
|
|
99
|
+
Context7 research does not authorize a dependency change.
|
|
100
|
+
|
|
101
|
+
Before proposing a new, removed, or upgraded dependency:
|
|
102
|
+
|
|
103
|
+
- Check whether the standard library or an existing dependency solves the problem.
|
|
104
|
+
- Identify the exact package, version policy, license, type support, maintenance
|
|
105
|
+
status, and transitive-dependency impact.
|
|
106
|
+
- Record alternatives considered in the plan.
|
|
107
|
+
- Obtain explicit approval before running `uv add`, removing a package, or changing
|
|
108
|
+
a version constraint.
|
|
109
|
+
|
|
110
|
+
## Reporting
|
|
111
|
+
|
|
112
|
+
When Context7 materially informed an implementation plan or code change, report:
|
|
113
|
+
|
|
114
|
+
- The library and version investigated.
|
|
115
|
+
- The API or behavior verified.
|
|
116
|
+
- The local files or tests that use the result.
|
|
117
|
+
- Any version uncertainty, caveat, or deviation from the documented example.
|
|
118
|
+
|
|
119
|
+
Keep the report brief. Do not paste long documentation excerpts when a concise
|
|
120
|
+
statement and local verification result are sufficient.
|
|
121
|
+
|
|
122
|
+
## Boundaries
|
|
123
|
+
|
|
124
|
+
- Context7 is evidence for external APIs, not a replacement for repository context.
|
|
125
|
+
- `AGENTS.md` controls code style, validation, dependencies, logging, and tests.
|
|
126
|
+
- `ARCHITECTURE.md` controls system boundaries, contracts, and invariants.
|
|
127
|
+
- `CONTRIBUTING.md` controls branches, commits, rebases, and review workflow.
|
|
128
|
+
- When Context7 conflicts with local project conventions, preserve local conventions
|
|
129
|
+
unless the task explicitly changes them.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v6.0.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: trailing-whitespace
|
|
6
|
+
- id: end-of-file-fixer
|
|
7
|
+
- id: check-added-large-files
|
|
8
|
+
|
|
9
|
+
- repo: local
|
|
10
|
+
hooks:
|
|
11
|
+
- id: lint
|
|
12
|
+
name: lint
|
|
13
|
+
entry: just lint
|
|
14
|
+
language: system
|
|
15
|
+
types_or: [python, pyi]
|
|
16
|
+
pass_filenames: true
|
|
17
|
+
|
|
18
|
+
- id: format
|
|
19
|
+
name: format
|
|
20
|
+
entry: just format
|
|
21
|
+
language: system
|
|
22
|
+
types_or: [python, pyi]
|
|
23
|
+
pass_filenames: true
|
|
24
|
+
|
|
25
|
+
- id: typecheck
|
|
26
|
+
name: typecheck
|
|
27
|
+
entry: just typecheck
|
|
28
|
+
language: system
|
|
29
|
+
types_or: [python, pyi]
|
|
30
|
+
pass_filenames: false
|
|
31
|
+
stages: [pre-push]
|
|
32
|
+
|
|
33
|
+
- id: test
|
|
34
|
+
name: test
|
|
35
|
+
entry: just test
|
|
36
|
+
language: system
|
|
37
|
+
pass_filenames: false
|
|
38
|
+
stages: [pre-push]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# AGENTS
|
|
2
|
+
|
|
3
|
+
## Important files
|
|
4
|
+
|
|
5
|
+
Read:
|
|
6
|
+
|
|
7
|
+
- [README](/README.md) to understand the purpose of the project.
|
|
8
|
+
- [ARCHITECTURE](/ARCHITECTURE.md) to grasp the structure and conventions.
|
|
9
|
+
- [CONTRIBUTING](/CONTRIBUTING.md) for contribution guidelines.
|
|
10
|
+
|
|
11
|
+
## Commands
|
|
12
|
+
|
|
13
|
+
- Use `just` as a task runner (see `just --summary` for the list of all tasks).
|
|
14
|
+
- Run `just lintcheck <paths>` to check linting.
|
|
15
|
+
- Run `just formatcheck <paths>` to check formatting.
|
|
16
|
+
- Run auto-fix before trying to fix issues manually:
|
|
17
|
+
`just format <paths>` (format fix), `just lint <paths>` (linting fix).
|
|
18
|
+
- Run `just typecheck <paths>` for static type-checking on selected files.
|
|
19
|
+
- Run `just test <pytest args>` for tests.
|
|
20
|
+
- Use `just check` to run all checks on the whole codebase.
|
|
21
|
+
|
|
22
|
+
You can use lightweight `just lint[check]` and `just format[check]` frequently.
|
|
23
|
+
Prefer focused `just typecheck` and `just test` over `just check`, for faster feedback.
|
|
24
|
+
To consider the task done, `just check` must pass without errors.
|
|
25
|
+
|
|
26
|
+
## Dependencies
|
|
27
|
+
|
|
28
|
+
- Prefer the standard library and existing project dependencies before adding a new package; add a new dependency only when absolutely necessary.
|
|
29
|
+
- Use `uv add <package>` or `uv add --group <group> <package>` to modify
|
|
30
|
+
dependencies. Do not edit the lockfile manually.
|
|
31
|
+
- Commit the dependency manifest and lockfile together whenever dependencies change.
|
|
32
|
+
- Pin or constrain versions only when compatibility, reproducibility, or a known
|
|
33
|
+
upstream regression requires it; otherwise follow the project's existing
|
|
34
|
+
version policy.
|
|
35
|
+
|
|
36
|
+
## Code style
|
|
37
|
+
|
|
38
|
+
- The code is written in Python 3.12.
|
|
39
|
+
- All code must be linted and formatted before commit.
|
|
40
|
+
- Depend on abstractions, not concrete backends; benefit from polymorphism.
|
|
41
|
+
|
|
42
|
+
### Typing rules
|
|
43
|
+
|
|
44
|
+
- Fully-type the code; Annotate all functions and methods.
|
|
45
|
+
- Never use `typing.Any`; Never suppress diagnostics.
|
|
46
|
+
- Use structural typing via `typing.Protocol` instead of `abc.ABC`.
|
|
47
|
+
- If you need to add a base class, use `abc.ABC` alongside (not instead of) `typing.Protocol`.
|
|
48
|
+
- Use Python 3.12+ style generics.
|
|
49
|
+
- Run `just typecheck <paths>` for static type-checking (`basedpyright` strict).
|
|
50
|
+
|
|
51
|
+
When working with poorly typed third-party stubs never give up on types --
|
|
52
|
+
reason through them step-by-step.
|
|
53
|
+
|
|
54
|
+
### Patterns
|
|
55
|
+
|
|
56
|
+
#### Control flow
|
|
57
|
+
|
|
58
|
+
- Prefer early returns over deeply nested conditionals.
|
|
59
|
+
- Prefer `match` when branching on a closed set of variants; use `if` only for
|
|
60
|
+
predicates.
|
|
61
|
+
- Prefer comprehensions for simple transformations; use named functions when the
|
|
62
|
+
transformation needs more than one clause.
|
|
63
|
+
- Prefer iterators and generators for streaming data; materialize collections only
|
|
64
|
+
when indexing, reuse, or batching requires it.
|
|
65
|
+
|
|
66
|
+
#### Function interfaces
|
|
67
|
+
|
|
68
|
+
- Use keyword-only arguments for parameters whose meaning is unclear at call sites.
|
|
69
|
+
- Represent optional values explicitly with `T | None`; validate or narrow them
|
|
70
|
+
immediately at the boundary where they are consumed.
|
|
71
|
+
- Prefer `Sequence[T]`, `Mapping[K, V]`, `Iterable[T]`, and `Collection[T]` in input
|
|
72
|
+
annotations unless mutation or a concrete operation requires `list` or `dict`.
|
|
73
|
+
- Accept the narrowest appropriate abstract collection type in inputs; return a
|
|
74
|
+
concrete immutable collection from public APIs when practical.
|
|
75
|
+
- Prefer returning new values over mutating inputs or shared state; permit in-place
|
|
76
|
+
mutation only when the function contract explicitly promises it.
|
|
77
|
+
- Never use mutable default arguments; use `None` and construct the value inside the
|
|
78
|
+
function.
|
|
79
|
+
|
|
80
|
+
#### Models and states
|
|
81
|
+
|
|
82
|
+
- Use `default_factory` for mutable dataclass fields.
|
|
83
|
+
- Use `msgspec.Struct` for schemas, serialization and where performance is crucial;
|
|
84
|
+
keep decoding and validation at system boundaries.
|
|
85
|
+
- Use `Enum` or discriminated union types for closed sets of states; do not represent
|
|
86
|
+
states with magic strings.
|
|
87
|
+
- Use `StrEnum` only when a string representation is part of a public boundary.
|
|
88
|
+
- Keep models that cross a boundary immutable unless mutation is essential to their
|
|
89
|
+
purpose.
|
|
90
|
+
|
|
91
|
+
#### Type narrowing
|
|
92
|
+
|
|
93
|
+
- Use `TypeIs` helper functions to narrow complex unions.
|
|
94
|
+
- Keep narrowing functions small, deterministic, and named as predicates.
|
|
95
|
+
- Use `typing.cast` only when the type equivalence is known and cannot be expressed
|
|
96
|
+
through normal narrowing.
|
|
97
|
+
|
|
98
|
+
#### Errors
|
|
99
|
+
|
|
100
|
+
- Catch only exceptions that can be handled meaningfully; keep `try` blocks minimal.
|
|
101
|
+
- Raise specific, contextual exceptions; chain external failures with
|
|
102
|
+
`raise ... from exc`.
|
|
103
|
+
- Keep error reporting at the boundary responsible for recovery or user-visible output.
|
|
104
|
+
- Make retries explicit and limited. Retry only transient, idempotent operations.
|
|
105
|
+
- Use exponential backoff with jitter for retryable external failures.
|
|
106
|
+
- Do not retry validation errors, malformed input, or authorization failures.
|
|
107
|
+
- For error logging, follow the [logging conventions](#logging) below.
|
|
108
|
+
|
|
109
|
+
#### Resources and I/O
|
|
110
|
+
|
|
111
|
+
- Use `pathlib.Path` for filesystem paths. Do not pass paths as raw strings internally.
|
|
112
|
+
- Use `contextlib` context managers for resources with explicit lifetimes.
|
|
113
|
+
- Close resources deterministically, including clients, files, streams, and database
|
|
114
|
+
sessions.
|
|
115
|
+
- Keep filesystem, network, database, and model calls at explicit boundaries.
|
|
116
|
+
|
|
117
|
+
#### Imports and modules
|
|
118
|
+
|
|
119
|
+
- Keep imports at module scope; use local imports only to avoid an import cycle or
|
|
120
|
+
defer an optional or expensive dependency.
|
|
121
|
+
- Keep modules focused on one cohesive concern. Split a module before it becomes a
|
|
122
|
+
catch-all for unrelated helpers.
|
|
123
|
+
- Avoid `utils`, `common`, and `helpers` modules. Name modules after the capability
|
|
124
|
+
they provide.
|
|
125
|
+
- Keep public exports explicit with `__all__` only for deliberately curated package APIs.
|
|
126
|
+
|
|
127
|
+
## Comment style
|
|
128
|
+
|
|
129
|
+
Your code shall read like prose, even without comments:
|
|
130
|
+
|
|
131
|
+
- Use PEP 257 one-line docstrings.
|
|
132
|
+
- Module-level docstrings should describe the module's purpose, not its contents.
|
|
133
|
+
- If something (e.g. module, class, function) cannot be explained with a
|
|
134
|
+
one-line docstring, refactor or split it instead.
|
|
135
|
+
- Comments explain _why_, not _what_.
|
|
136
|
+
- If you feel the need to explain _what_ the code does, refactor it instead.
|
|
137
|
+
- If comment or docstring restates the obvious, remove it.
|
|
138
|
+
|
|
139
|
+
## Logging
|
|
140
|
+
|
|
141
|
+
- Use the standard `logging` package and create one module-level logger:
|
|
142
|
+
`logger = logging.getLogger(__name__)`.
|
|
143
|
+
- Configure handlers, levels, and `colorlog` formatters only at application entry
|
|
144
|
+
points. Never configure logging in imported modules.
|
|
145
|
+
- Use the format `%(log_color)s%(levelname)-8s%(reset)s %(name)s: %(message)s`;
|
|
146
|
+
prefer a short package-relative module name when configuring the formatter.
|
|
147
|
+
- Keep messages brief, lower-case, and action-oriented:
|
|
148
|
+
`logger.info("parsed 12 formula")`.
|
|
149
|
+
- Use lazy `%s` formatting for values:
|
|
150
|
+
`logger.debug("processing %s", path)`.
|
|
151
|
+
- Log identifiers and concise counts, not complete document contents, prompts, graph
|
|
152
|
+
payloads, secrets, or personal data.
|
|
153
|
+
- Use `DEBUG` for diagnostics, `INFO` for meaningful progress, `WARNING` for handled
|
|
154
|
+
anomalies, and `ERROR` for operations that fail.
|
|
155
|
+
- Use `logger.exception("failed to parse %s", path)` only inside an `except` block
|
|
156
|
+
when the current layer owns reporting or recovery.
|
|
157
|
+
- Do not log an exception and re-raise it unless this layer adds actionable context
|
|
158
|
+
or terminates the operation.
|
|
159
|
+
|
|
160
|
+
## Testing
|
|
161
|
+
|
|
162
|
+
- Keep tests under `tests/`.
|
|
163
|
+
- Keep tests in Arrange–Act–Assert order, with one behavioral reason to fail per test.
|
|
164
|
+
- Test observable behavior and domain invariants, not private implementation details.
|
|
165
|
+
- Prefer parametrized tests for equivalent cases with different inputs and outputs.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# ARCHITECTURE
|
|
2
|
+
|
|
3
|
+
This document describes `latexwalker` library boundaries.
|
|
4
|
+
Keep it current when changing the project architecture.
|
|
5
|
+
|
|
6
|
+
## System
|
|
7
|
+
|
|
8
|
+
`latexwalker` is a library for parsing LaTeX source into a syntax tree and
|
|
9
|
+
normalizing its formatting. Parsing and normalization are separate concerns:
|
|
10
|
+
the parser produces a lossless view of the source (every node carries the
|
|
11
|
+
exact region it was built from), and the normalizer re-emits it in canonical
|
|
12
|
+
form.
|
|
13
|
+
|
|
14
|
+
## Project layout
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
src/latexwalker/
|
|
18
|
+
├── __init__.py # curated public API surface
|
|
19
|
+
├── _version.py # generated by hatch-vcs; do not edit
|
|
20
|
+
├── errors.py # LatexWalkerError hierarchy and ParseError context
|
|
21
|
+
├── source.py # SourcePoint / SourceSpan schemas and SourceMap offset index
|
|
22
|
+
├── tokens.py # TokenType vocabulary and immutable Token schema
|
|
23
|
+
├── lexer.py # streaming tokenizer over raw text
|
|
24
|
+
├── nodes.py # frozen syntax tree schemas (Document root + Content union)
|
|
25
|
+
├── parser.py # recursive-descent parser assembling documents
|
|
26
|
+
├── traverse.py # structural traversal over any node kind
|
|
27
|
+
├── rules.py # normalization rule-set schemas and YAML decoding
|
|
28
|
+
├── zones.py # preserve / math / text classification of source regions
|
|
29
|
+
├── rewrite.py # rule set fused into a single-pass rewriting automaton
|
|
30
|
+
├── normalize.py # public canonicalization entry point
|
|
31
|
+
└── resources/
|
|
32
|
+
└── rules.yaml
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Pipeline
|
|
36
|
+
|
|
37
|
+
1. **Lexing** (`lexer.tokenize`) converts a `SourceMap`-backed text into a
|
|
38
|
+
stream of `Token`s ending with an end-of-input sentinel. Whitespace runs
|
|
39
|
+
become `WHITESPACE` or, when they contain two or more newlines,
|
|
40
|
+
`PARAGRAPH_BREAK`. Comments run to the end of their line. Macro values
|
|
41
|
+
exclude the leading backslash.
|
|
42
|
+
2. **Parsing** (`parser.parse_document`) consumes tokens recursively into a
|
|
43
|
+
`Document` of frozen nodes. Each construct (groups, macros with attached
|
|
44
|
+
arguments, environments, math regions) declares the closers it owns, so
|
|
45
|
+
errors point at precise spans via `ParseError`. Nesting beyond a fixed
|
|
46
|
+
depth bound also fails with `ParseError`; the bound is chosen low enough
|
|
47
|
+
that nested parses never approach the interpreter's own stack limit.
|
|
48
|
+
3. **Traversal** (`traverse.walk`, `traverse.iter_children`) visits trees
|
|
49
|
+
structurally; unknown future node kinds fall through safely.
|
|
50
|
+
4. **Re-emission** currently means lossless extraction through spans
|
|
51
|
+
(`Document.source_of`); canonical formatting will build on the same node
|
|
52
|
+
contracts.
|
|
53
|
+
5. **Normalization** (`normalize.normalize`) reduces plain text toward its
|
|
54
|
+
canonical form. `zones.classify` first fences off what must never change
|
|
55
|
+
(comments, verbatim-family environments) and labels math regions; the
|
|
56
|
+
rewriteable zones then pass through `rewrite.CompiledRuleSet`, a rule
|
|
57
|
+
set fused into a single alternation so one regex scan serves any rule
|
|
58
|
+
count. Rule sets are strict msgspec schemas (`rules.RuleSet`) decoded
|
|
59
|
+
from YAML; the packaged `resources/rules.yaml` supplies defaults and
|
|
60
|
+
is the stage's only filesystem access.
|
|
61
|
+
|
|
62
|
+
## Lexer decisions
|
|
63
|
+
|
|
64
|
+
The lexer keys on raw characters instead of TeX's line-based
|
|
65
|
+
canonicalization. These behaviors are deliberate and pinned by tests:
|
|
66
|
+
|
|
67
|
+
- Control sequences never span lines: scanning stops at the newline, which is
|
|
68
|
+
emitted as its own whitespace token.
|
|
69
|
+
- A backslash followed by LF fails with `ParseError`; a backslash followed by
|
|
70
|
+
CR forms a control-symbol macro whose name is the CR character.
|
|
71
|
+
- Comments extend through any trailing CR of a CRLF terminator before ending
|
|
72
|
+
at the LF.
|
|
73
|
+
- Paragraph breaks are detected by counting raw LF characters (two or more)
|
|
74
|
+
in a whitespace run; blank lines built from CR alone stay whitespace.
|
|
75
|
+
|
|
76
|
+
## Boundaries
|
|
77
|
+
|
|
78
|
+
### Core
|
|
79
|
+
|
|
80
|
+
- Tokenizing and parsing raw LaTeX text into a syntax tree.
|
|
81
|
+
- The core does not read from or write to the filesystem.
|
|
82
|
+
|
|
83
|
+
### Normalization
|
|
84
|
+
|
|
85
|
+
- Works on plain strings, independent of parser node types, so it runs
|
|
86
|
+
before lexing or over extracted spans equally well.
|
|
87
|
+
- Rules are user-authored pattern/canonical pairs; scoping (`any`,
|
|
88
|
+
`math`, `text`) declares where a rewrite is render-equivalent.
|
|
89
|
+
- The packaged default rule set is the package's only filesystem read.
|
|
90
|
+
|
|
91
|
+
### API
|
|
92
|
+
|
|
93
|
+
- The public surface is whatever `src/latexwalker/__init__.py` exports;
|
|
94
|
+
submodules remain importable but are not covered by stability promises.
|
|
95
|
+
- Callers pass in source text and receive a `Document`; internal node types
|
|
96
|
+
are deliberately exported so downstream code can match on them.
|
|
97
|
+
- All errors derive from `LatexWalkerError`.
|
|
98
|
+
|
|
99
|
+
## Data contracts
|
|
100
|
+
|
|
101
|
+
- Schemas crossing a system boundary are defined with `msgspec.Struct`s;
|
|
102
|
+
decoding and validation happen at those boundaries only.
|
|
103
|
+
- Every schema class is `frozen=True, kw_only=True`, configured once on the
|
|
104
|
+
shared base classes.
|
|
105
|
+
- Boundary schemas reject unknown keys; the rule-set schema additionally
|
|
106
|
+
requires a `version` of at least 1 and non-empty patterns.
|
|
107
|
+
- Trees are immutable after construction; positions are 1-based lines and
|
|
108
|
+
0-based columns.
|