badness 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 (191) hide show
  1. badness-0.1.0/.gitattributes +17 -0
  2. badness-0.1.0/.github/FUNDING.yml +1 -0
  3. badness-0.1.0/.github/dependabot.yml +10 -0
  4. badness-0.1.0/.github/workflows/build-and-test.yml +108 -0
  5. badness-0.1.0/.github/workflows/docs.yml +78 -0
  6. badness-0.1.0/.github/workflows/lint.yml +58 -0
  7. badness-0.1.0/.github/workflows/packages.yml +180 -0
  8. badness-0.1.0/.github/workflows/publish-crates.yml +35 -0
  9. badness-0.1.0/.github/workflows/publish-pypi.yml +127 -0
  10. badness-0.1.0/.gitignore +348 -0
  11. badness-0.1.0/.versionary-manifest.json +18 -0
  12. badness-0.1.0/AGENTS.md +197 -0
  13. badness-0.1.0/CHANGELOG.md +31 -0
  14. badness-0.1.0/CLAUDE.md +1 -0
  15. badness-0.1.0/Cargo.lock +1315 -0
  16. badness-0.1.0/Cargo.toml +47 -0
  17. badness-0.1.0/LICENSE +21 -0
  18. badness-0.1.0/PARSE_COMPAT.md +21 -0
  19. badness-0.1.0/PKG-INFO +99 -0
  20. badness-0.1.0/README.md +73 -0
  21. badness-0.1.0/TODO.md +163 -0
  22. badness-0.1.0/Taskfile.yml +129 -0
  23. badness-0.1.0/branding/_common.tex +102 -0
  24. badness-0.1.0/branding/logo.pdf +0 -0
  25. badness-0.1.0/branding/logo.png +0 -0
  26. badness-0.1.0/branding/logo.svg +10 -0
  27. badness-0.1.0/branding/logo.tex +47 -0
  28. badness-0.1.0/branding/og.pdf +0 -0
  29. badness-0.1.0/branding/og.png +0 -0
  30. badness-0.1.0/branding/og.tex +76 -0
  31. badness-0.1.0/branding/wordmark.pdf +0 -0
  32. badness-0.1.0/branding/wordmark.png +0 -0
  33. badness-0.1.0/branding/wordmark.tex +41 -0
  34. badness-0.1.0/data/signatures.json +70 -0
  35. badness-0.1.0/deny.toml +242 -0
  36. badness-0.1.0/devenv.lock +146 -0
  37. badness-0.1.0/devenv.nix +54 -0
  38. badness-0.1.0/devenv.yaml +13 -0
  39. badness-0.1.0/docs/book.toml +18 -0
  40. badness-0.1.0/docs/src/SUMMARY.md +21 -0
  41. badness-0.1.0/docs/src/changelog.md +12 -0
  42. badness-0.1.0/docs/src/contributing.md +33 -0
  43. badness-0.1.0/docs/src/guide/editor-setup.md +39 -0
  44. badness-0.1.0/docs/src/guide/formatting.md +37 -0
  45. badness-0.1.0/docs/src/guide/getting-started.md +62 -0
  46. badness-0.1.0/docs/src/guide/installation.md +32 -0
  47. badness-0.1.0/docs/src/guide/linting.md +22 -0
  48. badness-0.1.0/docs/src/images/logo.svg +10 -0
  49. badness-0.1.0/docs/src/index.md +34 -0
  50. badness-0.1.0/docs/src/reference/cli.md +56 -0
  51. badness-0.1.0/docs/src/reference/wrap-modes.md +28 -0
  52. badness-0.1.0/docs/theme/favicon.png +0 -0
  53. badness-0.1.0/docs/theme/favicon.svg +10 -0
  54. badness-0.1.0/panache.toml +10 -0
  55. badness-0.1.0/pyproject.toml +35 -0
  56. badness-0.1.0/src/ast.rs +174 -0
  57. badness-0.1.0/src/formatter/check.rs +80 -0
  58. badness-0.1.0/src/formatter/context.rs +41 -0
  59. badness-0.1.0/src/formatter/core.rs +754 -0
  60. badness-0.1.0/src/formatter/ir.rs +315 -0
  61. badness-0.1.0/src/formatter/printer.rs +990 -0
  62. badness-0.1.0/src/formatter/style.rs +48 -0
  63. badness-0.1.0/src/formatter.rs +18 -0
  64. badness-0.1.0/src/incremental.rs +377 -0
  65. badness-0.1.0/src/lib.rs +14 -0
  66. badness-0.1.0/src/linter/diagnostic.rs +48 -0
  67. badness-0.1.0/src/linter/render.rs +179 -0
  68. badness-0.1.0/src/linter.rs +14 -0
  69. badness-0.1.0/src/lsp/task_pool.rs +145 -0
  70. badness-0.1.0/src/lsp.rs +857 -0
  71. badness-0.1.0/src/main.rs +279 -0
  72. badness-0.1.0/src/parser/core.rs +77 -0
  73. badness-0.1.0/src/parser/events.rs +18 -0
  74. badness-0.1.0/src/parser/grammar.rs +619 -0
  75. badness-0.1.0/src/parser/lexer.rs +349 -0
  76. badness-0.1.0/src/parser/tree_builder.rs +26 -0
  77. badness-0.1.0/src/parser.rs +15 -0
  78. badness-0.1.0/src/project/graph.rs +408 -0
  79. badness-0.1.0/src/project/include.rs +305 -0
  80. badness-0.1.0/src/project.rs +24 -0
  81. badness-0.1.0/src/semantic/builder.rs +110 -0
  82. badness-0.1.0/src/semantic/define.rs +320 -0
  83. badness-0.1.0/src/semantic/label.rs +86 -0
  84. badness-0.1.0/src/semantic/signature.rs +435 -0
  85. badness-0.1.0/src/semantic/xparse.rs +307 -0
  86. badness-0.1.0/src/semantic.rs +207 -0
  87. badness-0.1.0/src/syntax.rs +86 -0
  88. badness-0.1.0/src/text/line_index.rs +214 -0
  89. badness-0.1.0/src/text.rs +5 -0
  90. badness-0.1.0/tests/corpus/basic.tex +11 -0
  91. badness-0.1.0/tests/corpus/edge.tex +11 -0
  92. badness-0.1.0/tests/corpus/environments.tex +9 -0
  93. badness-0.1.0/tests/corpus/math.tex +12 -0
  94. badness-0.1.0/tests/fixtures/formatter/collapse_blank_lines/expected.tex +5 -0
  95. badness-0.1.0/tests/fixtures/formatter/collapse_blank_lines/input.tex +10 -0
  96. badness-0.1.0/tests/fixtures/formatter/environment_argument_glued/expected.tex +4 -0
  97. badness-0.1.0/tests/fixtures/formatter/environment_argument_glued/input.tex +5 -0
  98. badness-0.1.0/tests/fixtures/formatter/environment_begin_arguments/expected.tex +3 -0
  99. badness-0.1.0/tests/fixtures/formatter/environment_begin_arguments/input.tex +3 -0
  100. badness-0.1.0/tests/fixtures/formatter/environment_blank_lines_in_body/expected.tex +5 -0
  101. badness-0.1.0/tests/fixtures/formatter/environment_blank_lines_in_body/input.tex +7 -0
  102. badness-0.1.0/tests/fixtures/formatter/environment_indents_body/expected.tex +4 -0
  103. badness-0.1.0/tests/fixtures/formatter/environment_indents_body/input.tex +4 -0
  104. badness-0.1.0/tests/fixtures/formatter/environment_reindents/expected.tex +4 -0
  105. badness-0.1.0/tests/fixtures/formatter/environment_reindents/input.tex +4 -0
  106. badness-0.1.0/tests/fixtures/formatter/environment_user_defined_glued/expected.tex +4 -0
  107. badness-0.1.0/tests/fixtures/formatter/environment_user_defined_glued/input.tex +5 -0
  108. badness-0.1.0/tests/fixtures/formatter/environment_xparse_glued/expected.tex +4 -0
  109. badness-0.1.0/tests/fixtures/formatter/environment_xparse_glued/input.tex +6 -0
  110. badness-0.1.0/tests/fixtures/formatter/final_newline_added/expected.tex +1 -0
  111. badness-0.1.0/tests/fixtures/formatter/final_newline_added/input.tex +1 -0
  112. badness-0.1.0/tests/fixtures/formatter/group_indents_body/expected.tex +3 -0
  113. badness-0.1.0/tests/fixtures/formatter/group_indents_body/input.tex +3 -0
  114. badness-0.1.0/tests/fixtures/formatter/group_reindents/expected.tex +4 -0
  115. badness-0.1.0/tests/fixtures/formatter/group_reindents/input.tex +4 -0
  116. badness-0.1.0/tests/fixtures/formatter/group_single_line_stays_inline/expected.tex +1 -0
  117. badness-0.1.0/tests/fixtures/formatter/group_single_line_stays_inline/input.tex +1 -0
  118. badness-0.1.0/tests/fixtures/formatter/nested_environments/expected.tex +6 -0
  119. badness-0.1.0/tests/fixtures/formatter/nested_environments/input.tex +6 -0
  120. badness-0.1.0/tests/fixtures/formatter/nested_groups/expected.tex +6 -0
  121. badness-0.1.0/tests/fixtures/formatter/nested_groups/input.tex +6 -0
  122. badness-0.1.0/tests/fixtures/formatter/optional_indents_body/expected.tex +4 -0
  123. badness-0.1.0/tests/fixtures/formatter/optional_indents_body/input.tex +4 -0
  124. badness-0.1.0/tests/fixtures/formatter/protected_comment_trailing_space/expected.tex +2 -0
  125. badness-0.1.0/tests/fixtures/formatter/protected_comment_trailing_space/input.tex +2 -0
  126. badness-0.1.0/tests/fixtures/formatter/protected_verbatim/expected.tex +7 -0
  127. badness-0.1.0/tests/fixtures/formatter/protected_verbatim/input.tex +10 -0
  128. badness-0.1.0/tests/fixtures/formatter/reflow_comment_ends_line/expected.tex +2 -0
  129. badness-0.1.0/tests/fixtures/formatter/reflow_comment_ends_line/input.tex +2 -0
  130. badness-0.1.0/tests/fixtures/formatter/reflow_forced_break/expected.tex +2 -0
  131. badness-0.1.0/tests/fixtures/formatter/reflow_forced_break/input.tex +1 -0
  132. badness-0.1.0/tests/fixtures/formatter/reflow_forced_break_with_optarg/expected.tex +3 -0
  133. badness-0.1.0/tests/fixtures/formatter/reflow_forced_break_with_optarg/input.tex +1 -0
  134. badness-0.1.0/tests/fixtures/formatter/reflow_in_environment/expected.tex +4 -0
  135. badness-0.1.0/tests/fixtures/formatter/reflow_in_environment/input.tex +3 -0
  136. badness-0.1.0/tests/fixtures/formatter/reflow_join_short/expected.tex +1 -0
  137. badness-0.1.0/tests/fixtures/formatter/reflow_join_short/input.tex +3 -0
  138. badness-0.1.0/tests/fixtures/formatter/reflow_non_prose_preserved/expected.tex +3 -0
  139. badness-0.1.0/tests/fixtures/formatter/reflow_non_prose_preserved/input.tex +3 -0
  140. badness-0.1.0/tests/fixtures/formatter/reflow_prose_arg_blank_line/expected.tex +8 -0
  141. badness-0.1.0/tests/fixtures/formatter/reflow_prose_arg_blank_line/input.tex +3 -0
  142. badness-0.1.0/tests/fixtures/formatter/reflow_prose_arg_joins_short/expected.tex +1 -0
  143. badness-0.1.0/tests/fixtures/formatter/reflow_prose_arg_joins_short/input.tex +2 -0
  144. badness-0.1.0/tests/fixtures/formatter/reflow_prose_arg_nested_in_paragraph/expected.tex +7 -0
  145. badness-0.1.0/tests/fixtures/formatter/reflow_prose_arg_nested_in_paragraph/input.tex +1 -0
  146. badness-0.1.0/tests/fixtures/formatter/reflow_prose_arg_optional_omitted/expected.tex +4 -0
  147. badness-0.1.0/tests/fixtures/formatter/reflow_prose_arg_optional_omitted/input.tex +1 -0
  148. badness-0.1.0/tests/fixtures/formatter/reflow_prose_arg_wraps/expected.tex +5 -0
  149. badness-0.1.0/tests/fixtures/formatter/reflow_prose_arg_wraps/input.tex +1 -0
  150. badness-0.1.0/tests/fixtures/formatter/reflow_tie_no_break/expected.tex +3 -0
  151. badness-0.1.0/tests/fixtures/formatter/reflow_tie_no_break/input.tex +1 -0
  152. badness-0.1.0/tests/fixtures/formatter/reflow_wrap_to_width/expected.tex +5 -0
  153. badness-0.1.0/tests/fixtures/formatter/reflow_wrap_to_width/input.tex +1 -0
  154. badness-0.1.0/tests/fixtures/formatter/trailing_whitespace_only/expected.tex +3 -0
  155. badness-0.1.0/tests/fixtures/formatter/trailing_whitespace_only/input.tex +3 -0
  156. badness-0.1.0/tests/fixtures/formatter/verbatim_in_environment/expected.tex +5 -0
  157. badness-0.1.0/tests/fixtures/formatter/verbatim_in_environment/input.tex +5 -0
  158. badness-0.1.0/tests/fixtures/formatter/whitespace_trailing_and_blank_lines/expected.tex +3 -0
  159. badness-0.1.0/tests/fixtures/formatter/whitespace_trailing_and_blank_lines/input.tex +5 -0
  160. badness-0.1.0/tests/format.rs +314 -0
  161. badness-0.1.0/tests/incremental.rs +122 -0
  162. badness-0.1.0/tests/lexer_snapshots.rs +33 -0
  163. badness-0.1.0/tests/lsp.rs +351 -0
  164. badness-0.1.0/tests/parse_compat.rs +278 -0
  165. badness-0.1.0/tests/parse_compat_allowlist.toml +12 -0
  166. badness-0.1.0/tests/parse_oracle.rs +51 -0
  167. badness-0.1.0/tests/parser.rs +162 -0
  168. badness-0.1.0/tests/project.rs +137 -0
  169. badness-0.1.0/tests/roundtrip.rs +48 -0
  170. badness-0.1.0/tests/semantic.rs +102 -0
  171. badness-0.1.0/tests/snapshots/format__format_output_snapshot.snap +7 -0
  172. badness-0.1.0/tests/snapshots/lexer_snapshots__lex_command_with_args.snap +11 -0
  173. badness-0.1.0/tests/snapshots/lexer_snapshots__lex_comment_and_paragraph.snap +10 -0
  174. badness-0.1.0/tests/snapshots/lexer_snapshots__lex_environment.snap +23 -0
  175. badness-0.1.0/tests/snapshots/lexer_snapshots__lex_inline_math.snap +15 -0
  176. badness-0.1.0/tests/snapshots/parser__command_with_required_and_optional_args.snap +16 -0
  177. badness-0.1.0/tests/snapshots/parser__display_math_dollars.snap +16 -0
  178. badness-0.1.0/tests/snapshots/parser__environment_mismatch_recovers.snap +28 -0
  179. badness-0.1.0/tests/snapshots/parser__environment_with_body.snap +26 -0
  180. badness-0.1.0/tests/snapshots/parser__inline_and_display_math.snap +23 -0
  181. badness-0.1.0/tests/snapshots/parser__inline_verb_is_a_single_token.snap +11 -0
  182. badness-0.1.0/tests/snapshots/parser__line_break_does_not_cross_trivia_for_its_optional.snap +16 -0
  183. badness-0.1.0/tests/snapshots/parser__line_break_groups_star_and_optional_length.snap +40 -0
  184. badness-0.1.0/tests/snapshots/parser__makeatletter_control_word_with_at.snap +12 -0
  185. badness-0.1.0/tests/snapshots/parser__nested_groups.snap +17 -0
  186. badness-0.1.0/tests/snapshots/parser__paragraphs_split_on_blank_lines.snap +19 -0
  187. badness-0.1.0/tests/snapshots/parser__unclosed_environment_at_eof.snap +17 -0
  188. badness-0.1.0/tests/snapshots/parser__unmatched_closing_brace.snap +12 -0
  189. badness-0.1.0/tests/snapshots/parser__verbatim_environment_is_opaque.snap +20 -0
  190. badness-0.1.0/tests/support/parse_skeleton.rs +347 -0
  191. badness-0.1.0/versionary.jsonc +8 -0
@@ -0,0 +1,17 @@
1
+ # Let Git auto-detect text files
2
+ * text=auto
3
+
4
+ # LaTeX fixtures and corpus must use LF so formatter output (always LF) and
5
+ # losslessness/snapshot assertions match on every platform, including Windows.
6
+ tests/fixtures/**/*.tex eol=lf
7
+ tests/corpus/** eol=lf
8
+
9
+ # Snapshot files should always use LF
10
+ tests/**/snapshots/**/* eol=lf
11
+ tests/snapshots/**/* eol=lf
12
+
13
+ # Preserve line-ending fixtures as-is (CRLF/LF) once they exist.
14
+ tests/fixtures/**/crlf_*/* -text
15
+ tests/fixtures/**/lf_*/* -text
16
+ tests/fixtures/**/*_crlf_*/* -text
17
+ tests/fixtures/**/*_lf_*/* -text
@@ -0,0 +1 @@
1
+ github: jolars
@@ -0,0 +1,10 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "github-actions"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ - package-ecosystem: "cargo"
8
+ directory: "/"
9
+ schedule:
10
+ interval: "weekly"
@@ -0,0 +1,108 @@
1
+ name: Build and Test
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ workflow_call:
6
+ pull_request:
7
+ push:
8
+ branches:
9
+ - main
10
+ schedule:
11
+ - cron: "0 3 * * *"
12
+
13
+ env:
14
+ CARGO_TERM_COLOR: always
15
+
16
+ jobs:
17
+ test:
18
+ if: github.event_name != 'schedule'
19
+ name: Test
20
+ runs-on: ${{ matrix.os }}
21
+ strategy:
22
+ matrix:
23
+ os: [ubuntu-latest, windows-latest, macos-latest]
24
+ rust: [stable]
25
+
26
+ steps:
27
+ - uses: actions/checkout@v6
28
+
29
+ - name: Install Rust
30
+ uses: dtolnay/rust-toolchain@master
31
+ with:
32
+ toolchain: ${{ matrix.rust }}
33
+ targets: wasm32-unknown-unknown
34
+
35
+ - name: Cache cargo registry
36
+ uses: actions/cache@v5
37
+ with:
38
+ path: |
39
+ ~/.cargo/registry
40
+ ~/.cargo/git
41
+ target
42
+ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
43
+ restore-keys: |
44
+ ${{ runner.os }}-cargo-
45
+
46
+ - name: Build
47
+ run: cargo build --verbose
48
+
49
+ - name: Run tests
50
+ run: cargo test --verbose
51
+
52
+ cargo-audit:
53
+ name: Cargo Audit
54
+ runs-on: ubuntu-latest
55
+ permissions:
56
+ issues: write
57
+ checks: write
58
+ steps:
59
+ - uses: actions/checkout@v6
60
+ - uses: rustsec/audit-check@v2
61
+ with:
62
+ token: ${{ secrets.GITHUB_TOKEN }}
63
+
64
+ cargo-deny:
65
+ name: Cargo Deny
66
+ runs-on: ubuntu-latest
67
+ permissions:
68
+ issues: write
69
+ checks: write
70
+ steps:
71
+ - uses: actions/checkout@v6
72
+ - uses: EmbarkStudios/cargo-deny-action@v2
73
+
74
+ versionary:
75
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
76
+ needs: [test, cargo-audit, cargo-deny]
77
+ runs-on: ubuntu-latest
78
+ permissions:
79
+ contents: write
80
+ pull-requests: write
81
+ issues: write
82
+ outputs:
83
+ release_created: ${{ steps.versionary.outputs.release_created }}
84
+ tag_name: ${{ steps.versionary.outputs.tag_name }}
85
+ steps:
86
+ - uses: actions/checkout@v6
87
+ with:
88
+ fetch-depth: 0
89
+ fetch-tags: true
90
+ token: ${{ secrets.RELEASE_TOKEN }}
91
+
92
+ - uses: dtolnay/rust-toolchain@stable
93
+
94
+ - id: versionary
95
+ uses: jolars/versionary@v0
96
+ with:
97
+ github-token: ${{ secrets.RELEASE_TOKEN }}
98
+
99
+ build-release-assets:
100
+ name: Build & Upload Release Assets
101
+ needs: versionary
102
+ if: ${{ needs.versionary.outputs.release_created == 'true' }}
103
+ uses: ./.github/workflows/packages.yml
104
+ with:
105
+ upload: "true"
106
+ tag: ${{ needs.versionary.outputs.tag_name }}
107
+ permissions:
108
+ contents: write
@@ -0,0 +1,78 @@
1
+ name: Documentation
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ workflow_call:
6
+ pull_request:
7
+ push:
8
+ branches:
9
+ - main
10
+
11
+ concurrency:
12
+ group: ${{ github.workflow }}-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ env:
16
+ CARGO_TERM_COLOR: always
17
+
18
+ jobs:
19
+ build:
20
+ name: Build Documentation
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - name: Checkout
24
+ uses: actions/checkout@v6
25
+
26
+ - name: Install Rust
27
+ uses: dtolnay/rust-toolchain@master
28
+ with:
29
+ toolchain: stable
30
+
31
+ - name: Cache cargo dependencies
32
+ uses: actions/cache@v5
33
+ with:
34
+ path: |
35
+ ~/.cargo/registry
36
+ ~/.cargo/git
37
+ docs/doc-utils/target
38
+ key: ${{ runner.os }}-docs-${{ hashFiles('docs/doc-utils/Cargo.lock', 'Cargo.toml') }}
39
+ restore-keys: |
40
+ ${{ runner.os }}-docs-
41
+
42
+ - uses: jontze/action-mdbook@v4
43
+ with:
44
+ token: ${{ secrets.GITHUB_TOKEN }}
45
+
46
+ # Build the version-substitution preprocessor up front so the (optional)
47
+ # plugin actually runs during `mdbook build`. Without this the build still
48
+ # succeeds but ships raw `{{ badness-version }}` markers.
49
+ - name: Build docs preprocessor
50
+ run: cargo build --manifest-path docs/doc-utils/Cargo.toml
51
+
52
+ - name: Build documentation
53
+ run: mdbook build docs
54
+
55
+ - name: Upload Pages artifact
56
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
57
+ uses: actions/upload-pages-artifact@v4
58
+ with:
59
+ path: docs/book
60
+
61
+ deploy:
62
+ name: Deploy to GitHub Pages
63
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
64
+ needs: build
65
+ runs-on: ubuntu-latest
66
+ permissions:
67
+ pages: write
68
+ id-token: write
69
+ environment:
70
+ name: github-pages
71
+ url: ${{ steps.deployment.outputs.page_url }}
72
+ steps:
73
+ - name: Setup Pages
74
+ uses: actions/configure-pages@v6
75
+
76
+ - name: Deploy to GitHub Pages
77
+ id: deployment
78
+ uses: actions/deploy-pages@v5
@@ -0,0 +1,58 @@
1
+ name: Lint
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ workflow_call:
6
+ pull_request:
7
+ push:
8
+ branches:
9
+ - main
10
+
11
+ concurrency:
12
+ group: ${{ github.workflow }}-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ env:
16
+ CARGO_TERM_COLOR: always
17
+
18
+ jobs:
19
+ clippy:
20
+ name: Clippy
21
+ runs-on: ubuntu-latest
22
+
23
+ steps:
24
+ - name: Check out repository
25
+ uses: actions/checkout@v6
26
+
27
+ - name: Install Rust
28
+ uses: dtolnay/rust-toolchain@master
29
+ with:
30
+ components: rustfmt, clippy
31
+ toolchain: stable
32
+
33
+ - name: Cache cargo registry
34
+ uses: actions/cache@v5
35
+ with:
36
+ path: |
37
+ ~/.cargo/registry
38
+ ~/.cargo/git
39
+ target
40
+ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
41
+ restore-keys: |
42
+ ${{ runner.os }}-cargo-
43
+
44
+ - name: Build
45
+ run: cargo build --verbose
46
+
47
+ - name: Run clippy
48
+ run: cargo clippy -- -D warnings
49
+
50
+ - name: Check formatting
51
+ run: cargo fmt -- --check
52
+
53
+ panache:
54
+ name: Panache
55
+ runs-on: ubuntu-latest
56
+ steps:
57
+ - uses: actions/checkout@v6
58
+ - uses: jolars/panache-action@v1
@@ -0,0 +1,180 @@
1
+ name: Build Release Binaries
2
+
3
+ on:
4
+ workflow_call:
5
+ inputs:
6
+ upload:
7
+ description: "Upload to release (requires a release to exist)"
8
+ required: false
9
+ default: "false"
10
+ type: string
11
+ tag:
12
+ description: "Release tag to upload to (only if upload is true)"
13
+ required: false
14
+ type: string
15
+ workflow_dispatch:
16
+ inputs:
17
+ upload:
18
+ description: "Upload to release (requires a release to exist)"
19
+ required: false
20
+ default: "false"
21
+ type: choice
22
+ options:
23
+ - "true"
24
+ - "false"
25
+ tag:
26
+ description: "Release tag to upload to (only if upload is true)"
27
+ required: false
28
+ type: string
29
+
30
+ env:
31
+ CARGO_TERM_COLOR: always
32
+
33
+ jobs:
34
+ build-binaries:
35
+ name: Build ${{ matrix.target }}
36
+ runs-on: ${{ matrix.os }}
37
+ permissions:
38
+ contents: write
39
+ strategy:
40
+ fail-fast: false
41
+ matrix:
42
+ include:
43
+ # Linux x86_64 (glibc) — built with cargo-zigbuild against an old
44
+ # glibc floor so the binary runs on distros older than the runner.
45
+ - target: x86_64-unknown-linux-gnu
46
+ os: ubuntu-latest
47
+ glibc: "2.17"
48
+
49
+ # Linux ARM64 (glibc)
50
+ - target: aarch64-unknown-linux-gnu
51
+ os: ubuntu-24.04-arm
52
+ glibc: "2.17"
53
+
54
+ # Linux x86_64 (musl)
55
+ - target: x86_64-unknown-linux-musl
56
+ os: ubuntu-latest
57
+
58
+ # Linux ARM64 (musl)
59
+ - target: aarch64-unknown-linux-musl
60
+ os: ubuntu-24.04-arm
61
+
62
+ # macOS x86_64
63
+ - target: x86_64-apple-darwin
64
+ os: macos-15-intel
65
+
66
+ # macOS ARM64 (Apple Silicon)
67
+ - target: aarch64-apple-darwin
68
+ os: macos-latest
69
+
70
+ # Windows x86_64
71
+ - target: x86_64-pc-windows-msvc
72
+ os: windows-latest
73
+
74
+ # Windows ARM64
75
+ - target: aarch64-pc-windows-msvc
76
+ os: windows-11-arm
77
+
78
+ steps:
79
+ - uses: actions/checkout@v6
80
+ with:
81
+ ref: ${{ inputs.tag || github.ref }}
82
+
83
+ - name: Install Rust
84
+ uses: dtolnay/rust-toolchain@stable
85
+ with:
86
+ targets: ${{ matrix.target }}
87
+
88
+ - name: Cache Rust artifacts
89
+ uses: Swatinem/rust-cache@v2
90
+ with:
91
+ cache-bin: "false"
92
+ prefix-key: "v1-rust"
93
+
94
+ - name: Install musl toolchain
95
+ if: endsWith(matrix.target, '-unknown-linux-musl')
96
+ run: sudo apt-get update && sudo apt-get install -y musl-tools
97
+
98
+ # Pin the glibc symbol-version floor (link-time only; the binary still
99
+ # uses the host's glibc at runtime) so it runs on distros older than the
100
+ # CI runner.
101
+ - name: Install Zig
102
+ if: matrix.glibc
103
+ uses: mlugg/setup-zig@v2
104
+ with:
105
+ version: 0.14.1
106
+
107
+ - name: Install cargo-zigbuild
108
+ if: matrix.glibc
109
+ run: cargo install --locked cargo-zigbuild
110
+
111
+ - name: Build release binary (zigbuild)
112
+ if: matrix.glibc
113
+ run: cargo zigbuild --release --target ${{ matrix.target }}.${{ matrix.glibc }}
114
+
115
+ - name: Build release binary
116
+ if: ${{ !matrix.glibc }}
117
+ run: cargo build --release --target ${{ matrix.target }}
118
+
119
+ # Regression guard: fail if the binary ever requires a glibc newer than
120
+ # the declared floor.
121
+ - name: Verify glibc floor
122
+ if: matrix.glibc
123
+ run: |
124
+ set -euo pipefail
125
+ bin="target/${{ matrix.target }}/release/badness"
126
+ max=$(objdump -T "$bin" 2>/dev/null \
127
+ | grep -oE 'GLIBC_[0-9]+(\.[0-9]+)+' \
128
+ | sed 's/GLIBC_//' | sort -V | tail -n1 || true)
129
+ echo "Highest required glibc symbol: ${max:-none}"
130
+ if [ -n "$max" ] && [ "$(printf '%s\n%s\n' "$max" "${{ matrix.glibc }}" | sort -V | tail -n1)" != "${{ matrix.glibc }}" ]; then
131
+ echo "::error::badness requires glibc $max, above the ${{ matrix.glibc }} floor"
132
+ exit 1
133
+ fi
134
+
135
+ - name: Strip binary (Unix)
136
+ if: runner.os != 'Windows'
137
+ run: strip target/${{ matrix.target }}/release/badness
138
+
139
+ - name: Create archive (Unix)
140
+ if: runner.os != 'Windows'
141
+ run: |
142
+ mkdir -p dist
143
+ cp target/${{ matrix.target }}/release/badness dist/
144
+ cp LICENSE dist/
145
+ cp README.md dist/
146
+ tar czf badness-${{ matrix.target }}.tar.gz -C dist .
147
+ rm -rf dist
148
+
149
+ - name: Create archive (Windows)
150
+ if: runner.os == 'Windows'
151
+ shell: pwsh
152
+ run: |
153
+ New-Item -ItemType Directory -Path dist -Force
154
+ Copy-Item target/${{ matrix.target }}/release/badness.exe dist/
155
+ Copy-Item LICENSE dist/
156
+ Copy-Item README.md dist/
157
+ Compress-Archive -Path dist/* -DestinationPath badness-${{ matrix.target }}.zip
158
+ Remove-Item -Recurse -Force dist
159
+
160
+ - name: Upload archive artifact
161
+ uses: actions/upload-artifact@v7
162
+ with:
163
+ name: badness-${{ matrix.target }}
164
+ path: |
165
+ badness-${{ matrix.target }}.tar.gz
166
+ badness-${{ matrix.target }}.zip
167
+
168
+ - name: Upload binary to release
169
+ if: inputs.upload == 'true'
170
+ shell: bash
171
+ run: |
172
+ TAG="${{ inputs.tag }}"
173
+ if [ -f badness-${{ matrix.target }}.tar.gz ]; then
174
+ gh release upload --clobber "$TAG" badness-${{ matrix.target }}.tar.gz
175
+ fi
176
+ if [ -f badness-${{ matrix.target }}.zip ]; then
177
+ gh release upload --clobber "$TAG" badness-${{ matrix.target }}.zip
178
+ fi
179
+ env:
180
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,35 @@
1
+ name: Publish Crates
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ tags:
7
+ - "v*"
8
+
9
+ concurrency:
10
+ group: ${{ github.workflow }}-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ env:
14
+ CARGO_TERM_COLOR: always
15
+
16
+ jobs:
17
+ publish:
18
+ runs-on: ubuntu-latest
19
+ environment: release # Optional: for enhanced security
20
+ permissions:
21
+ id-token: write # Required for OIDC token exchange
22
+ steps:
23
+ - uses: actions/checkout@v6
24
+
25
+ - uses: rust-lang/crates-io-auth-action@v1
26
+ id: auth
27
+
28
+ - uses: dtolnay/rust-toolchain@master
29
+ with:
30
+ toolchain: 1.94.1
31
+
32
+ - name: Publish to crates.io
33
+ env:
34
+ CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
35
+ run: cargo publish --no-verify
@@ -0,0 +1,127 @@
1
+ name: Publish PyPI
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ repository:
7
+ description: "Target repository"
8
+ required: true
9
+ default: "pypi"
10
+ type: choice
11
+ options:
12
+ - pypi
13
+ - testpypi
14
+ push:
15
+ tags:
16
+ - "v*"
17
+
18
+ concurrency:
19
+ group: ${{ github.workflow }}-${{ github.ref }}
20
+ cancel-in-progress: true
21
+
22
+ env:
23
+ CARGO_TERM_COLOR: always
24
+
25
+ jobs:
26
+ build-wheels:
27
+ name: Build wheel ${{ matrix.target }}
28
+ runs-on: ${{ matrix.os }}
29
+ strategy:
30
+ fail-fast: false
31
+ matrix:
32
+ include:
33
+ - target: x86_64-unknown-linux-gnu
34
+ os: ubuntu-latest
35
+ manylinux: auto
36
+ - target: aarch64-unknown-linux-gnu
37
+ os: ubuntu-24.04-arm
38
+ manylinux: auto
39
+ - target: x86_64-unknown-linux-musl
40
+ os: ubuntu-latest
41
+ manylinux: musllinux_1_2
42
+ - target: aarch64-unknown-linux-musl
43
+ os: ubuntu-24.04-arm
44
+ manylinux: musllinux_1_2
45
+ - target: x86_64-apple-darwin
46
+ os: macos-15-intel
47
+ manylinux: ""
48
+ - target: aarch64-apple-darwin
49
+ os: macos-latest
50
+ manylinux: ""
51
+ - target: x86_64-pc-windows-msvc
52
+ os: windows-latest
53
+ manylinux: ""
54
+ - target: aarch64-pc-windows-msvc
55
+ os: windows-11-arm
56
+ manylinux: ""
57
+ steps:
58
+ - uses: actions/checkout@v6
59
+
60
+ - name: Install Rust (non-Linux)
61
+ if: runner.os != 'Linux'
62
+ uses: dtolnay/rust-toolchain@stable
63
+ with:
64
+ targets: ${{ matrix.target }}
65
+
66
+ - name: Cache Rust artifacts
67
+ if: runner.os != 'Linux'
68
+ uses: Swatinem/rust-cache@v2
69
+ with:
70
+ key: ${{ matrix.target }}
71
+ cache-bin: "false"
72
+ prefix-key: "v1-rust"
73
+
74
+ - name: Build wheel
75
+ uses: PyO3/maturin-action@v1
76
+ with:
77
+ target: ${{ matrix.target }}
78
+ manylinux: ${{ matrix.manylinux }}
79
+ args: --release --out dist
80
+
81
+ - name: Upload wheel artifact
82
+ uses: actions/upload-artifact@v7
83
+ with:
84
+ name: wheel-${{ matrix.target }}
85
+ path: dist/*.whl
86
+
87
+ build-sdist:
88
+ name: Build sdist
89
+ runs-on: ubuntu-latest
90
+ steps:
91
+ - uses: actions/checkout@v6
92
+
93
+ - name: Build sdist
94
+ uses: PyO3/maturin-action@v1
95
+ with:
96
+ command: sdist
97
+ args: --out dist
98
+
99
+ - name: Upload sdist artifact
100
+ uses: actions/upload-artifact@v7
101
+ with:
102
+ name: sdist
103
+ path: dist/*.tar.gz
104
+
105
+ publish:
106
+ name: Publish to ${{ github.event.inputs.repository || 'pypi' }}
107
+ needs: [build-wheels, build-sdist]
108
+ runs-on: ubuntu-latest
109
+ environment: release
110
+ permissions:
111
+ id-token: write
112
+ steps:
113
+ - name: Download all artifacts
114
+ uses: actions/download-artifact@v8
115
+ with:
116
+ path: dist
117
+ merge-multiple: true
118
+
119
+ - name: Publish to PyPI
120
+ if: github.event.inputs.repository != 'testpypi'
121
+ uses: pypa/gh-action-pypi-publish@release/v1
122
+
123
+ - name: Publish to TestPyPI
124
+ if: github.event.inputs.repository == 'testpypi'
125
+ uses: pypa/gh-action-pypi-publish@release/v1
126
+ with:
127
+ repository-url: https://test.pypi.org/legacy/