isar-tools 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.
Files changed (133) hide show
  1. isar_tools-0.1.0/.gitignore +9 -0
  2. isar_tools-0.1.0/CHANGELOG.md +80 -0
  3. isar_tools-0.1.0/LICENSE +21 -0
  4. isar_tools-0.1.0/PKG-INFO +230 -0
  5. isar_tools-0.1.0/README.md +209 -0
  6. isar_tools-0.1.0/docs/FORMATTER.md +127 -0
  7. isar_tools-0.1.0/docs/PARSER_DECISION.md +194 -0
  8. isar_tools-0.1.0/docs/PLAN.md +302 -0
  9. isar_tools-0.1.0/docs/RELEASING.md +99 -0
  10. isar_tools-0.1.0/pyproject.toml +69 -0
  11. isar_tools-0.1.0/src/isar_tools/__init__.py +1 -0
  12. isar_tools-0.1.0/src/isar_tools/__main__.py +5 -0
  13. isar_tools-0.1.0/src/isar_tools/checks/__init__.py +0 -0
  14. isar_tools-0.1.0/src/isar_tools/checks/cli.py +156 -0
  15. isar_tools-0.1.0/src/isar_tools/checks/docs.py +114 -0
  16. isar_tools-0.1.0/src/isar_tools/checks/findings.py +55 -0
  17. isar_tools-0.1.0/src/isar_tools/checks/locales.py +370 -0
  18. isar_tools-0.1.0/src/isar_tools/checks/project.py +97 -0
  19. isar_tools-0.1.0/src/isar_tools/checks/theory.py +117 -0
  20. isar_tools-0.1.0/src/isar_tools/cli.py +55 -0
  21. isar_tools-0.1.0/src/isar_tools/formatter/__init__.py +0 -0
  22. isar_tools-0.1.0/src/isar_tools/formatter/cli.py +103 -0
  23. isar_tools-0.1.0/src/isar_tools/formatter/formatter.py +377 -0
  24. isar_tools-0.1.0/src/isar_tools/formatter/wrap.py +103 -0
  25. isar_tools-0.1.0/src/isar_tools/project/__init__.py +0 -0
  26. isar_tools-0.1.0/src/isar_tools/project/cli.py +548 -0
  27. isar_tools-0.1.0/src/isar_tools/project/hierarchy.py +330 -0
  28. isar_tools-0.1.0/src/isar_tools/project/model.py +353 -0
  29. isar_tools-0.1.0/src/isar_tools/project/names.py +246 -0
  30. isar_tools-0.1.0/src/isar_tools/project/root.py +298 -0
  31. isar_tools-0.1.0/src/isar_tools/project/workspace.py +125 -0
  32. isar_tools-0.1.0/src/isar_tools/render.py +110 -0
  33. isar_tools-0.1.0/src/isar_tools/source/__init__.py +0 -0
  34. isar_tools-0.1.0/src/isar_tools/source/files.py +16 -0
  35. isar_tools-0.1.0/src/isar_tools/source/keywords.py +197 -0
  36. isar_tools-0.1.0/src/isar_tools/source/lexer.py +206 -0
  37. isar_tools-0.1.0/src/isar_tools/source/symbol_table.py +448 -0
  38. isar_tools-0.1.0/src/isar_tools/source/symbols.py +48 -0
  39. isar_tools-0.1.0/src/isar_tools/source/theory.py +387 -0
  40. isar_tools-0.1.0/src/isar_tools/stats/__init__.py +0 -0
  41. isar_tools-0.1.0/src/isar_tools/stats/build.py +278 -0
  42. isar_tools-0.1.0/src/isar_tools/stats/cli.py +213 -0
  43. isar_tools-0.1.0/src/isar_tools/stats/metrics.py +154 -0
  44. isar_tools-0.1.0/src/isar_tools/stats/views.py +295 -0
  45. isar_tools-0.1.0/src/isar_tools/style.py +73 -0
  46. isar_tools-0.1.0/src/isar_tools/symbols_cli.py +61 -0
  47. isar_tools-0.1.0/tests/cli/test_check_cli.py +114 -0
  48. isar_tools-0.1.0/tests/cli/test_check_docs_cli.py +45 -0
  49. isar_tools-0.1.0/tests/cli/test_extract_cli.py +169 -0
  50. isar_tools-0.1.0/tests/cli/test_fmt_cli.py +100 -0
  51. isar_tools-0.1.0/tests/cli/test_hierarchy_cli.py +92 -0
  52. isar_tools-0.1.0/tests/cli/test_project_cli.py +60 -0
  53. isar_tools-0.1.0/tests/cli/test_stats_build_cli.py +170 -0
  54. isar_tools-0.1.0/tests/cli/test_stats_cli.py +139 -0
  55. isar_tools-0.1.0/tests/cli/test_symbols_cli.py +55 -0
  56. isar_tools-0.1.0/tests/conftest.py +46 -0
  57. isar_tools-0.1.0/tests/corpus/test_corpus.py +68 -0
  58. isar_tools-0.1.0/tests/formatter/expected/apply_scripts.thy +21 -0
  59. isar_tools-0.1.0/tests/formatter/expected/context.thy +18 -0
  60. isar_tools-0.1.0/tests/formatter/expected/crlf.thy +4 -0
  61. isar_tools-0.1.0/tests/formatter/expected/layout.thy +28 -0
  62. isar_tools-0.1.0/tests/formatter/expected/plan_example.thy +18 -0
  63. isar_tools-0.1.0/tests/formatter/expected/structure.thy +26 -0
  64. isar_tools-0.1.0/tests/formatter/expected-normalize/apply_scripts.thy +21 -0
  65. isar_tools-0.1.0/tests/formatter/expected-normalize/context.thy +18 -0
  66. isar_tools-0.1.0/tests/formatter/expected-normalize/crlf.thy +4 -0
  67. isar_tools-0.1.0/tests/formatter/expected-normalize/layout.thy +28 -0
  68. isar_tools-0.1.0/tests/formatter/expected-normalize/plan_example.thy +18 -0
  69. isar_tools-0.1.0/tests/formatter/expected-normalize/structure.thy +26 -0
  70. isar_tools-0.1.0/tests/formatter/input/apply_scripts.thy +21 -0
  71. isar_tools-0.1.0/tests/formatter/input/context.thy +18 -0
  72. isar_tools-0.1.0/tests/formatter/input/crlf.thy +4 -0
  73. isar_tools-0.1.0/tests/formatter/input/layout.thy +30 -0
  74. isar_tools-0.1.0/tests/formatter/input/plan_example.thy +18 -0
  75. isar_tools-0.1.0/tests/formatter/input/structure.thy +26 -0
  76. isar_tools-0.1.0/tests/formatter/test_formatter.py +77 -0
  77. isar_tools-0.1.0/tests/formatter/test_wrap.py +62 -0
  78. isar_tools-0.1.0/tests/golden/check/a.json +18 -0
  79. isar_tools-0.1.0/tests/golden/check/default.txt +3 -0
  80. isar_tools-0.1.0/tests/golden/check/docs.txt +3 -0
  81. isar_tools-0.1.0/tests/golden/check/locales.txt +1 -0
  82. isar_tools-0.1.0/tests/golden/extract/kinds.txt +5 -0
  83. isar_tools-0.1.0/tests/golden/extract/names.json +44 -0
  84. isar_tools-0.1.0/tests/golden/extract/names.md +17 -0
  85. isar_tools-0.1.0/tests/golden/extract/names.txt +7 -0
  86. isar_tools-0.1.0/tests/golden/extract/one.json +12 -0
  87. isar_tools-0.1.0/tests/golden/extract/print.txt +7 -0
  88. isar_tools-0.1.0/tests/golden/hierarchy/all.txt +12 -0
  89. isar_tools-0.1.0/tests/golden/hierarchy/numeric.json +59 -0
  90. isar_tools-0.1.0/tests/golden/hierarchy/own_only.txt +12 -0
  91. isar_tools-0.1.0/tests/golden/hierarchy/root.dot +6 -0
  92. isar_tools-0.1.0/tests/golden/hierarchy/root.txt +7 -0
  93. isar_tools-0.1.0/tests/golden/hierarchy/sorts.dot +8 -0
  94. isar_tools-0.1.0/tests/golden/project/graph.dot +10 -0
  95. isar_tools-0.1.0/tests/golden/project/graph.txt +8 -0
  96. isar_tools-0.1.0/tests/golden/project/sessions.txt +7 -0
  97. isar_tools-0.1.0/tests/golden/project/theories.csv +4 -0
  98. isar_tools-0.1.0/tests/golden/project/theories.dot +9 -0
  99. isar_tools-0.1.0/tests/golden/project/theories_graph.txt +7 -0
  100. isar_tools-0.1.0/tests/golden/render/two_tables.csv +9 -0
  101. isar_tools-0.1.0/tests/golden/render/two_tables.json +22 -0
  102. isar_tools-0.1.0/tests/golden/render/two_tables.markdown +13 -0
  103. isar_tools-0.1.0/tests/golden/render/two_tables.text +11 -0
  104. isar_tools-0.1.0/tests/golden/stats/build.json +53 -0
  105. isar_tools-0.1.0/tests/golden/stats/build.txt +14 -0
  106. isar_tools-0.1.0/tests/golden/stats/build_budgets.txt +19 -0
  107. isar_tools-0.1.0/tests/golden/stats/commands.txt +23 -0
  108. isar_tools-0.1.0/tests/golden/stats/core.json +34 -0
  109. isar_tools-0.1.0/tests/golden/stats/proofs.csv +5 -0
  110. isar_tools-0.1.0/tests/golden/stats/sessions.txt +6 -0
  111. isar_tools-0.1.0/tests/golden/stats/style.txt +6 -0
  112. isar_tools-0.1.0/tests/golden/stats/style_default.txt +4 -0
  113. isar_tools-0.1.0/tests/golden/stats/summary.md +15 -0
  114. isar_tools-0.1.0/tests/golden/stats/summary.txt +13 -0
  115. isar_tools-0.1.0/tests/golden/stats/theories_top2.txt +5 -0
  116. isar_tools-0.1.0/tests/property/test_formatter_properties.py +97 -0
  117. isar_tools-0.1.0/tests/property/test_lexer_properties.py +70 -0
  118. isar_tools-0.1.0/tests/unit/test_build.py +141 -0
  119. isar_tools-0.1.0/tests/unit/test_checks.py +161 -0
  120. isar_tools-0.1.0/tests/unit/test_cli.py +65 -0
  121. isar_tools-0.1.0/tests/unit/test_docs_check.py +109 -0
  122. isar_tools-0.1.0/tests/unit/test_hierarchy.py +126 -0
  123. isar_tools-0.1.0/tests/unit/test_lexer.py +92 -0
  124. isar_tools-0.1.0/tests/unit/test_locales.py +150 -0
  125. isar_tools-0.1.0/tests/unit/test_metrics.py +90 -0
  126. isar_tools-0.1.0/tests/unit/test_model.py +176 -0
  127. isar_tools-0.1.0/tests/unit/test_names.py +141 -0
  128. isar_tools-0.1.0/tests/unit/test_render.py +32 -0
  129. isar_tools-0.1.0/tests/unit/test_root.py +131 -0
  130. isar_tools-0.1.0/tests/unit/test_style.py +45 -0
  131. isar_tools-0.1.0/tests/unit/test_symbols.py +38 -0
  132. isar_tools-0.1.0/tests/unit/test_theory.py +264 -0
  133. isar_tools-0.1.0/tests/unit/test_workspace.py +64 -0
@@ -0,0 +1,9 @@
1
+ .pixi/
2
+ __pycache__/
3
+ *.egg-info/
4
+ .coverage
5
+ .pytest_cache/
6
+ .ruff_cache/
7
+ .hypothesis/
8
+ dist/
9
+ build/
@@ -0,0 +1,80 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (2026-09-27)
4
+
5
+ First release.
6
+
7
+ - Packaging: the sdist holds sources, tests, and documentation only (no lock files or
8
+ demo recording); README links are absolute, so they work on PyPI.
9
+ `docs/RELEASING.md` describes a release, including the conda-forge recipe.
10
+ - `-d DIR` must name an existing directory (exit status 2 otherwise), as with
11
+ `isabelle build -d`. Before, a mistyped `-d` was ignored, and commands of that
12
+ directory were silently read as part of the previous command.
13
+ - `isar project names`: named declarations with qualified name (`Theory.locale.name`),
14
+ kind, command, session, location, and docstring (a directly preceding `text` block), as
15
+ text, JSON, CSV, or a Markdown index grouped by session and theory. `--kind` filters;
16
+ `--name` (base or exact qualified name) exits 1 for a name that declares nothing and
17
+ suggests the qualified names that exist. Replaces Voblint's
18
+ `extract_definitions.py --dump` and `check_theory_anchors.py`.
19
+ - `isar ... | head` no longer prints a `BrokenPipeError` traceback.
20
+ - `isar project extract NAME...`: the source of a declaration (command and, for a goal,
21
+ its proof), found by `name`, `locale.name`, `Theory.name`, or `Theory.locale.name`; a
22
+ name must identify exactly one declaration. `--manifest TOML --out DIR --write|--check`
23
+ keeps quoted declarations in a document in sync with the theories (the format of
24
+ Voblint's `thesis/shared/snippets.toml`).
25
+ - `isar check docs`: opt-in documentation-coverage group, ported from Voblint's
26
+ `extract_definitions.py --lint`. `undocumented-theory` (no `text` before the first
27
+ declaration), `undocumented-heading` (no `text` right after or before a heading),
28
+ `undocumented-locale` and `undocumented-class` (no `text` right before the declaration).
29
+ `(* *)` comments, formal comments, and `text_raw` do not count as documentation.
30
+ - `isar check locales` (opt-in group, code `locale-free-variable`): identifiers in the
31
+ terms of `locale` and unnamed `context` headers that are no parameter (own, `for`
32
+ clause, `defines`, or inherited from parent locales through imports), not bound in the
33
+ term, and not used anywhere else in the project or its `-d` imports; Isabelle would
34
+ generalize over them. Heuristic filter: at least four characters and an underscore.
35
+ `--allow NAME` (repeatable). Ported from Voblint's `check_locale_parameters.py`.
36
+ - `isar project hierarchy` model: declarations also record `for` clause parameters,
37
+ `defines`, and term tokens; `context` headers parse too; `opening` ends a locale
38
+ expression.
39
+ - Lexer: `\<in>`, `\<le>`, and other two-letter symbols are no longer identifier letters
40
+ (only doubled letters such as `\<AA>` are), and `\<^sup>`/`\<^bold>` no longer continue
41
+ an identifier.
42
+ - `isar project hierarchy`: class and locale declarations (parents, fixes with type and
43
+ notation, assumes, parameter sorts) as text, JSON, or DOT; `--root` follows parents
44
+ through imports, including `-d` directories, and reports unresolved names.
45
+ - Unqualified imports of global theory names (`Main`) resolve through the parent
46
+ session chain.
47
+ - `isar stats build BUILD_LOG`: where theory elaboration time went in an `isabelle build -v`
48
+ log. Per building session: theories elaborated, cpu seconds, and the share spent on
49
+ theories owned by other sessions; theories elaborated more than once with the time
50
+ wasted (`--top N`); `--budget SESSION=N` (repeatable) exits 1 when SESSION's theories
51
+ are elaborated inside other sessions more than N times. Reads only the line format
52
+ `SESSION: theory OWNER.THEORY 100% (Ns cumulated time)`; a log without such lines, or one
53
+ in which a session elaborates a theory twice, is exit status 2 (`--allow-empty` accepts
54
+ an incremental build that rebuilt nothing). `build` is now a view
55
+ name, so a directory called `build` needs `isar stats ./build`.
56
+ - `isar check` prints a summary line (`2 findings: 1 missing-theory, ...`) and colours
57
+ findings on a terminal; `fmt --diff` and `symbols normalize --diff` colour their diffs.
58
+ `--color auto|always|never`; `NO_COLOR` is honoured. Finding messages are shorter.
59
+ - README demo GIF recorded by VHS from `docs/demo/demo.tape` (`pixi run demo`, in a
60
+ separate `demo` environment). It shows `isar fmt --diff`; `pixi run demo-check` runs the
61
+ tape's commands without recording and checks their exit status.
62
+ - Source model: lossless outer-syntax lexer, Isabelle symbol table, theory headers,
63
+ command segmentation with per-theory keyword tables, and goal blocks.
64
+ - Project model: ROOT parser with positioned diagnostics, `ROOTS` discovery, theory and
65
+ import resolution, reachability.
66
+ - `isar stats` with views `summary`, `sessions`, `theories`, `proofs`, `commands`, and
67
+ `style`, in text, Markdown, JSON, and CSV.
68
+ - `isar check` with groups `project`, `proofs`, `syntax` (default) and `symbols`; stable
69
+ finding codes, `--ignore`, text/JSON/CSV output, exit status 1 on findings.
70
+ - `isar symbols normalize` (ASCII or Unicode spelling; `--check`, `--diff`).
71
+ - `isar project sessions|theories|graph` (graph as text, JSON, or DOT).
72
+ - `-d DIR` on `stats`, `check`, and `project`: sessions of DIR resolve imports and
73
+ commands, like `isabelle build -d`.
74
+ - Source files are read and written byte-exactly, so CRLF line endings survive.
75
+ - `isar fmt`: conservative formatter (indentation from proof structure, trailing
76
+ whitespace, blank lines); `--check`, `--diff`, `--normalize`, stdin via `-`. Changes
77
+ only layout, by construction and by test.
78
+ - Opt-in corpus tests (`pixi run corpus` with `ISAR_CORPUS`).
79
+ - Repository scaffold: pixi environment, package skeleton, `isar` CLI with
80
+ placeholder commands, tests, lint, type checking, and CI.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Manuel Lerchner
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,230 @@
1
+ Metadata-Version: 2.5
2
+ Name: isar-tools
3
+ Version: 0.1.0
4
+ Summary: Source tooling for Isabelle/Isar projects: formatting, checks, and statistics.
5
+ Project-URL: Homepage, https://github.com/ManuelLerchner/isar-tools
6
+ Project-URL: Repository, https://github.com/ManuelLerchner/isar-tools
7
+ Project-URL: Issues, https://github.com/ManuelLerchner/isar-tools/issues
8
+ Author: Manuel Lerchner
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: formatter,isabelle,isar,theorem-proving
12
+ Classifier: Development Status :: 2 - Pre-Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Topic :: Software Development :: Quality Assurance
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+
22
+ # isar-tools
23
+
24
+ [![CI Status][ci-badge]][ci]
25
+ [![License][license-badge]][license]
26
+
27
+ [ci-badge]: https://img.shields.io/github/actions/workflow/status/ManuelLerchner/isar-tools/ci.yml?branch=main&style=flat-square&label=CI
28
+ [ci]: https://github.com/ManuelLerchner/isar-tools/actions/workflows/ci.yml
29
+ [license-badge]: https://img.shields.io/github/license/ManuelLerchner/isar-tools?style=flat-square
30
+ [license]: https://github.com/ManuelLerchner/isar-tools/blob/main/LICENSE
31
+
32
+ Source tooling for Isabelle/Isar projects: a formatter, project and source
33
+ checks, and statistics. Pure Python. Works on `.thy` and `ROOT` files without
34
+ running Isabelle.
35
+
36
+ ![Terminal recording: isar stats prints session and theory tables for a small
37
+ demo project, isar check reports an unfinished proof (sorry), isar fmt --diff
38
+ indents a proof and removes trailing whitespace and extra blank lines, and isar
39
+ project graph prints the theory import graph.](https://raw.githubusercontent.com/ManuelLerchner/isar-tools/main/docs/demo/demo.gif)
40
+
41
+ Status: pre-alpha. See [`CHANGELOG.md`](https://github.com/ManuelLerchner/isar-tools/blob/main/CHANGELOG.md) and
42
+ [`docs/PLAN.md`](https://github.com/ManuelLerchner/isar-tools/blob/main/docs/PLAN.md).
43
+
44
+ ## Install
45
+
46
+ ```sh
47
+ pip install isar-tools # or: uv tool install isar-tools, pipx install isar-tools
48
+ isar --help
49
+ ```
50
+
51
+ Python 3.11 or newer; no runtime dependencies. A conda-forge package
52
+ (`pixi global install isar-tools`) follows the first PyPI release.
53
+
54
+ ## Commands
55
+
56
+ | Command | What it does |
57
+ | ---------------------------------------- | ------------------------------------------------------------------------------------------ |
58
+ | `isar fmt [PATH...]` | Format theories: indentation, trailing whitespace, blank lines, and optional line wrapping |
59
+ | `isar check [GROUP] [PATH...]` | Report problems in ROOT files, proofs, syntax, symbols, docs, and locales |
60
+ | `isar stats [VIEW] [PATH...]` | Size, proof, and command statistics |
61
+ | `isar stats build BUILD_LOG` | Where theory elaboration time went in an `isabelle build -v` log |
62
+ | `isar project sessions\|theories\|graph` | Sessions, theories, and the session or theory import graph |
63
+ | `isar project hierarchy` | Class and locale declarations: parents, parameters, assumptions |
64
+ | `isar project names` | Named declarations: qualified name, kind, location, docstring; a Markdown index |
65
+ | `isar project extract NAME...` | The source of a declaration by name; keeps quoted declarations in sync with a manifest |
66
+ | `isar symbols normalize PATH...` | Rewrite symbols as `\<name>`, or as Unicode |
67
+
68
+ A path is a project directory, read like `isabelle build -D` (its `ROOT`, and
69
+ `ROOTS` recursively), or a `.thy` file. Commands that read a project default to the current
70
+ directory.
71
+
72
+ Exit status: `0` success, `1` findings or differences, `2` invalid invocation or
73
+ unreadable input. Data goes to stdout, diagnostics to stderr. JSON and CSV output
74
+ use stable snake_case keys and are never coloured.
75
+
76
+ ### Formatting
77
+
78
+ ```sh
79
+ isar fmt # format every .thy below the current directory
80
+ isar fmt --check # list files that would change; exit 1 if any
81
+ isar fmt --diff Foo.thy # show the change without writing
82
+ isar fmt --max-line-length 100 # also wrap long lines
83
+ isar fmt - # stdin to stdout, for editors
84
+ ```
85
+
86
+ The formatter only changes layout. It never changes a token, never touches the
87
+ space between two tokens on a line, and never rewrites strings, cartouches,
88
+ comments, or ML. By default it only indents lines that are too shallow for their
89
+ proof structure; `--normalize` sets indentation exactly. See
90
+ [`docs/FORMATTER.md`](https://github.com/ManuelLerchner/isar-tools/blob/main/docs/FORMATTER.md).
91
+
92
+ As a git hook with [lefthook](https://github.com/evilmartians/lefthook):
93
+
94
+ ```yaml
95
+ pre-commit:
96
+ jobs:
97
+ - name: isar-fmt
98
+ glob: "*.thy"
99
+ run: isar fmt {staged_files}
100
+ stage_fixed: true
101
+ ```
102
+
103
+ ### Checks
104
+
105
+ ```sh
106
+ isar check # groups project, proofs, and syntax
107
+ isar check symbols src/ # non-ASCII characters outside comments
108
+ isar check docs src/ # theories, headings, locales, classes without a text block
109
+ isar check locales -d ~/afp/thys # free variables in locale headers
110
+ isar check --ignore oops --format json
111
+ ```
112
+
113
+ | Group | Codes |
114
+ | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
115
+ | `project` | `root-syntax`, `duplicate-session`, `missing-theory`, `missing-directory`, `missing-document-file`, `duplicate-theory-name`, `unreached-theory` |
116
+ | `proofs` | `unfinished-proof` (`sorry`, `\<proof>`), `oops`, `unclosed-proof` |
117
+ | `syntax` | `lexical-error`, `document-argument` |
118
+ | `symbols` | `non-ascii` (opt-in) |
119
+ | `docs` | `undocumented-theory`, `undocumented-heading`, `undocumented-locale`, `undocumented-class` (opt-in) |
120
+ | `locales` | `locale-free-variable` (opt-in, heuristic) |
121
+
122
+ Project checks run for directory arguments only. `isar check --help` describes
123
+ every code.
124
+
125
+ `locales` is heuristic. Inside the terms of a `locale` or `context` header,
126
+ Isabelle reads an unknown identifier as a free variable and generalizes over
127
+ it, so an assumption citing a deleted or misspelt constant still builds. The
128
+ check reports identifiers that are no parameter of the header (its `fixes`,
129
+ `for` clause, `defines`, or those of the locales it extends, resolved through
130
+ imports), not bound in the term, and not used anywhere else in the project or
131
+ in the `-d` theories it imports. Only names of at least four characters with an
132
+ underscore (or `\<^sub>`) are reported; `--allow NAME` (repeatable) accepts a
133
+ name. Inner syntax is approximated lexically: see `isar_tools/checks/locales.py`.
134
+
135
+ ### Commands of other sessions
136
+
137
+ Whether a word is an Isar command depends on the theories a file imports. The
138
+ built-in table covers Pure and HOL, and commands declared in the theory headers
139
+ of the project are found automatically. For commands of other projects, such as
140
+ AFP entries, pass their directory with `-d`, as with `isabelle build -d`:
141
+
142
+ ```sh
143
+ isar check -d ~/afp/thys .
144
+ isar project hierarchy --root numeric_domain -d ~/afp/thys --format json
145
+ ```
146
+
147
+ ### Names
148
+
149
+ ```sh
150
+ isar project names --kind locale # every locale, as Theory.locale
151
+ isar project names --format markdown > NAMES.md # an index with docstrings
152
+ isar project names --name Foo.loc.bar_lemma # exit 1 if no such declaration
153
+ ```
154
+
155
+ Names are qualified as Isabelle renders them: a lemma inside `context loc` is
156
+ `Theory.loc.name`. With `--name`, a qualifier naming the wrong scope does not
157
+ match, and the error suggests the names that exist, so links into rendered
158
+ theories can be checked without building them. The docstring is a `text` block
159
+ directly before the declaration.
160
+
161
+ ### Quoting declarations
162
+
163
+ ```sh
164
+ isar project extract combine_env locale_name.lemma_name
165
+ isar project extract --manifest snippets.toml --out generated/ --write # regenerate
166
+ isar project extract --manifest snippets.toml --out generated/ --check # diff; exit 1 on drift
167
+ ```
168
+
169
+ A name is `name`, `locale.name`, `Theory.name`, or `Theory.locale.name`, and
170
+ must identify one declaration; its source is the command and, for a goal, its
171
+ proof. A manifest lists names as TOML tables, so a document that quotes a
172
+ definition fails its check when the definition changes or is renamed:
173
+
174
+ ```toml
175
+ [snippets.combine_env]
176
+ why = "shown in chapter 3" # free text, ignored
177
+ [snippets.succ_pos]
178
+ file = "src/B.thy" # choose between declarations of the same name
179
+ ```
180
+
181
+ ### Statistics
182
+
183
+ ```sh
184
+ isar stats # sessions, then the largest theories
185
+ isar stats proofs --top 10 # the longest proofs
186
+ isar stats style --max-line-length 100 # long theories, long lines, sorry, watched methods
187
+ isar stats build build.log --budget HOL-Library=0
188
+ ```
189
+
190
+ ## Limits
191
+
192
+ - Nothing runs Isabelle, so nothing is type-checked or proved. A formatted file
193
+ is guaranteed to contain the same tokens; building it is the project's CI's
194
+ job.
195
+ - The built-in command table is hand-written. A command of a session that is
196
+ neither built in nor passed with `-d` is read as part of the previous command.
197
+ - `project extract` finds names that commands declare; derived names
198
+ (`foo_def`, `foo.simps`) and names made by interpretations are not found.
199
+ - `project hierarchy` follows declared parents, not `sublocale`, `subclass`, or
200
+ `interpretation`.
201
+ - `stats build` reads one log line format, observed in Isabelle2025 logs.
202
+
203
+ ## Development
204
+
205
+ Requires [pixi](https://pixi.sh).
206
+
207
+ ```sh
208
+ pixi run pre-commit-install # git hooks
209
+ pixi run test
210
+ pixi run coverage # 100% line and branch coverage required
211
+ pixi run typecheck # pyright, strict
212
+ pixi run lint
213
+ pixi run isar --help
214
+ pixi run demo # re-record docs/demo/demo.gif with VHS
215
+ pixi run demo-check # run the demo commands without recording
216
+ ISAR_CORPUS=~/afp/thys pixi run corpus # integration tests over real projects
217
+ ```
218
+
219
+ Expected output lives in golden files under `tests/golden/` and
220
+ `tests/formatter/`. `UPDATE_GOLDEN=1 pixi run test` rewrites them, so a change in
221
+ output shows up as a diff in review.
222
+
223
+ The demo GIF is generated by [VHS](https://github.com/charmbracelet/vhs) from
224
+ [`docs/demo/demo.tape`](https://github.com/ManuelLerchner/isar-tools/blob/main/docs/demo/demo.tape), which runs the real commands on the
225
+ small project in [`docs/demo/project`](https://github.com/ManuelLerchner/isar-tools/tree/main/docs/demo/project). The `demo` task uses
226
+ its own pixi environment, so VHS, ttyd, and ffmpeg stay out of the default one.
227
+
228
+ ## License
229
+
230
+ MIT
@@ -0,0 +1,209 @@
1
+ # isar-tools
2
+
3
+ [![CI Status][ci-badge]][ci]
4
+ [![License][license-badge]][license]
5
+
6
+ [ci-badge]: https://img.shields.io/github/actions/workflow/status/ManuelLerchner/isar-tools/ci.yml?branch=main&style=flat-square&label=CI
7
+ [ci]: https://github.com/ManuelLerchner/isar-tools/actions/workflows/ci.yml
8
+ [license-badge]: https://img.shields.io/github/license/ManuelLerchner/isar-tools?style=flat-square
9
+ [license]: https://github.com/ManuelLerchner/isar-tools/blob/main/LICENSE
10
+
11
+ Source tooling for Isabelle/Isar projects: a formatter, project and source
12
+ checks, and statistics. Pure Python. Works on `.thy` and `ROOT` files without
13
+ running Isabelle.
14
+
15
+ ![Terminal recording: isar stats prints session and theory tables for a small
16
+ demo project, isar check reports an unfinished proof (sorry), isar fmt --diff
17
+ indents a proof and removes trailing whitespace and extra blank lines, and isar
18
+ project graph prints the theory import graph.](https://raw.githubusercontent.com/ManuelLerchner/isar-tools/main/docs/demo/demo.gif)
19
+
20
+ Status: pre-alpha. See [`CHANGELOG.md`](https://github.com/ManuelLerchner/isar-tools/blob/main/CHANGELOG.md) and
21
+ [`docs/PLAN.md`](https://github.com/ManuelLerchner/isar-tools/blob/main/docs/PLAN.md).
22
+
23
+ ## Install
24
+
25
+ ```sh
26
+ pip install isar-tools # or: uv tool install isar-tools, pipx install isar-tools
27
+ isar --help
28
+ ```
29
+
30
+ Python 3.11 or newer; no runtime dependencies. A conda-forge package
31
+ (`pixi global install isar-tools`) follows the first PyPI release.
32
+
33
+ ## Commands
34
+
35
+ | Command | What it does |
36
+ | ---------------------------------------- | ------------------------------------------------------------------------------------------ |
37
+ | `isar fmt [PATH...]` | Format theories: indentation, trailing whitespace, blank lines, and optional line wrapping |
38
+ | `isar check [GROUP] [PATH...]` | Report problems in ROOT files, proofs, syntax, symbols, docs, and locales |
39
+ | `isar stats [VIEW] [PATH...]` | Size, proof, and command statistics |
40
+ | `isar stats build BUILD_LOG` | Where theory elaboration time went in an `isabelle build -v` log |
41
+ | `isar project sessions\|theories\|graph` | Sessions, theories, and the session or theory import graph |
42
+ | `isar project hierarchy` | Class and locale declarations: parents, parameters, assumptions |
43
+ | `isar project names` | Named declarations: qualified name, kind, location, docstring; a Markdown index |
44
+ | `isar project extract NAME...` | The source of a declaration by name; keeps quoted declarations in sync with a manifest |
45
+ | `isar symbols normalize PATH...` | Rewrite symbols as `\<name>`, or as Unicode |
46
+
47
+ A path is a project directory, read like `isabelle build -D` (its `ROOT`, and
48
+ `ROOTS` recursively), or a `.thy` file. Commands that read a project default to the current
49
+ directory.
50
+
51
+ Exit status: `0` success, `1` findings or differences, `2` invalid invocation or
52
+ unreadable input. Data goes to stdout, diagnostics to stderr. JSON and CSV output
53
+ use stable snake_case keys and are never coloured.
54
+
55
+ ### Formatting
56
+
57
+ ```sh
58
+ isar fmt # format every .thy below the current directory
59
+ isar fmt --check # list files that would change; exit 1 if any
60
+ isar fmt --diff Foo.thy # show the change without writing
61
+ isar fmt --max-line-length 100 # also wrap long lines
62
+ isar fmt - # stdin to stdout, for editors
63
+ ```
64
+
65
+ The formatter only changes layout. It never changes a token, never touches the
66
+ space between two tokens on a line, and never rewrites strings, cartouches,
67
+ comments, or ML. By default it only indents lines that are too shallow for their
68
+ proof structure; `--normalize` sets indentation exactly. See
69
+ [`docs/FORMATTER.md`](https://github.com/ManuelLerchner/isar-tools/blob/main/docs/FORMATTER.md).
70
+
71
+ As a git hook with [lefthook](https://github.com/evilmartians/lefthook):
72
+
73
+ ```yaml
74
+ pre-commit:
75
+ jobs:
76
+ - name: isar-fmt
77
+ glob: "*.thy"
78
+ run: isar fmt {staged_files}
79
+ stage_fixed: true
80
+ ```
81
+
82
+ ### Checks
83
+
84
+ ```sh
85
+ isar check # groups project, proofs, and syntax
86
+ isar check symbols src/ # non-ASCII characters outside comments
87
+ isar check docs src/ # theories, headings, locales, classes without a text block
88
+ isar check locales -d ~/afp/thys # free variables in locale headers
89
+ isar check --ignore oops --format json
90
+ ```
91
+
92
+ | Group | Codes |
93
+ | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
94
+ | `project` | `root-syntax`, `duplicate-session`, `missing-theory`, `missing-directory`, `missing-document-file`, `duplicate-theory-name`, `unreached-theory` |
95
+ | `proofs` | `unfinished-proof` (`sorry`, `\<proof>`), `oops`, `unclosed-proof` |
96
+ | `syntax` | `lexical-error`, `document-argument` |
97
+ | `symbols` | `non-ascii` (opt-in) |
98
+ | `docs` | `undocumented-theory`, `undocumented-heading`, `undocumented-locale`, `undocumented-class` (opt-in) |
99
+ | `locales` | `locale-free-variable` (opt-in, heuristic) |
100
+
101
+ Project checks run for directory arguments only. `isar check --help` describes
102
+ every code.
103
+
104
+ `locales` is heuristic. Inside the terms of a `locale` or `context` header,
105
+ Isabelle reads an unknown identifier as a free variable and generalizes over
106
+ it, so an assumption citing a deleted or misspelt constant still builds. The
107
+ check reports identifiers that are no parameter of the header (its `fixes`,
108
+ `for` clause, `defines`, or those of the locales it extends, resolved through
109
+ imports), not bound in the term, and not used anywhere else in the project or
110
+ in the `-d` theories it imports. Only names of at least four characters with an
111
+ underscore (or `\<^sub>`) are reported; `--allow NAME` (repeatable) accepts a
112
+ name. Inner syntax is approximated lexically: see `isar_tools/checks/locales.py`.
113
+
114
+ ### Commands of other sessions
115
+
116
+ Whether a word is an Isar command depends on the theories a file imports. The
117
+ built-in table covers Pure and HOL, and commands declared in the theory headers
118
+ of the project are found automatically. For commands of other projects, such as
119
+ AFP entries, pass their directory with `-d`, as with `isabelle build -d`:
120
+
121
+ ```sh
122
+ isar check -d ~/afp/thys .
123
+ isar project hierarchy --root numeric_domain -d ~/afp/thys --format json
124
+ ```
125
+
126
+ ### Names
127
+
128
+ ```sh
129
+ isar project names --kind locale # every locale, as Theory.locale
130
+ isar project names --format markdown > NAMES.md # an index with docstrings
131
+ isar project names --name Foo.loc.bar_lemma # exit 1 if no such declaration
132
+ ```
133
+
134
+ Names are qualified as Isabelle renders them: a lemma inside `context loc` is
135
+ `Theory.loc.name`. With `--name`, a qualifier naming the wrong scope does not
136
+ match, and the error suggests the names that exist, so links into rendered
137
+ theories can be checked without building them. The docstring is a `text` block
138
+ directly before the declaration.
139
+
140
+ ### Quoting declarations
141
+
142
+ ```sh
143
+ isar project extract combine_env locale_name.lemma_name
144
+ isar project extract --manifest snippets.toml --out generated/ --write # regenerate
145
+ isar project extract --manifest snippets.toml --out generated/ --check # diff; exit 1 on drift
146
+ ```
147
+
148
+ A name is `name`, `locale.name`, `Theory.name`, or `Theory.locale.name`, and
149
+ must identify one declaration; its source is the command and, for a goal, its
150
+ proof. A manifest lists names as TOML tables, so a document that quotes a
151
+ definition fails its check when the definition changes or is renamed:
152
+
153
+ ```toml
154
+ [snippets.combine_env]
155
+ why = "shown in chapter 3" # free text, ignored
156
+ [snippets.succ_pos]
157
+ file = "src/B.thy" # choose between declarations of the same name
158
+ ```
159
+
160
+ ### Statistics
161
+
162
+ ```sh
163
+ isar stats # sessions, then the largest theories
164
+ isar stats proofs --top 10 # the longest proofs
165
+ isar stats style --max-line-length 100 # long theories, long lines, sorry, watched methods
166
+ isar stats build build.log --budget HOL-Library=0
167
+ ```
168
+
169
+ ## Limits
170
+
171
+ - Nothing runs Isabelle, so nothing is type-checked or proved. A formatted file
172
+ is guaranteed to contain the same tokens; building it is the project's CI's
173
+ job.
174
+ - The built-in command table is hand-written. A command of a session that is
175
+ neither built in nor passed with `-d` is read as part of the previous command.
176
+ - `project extract` finds names that commands declare; derived names
177
+ (`foo_def`, `foo.simps`) and names made by interpretations are not found.
178
+ - `project hierarchy` follows declared parents, not `sublocale`, `subclass`, or
179
+ `interpretation`.
180
+ - `stats build` reads one log line format, observed in Isabelle2025 logs.
181
+
182
+ ## Development
183
+
184
+ Requires [pixi](https://pixi.sh).
185
+
186
+ ```sh
187
+ pixi run pre-commit-install # git hooks
188
+ pixi run test
189
+ pixi run coverage # 100% line and branch coverage required
190
+ pixi run typecheck # pyright, strict
191
+ pixi run lint
192
+ pixi run isar --help
193
+ pixi run demo # re-record docs/demo/demo.gif with VHS
194
+ pixi run demo-check # run the demo commands without recording
195
+ ISAR_CORPUS=~/afp/thys pixi run corpus # integration tests over real projects
196
+ ```
197
+
198
+ Expected output lives in golden files under `tests/golden/` and
199
+ `tests/formatter/`. `UPDATE_GOLDEN=1 pixi run test` rewrites them, so a change in
200
+ output shows up as a diff in review.
201
+
202
+ The demo GIF is generated by [VHS](https://github.com/charmbracelet/vhs) from
203
+ [`docs/demo/demo.tape`](https://github.com/ManuelLerchner/isar-tools/blob/main/docs/demo/demo.tape), which runs the real commands on the
204
+ small project in [`docs/demo/project`](https://github.com/ManuelLerchner/isar-tools/tree/main/docs/demo/project). The `demo` task uses
205
+ its own pixi environment, so VHS, ttyd, and ffmpeg stay out of the default one.
206
+
207
+ ## License
208
+
209
+ MIT