├── .github
├── copyright.sh
├── debug_assertions.sh
└── workflows
│ └── ci.yml
├── .gitignore
├── .typos.toml
├── AUTHORS
├── CHANGELOG.md
├── Cargo.lock
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── docs
├── logo.png
├── logo_small.png
└── screenshot.png
├── example
├── .cargo
│ └── config.toml
├── Cargo.lock
├── Cargo.toml
├── README.md
├── demolib
│ ├── Cargo.toml
│ └── src
│ │ ├── lib.rs
│ │ └── test_utils.rs
├── tests
│ ├── current
│ │ └── .gitignore
│ └── snapshots
│ │ └── create_rectangle.png
└── xtask_kompari
│ ├── Cargo.toml
│ └── src
│ └── main.rs
├── kompari-cli
├── Cargo.toml
├── README.md
├── src
│ └── main.rs
└── tests
│ └── tests.rs
├── kompari-html
├── Cargo.toml
├── README.md
└── src
│ ├── lib.rs
│ ├── pageconsts.rs
│ ├── report.rs
│ └── review.rs
├── kompari-tasks
├── Cargo.toml
├── README.md
└── src
│ ├── args.rs
│ ├── lib.rs
│ ├── optimizations.rs
│ └── task.rs
├── kompari
├── Cargo.toml
├── README.md
├── src
│ ├── dirdiff.rs
│ ├── fsutils.rs
│ ├── imageutils.rs
│ ├── imgdiff.rs
│ └── lib.rs
└── tests
│ └── compare.rs
└── tests
├── left
├── bright.png
├── changetext.png
├── right_missing.png
├── same.png
├── shift.png
└── size_error.png
└── right
├── bright.png
├── changetext.png
├── left_missing.png
├── same.png
├── shift.png
└── size_error.png
/.github/copyright.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # If there are new files with headers that can't match the conditions here,
4 | # then the files can be ignored by an additional glob argument via the -g flag.
5 | # For example:
6 | # -g "!src/special_file.rs"
7 | # -g "!src/special_directory"
8 |
9 | # Check all the standard Rust source files
10 | output=$(rg "^// Copyright (19|20)[\d]{2} (.+ and )?the Kompari Authors( and .+)?$\n^// SPDX-License-Identifier: Apache-2\.0 OR MIT$\n\n" --files-without-match --multiline -g "*.rs" .)
11 |
12 | if [ -n "$output" ]; then
13 | echo -e "The following files lack the correct copyright header:\n"
14 | echo $output
15 | echo -e "\n\nPlease add the following header:\n"
16 | echo "// Copyright $(date +%Y) the Kompari Authors"
17 | echo "// SPDX-License-Identifier: Apache-2.0 OR MIT"
18 | echo -e "\n... rest of the file ...\n"
19 | exit 1
20 | fi
21 |
22 | echo "All files have correct copyright headers."
23 | exit 0
24 |
--------------------------------------------------------------------------------
/.github/debug_assertions.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Check all the standard Rust source files
4 | output=$(rg "debug_assertions" -g "*.rs" .)
5 |
6 | if [ -z "$output" ]; then
7 | if [ "$USING_DEBUG_ASSERTIONS" = "true" ]; then
8 | echo "Could not find any debug_assertions usage in Rust code."
9 | echo "The CI script must be modified to not expect usage."
10 | echo "Set USING_DEBUG_ASSERTIONS to false in .github/workflows/ci.yml."
11 | exit 1
12 | else
13 | echo "Expected no debug_assertions usage in Rust code and found none."
14 | exit 0
15 | fi
16 | else
17 | if [ "$USING_DEBUG_ASSERTIONS" = "true" ]; then
18 | echo "Expected debug_assertions to be used in Rust code and found it."
19 | exit 0
20 | else
21 | echo "Found debug_assertions usage in Rust code."
22 | echo ""
23 | echo $output
24 | echo ""
25 | echo "The CI script must be modified to expect this usage."
26 | echo "Set USING_DEBUG_ASSERTIONS to true in .github/workflows/ci.yml."
27 | exit 1
28 | fi
29 | fi
30 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | env:
2 | # We aim to always test with the latest stable Rust toolchain, however we pin to a specific
3 | # version like 1.70. Note that we only specify MAJOR.MINOR and not PATCH so that bugfixes still
4 | # come automatically. If the version specified here is no longer the latest stable version,
5 | # then please feel free to submit a PR that adjusts it along with the potential clippy fixes.
6 | RUST_STABLE_VER: "1.83" # In quotes because otherwise (e.g.) 1.70 would be interpreted as 1.7
7 | # The purpose of checking with the minimum supported Rust toolchain is to detect its staleness.
8 | # If the compilation fails, then the version specified here needs to be bumped up to reality.
9 | # Be sure to also update the rust-version property in the workspace Cargo.toml file,
10 | # the Unreleased section of CHANGELOG.md, plus all the README.md files of the affected packages.
11 | RUST_MIN_VER: "1.78"
12 | # List of packages that can not target Wasm.
13 | NO_WASM_PKGS: "--exclude kompari-cli --exclude kompari-html --exclude kompari-tasks"
14 | # List of packages that will be checked with the minimum supported Rust version.
15 | # This should be limited to packages that are intended for publishing.
16 | RUST_MIN_VER_PKGS: "-p kompari -p kompari-html -p kompari-tasks"
17 | # Whether the workspace contains Rust code using the debug_assertions configuration option.
18 | USING_DEBUG_ASSERTIONS: "false"
19 |
20 |
21 | # Rationale
22 | #
23 | # We don't run clippy with --all-targets because then even --lib and --bins are compiled with
24 | # dev dependencies enabled, which does not match how they would be compiled by users.
25 | # A dev dependency might enable a feature that we need for a regular dependency,
26 | # and checking with --all-targets would not find our feature requirements lacking.
27 | # This problem still applies to cargo resolver version 2.
28 | # Thus we split all the targets into two steps, one with --lib --bins
29 | # and another with --tests --benches --examples.
30 | # Also, we can't give --lib --bins explicitly because then cargo will error on binary-only packages.
31 | # Luckily the default behavior of cargo with no explicit targets is the same but without the error.
32 | #
33 | # We use cargo-hack for a similar reason. Cargo's --workspace will do feature unification across
34 | # the whole workspace. While cargo-hack will instead check each workspace package separately.
35 | #
36 | # Using cargo-hack also allows us to more easily test the feature matrix of our packages.
37 | # We use --each-feature & --optional-deps which will run a separate check for every feature.
38 | #
39 | # We use cargo-nextest, which has a faster concurrency model for running tests.
40 | # However cargo-nextest does not support running doc tests, so we also have a cargo test --doc step.
41 | # For more information see https://github.com/nextest-rs/nextest/issues/16
42 | #
43 | # The MSRV jobs run only cargo check because different clippy versions can disagree on goals and
44 | # running tests introduces dev dependencies which may require a higher MSRV than the bare package.
45 | #
46 | # If the workspace uses debug_assertions then we verify code twice, with it set to true or false.
47 | # We always keep it true for external dependencies so that we can reuse the cache for faster builds.
48 | #
49 | # We don't save caches in the merge-group cases, because those caches will never be re-used (apart
50 | # from the very rare cases where there are multiple PRs in the merge queue).
51 | # This is because GitHub doesn't share caches between merge queues and the main branch.
52 |
53 | name: CI
54 |
55 | on:
56 | pull_request:
57 | merge_group:
58 | # We run on push, even though the commit is the same as when we ran in merge_group.
59 | # This allows the cache to be primed.
60 | # See https://github.com/orgs/community/discussions/66430
61 | push:
62 | branches:
63 | - main
64 |
65 | jobs:
66 | fmt:
67 | name: formatting
68 | runs-on: ubuntu-latest
69 | steps:
70 | - uses: actions/checkout@v4
71 |
72 | - name: install stable toolchain
73 | uses: dtolnay/rust-toolchain@master
74 | with:
75 | toolchain: ${{ env.RUST_STABLE_VER }}
76 | components: rustfmt
77 |
78 | - name: cargo fmt
79 | run: cargo fmt --all --check
80 |
81 | - name: install ripgrep
82 | run: |
83 | sudo apt update
84 | sudo apt install ripgrep
85 |
86 | - name: check copyright headers
87 | run: bash .github/copyright.sh
88 |
89 | - name: check debug_assertions presence
90 | run: bash .github/debug_assertions.sh
91 |
92 | clippy-stable:
93 | name: cargo clippy
94 | runs-on: ${{ matrix.os }}
95 | strategy:
96 | matrix:
97 | os: [ windows-latest, macos-latest, ubuntu-latest ]
98 | steps:
99 | - uses: actions/checkout@v4
100 |
101 | - name: install stable toolchain
102 | uses: dtolnay/rust-toolchain@master
103 | with:
104 | toolchain: ${{ env.RUST_STABLE_VER }}
105 | components: clippy
106 |
107 | - name: install cargo-hack
108 | uses: taiki-e/install-action@v2
109 | with:
110 | tool: cargo-hack
111 |
112 | - name: restore cache
113 | uses: Swatinem/rust-cache@v2
114 | with:
115 | save-if: ${{ github.event_name != 'merge_group' }}
116 |
117 | - name: cargo clippy
118 | run: cargo hack clippy --workspace --locked --profile ci --optional-deps --each-feature -- -D warnings
119 |
120 | - name: cargo clippy (auxiliary)
121 | run: cargo hack clippy --workspace --locked --profile ci --optional-deps --each-feature --tests --benches --examples -- -D warnings
122 |
123 | - name: cargo clippy (no debug_assertions)
124 | if: env.USING_DEBUG_ASSERTIONS == 'true'
125 | run: cargo hack clippy --workspace --locked --profile ci --optional-deps --each-feature -- -D warnings
126 | env:
127 | CARGO_PROFILE_CI_DEBUG_ASSERTIONS: "false"
128 |
129 | - name: cargo clippy (auxiliary) (no debug_assertions)
130 | if: env.USING_DEBUG_ASSERTIONS == 'true'
131 | run: cargo hack clippy --workspace --locked --profile ci --optional-deps --each-feature --tests --benches --examples -- -D warnings
132 | env:
133 | CARGO_PROFILE_CI_DEBUG_ASSERTIONS: "false"
134 |
135 | clippy-stable-wasm:
136 | name: cargo clippy (wasm32)
137 | runs-on: ubuntu-latest
138 | steps:
139 | - uses: actions/checkout@v4
140 |
141 | - name: install stable toolchain
142 | uses: dtolnay/rust-toolchain@master
143 | with:
144 | toolchain: ${{ env.RUST_STABLE_VER }}
145 | targets: wasm32-unknown-unknown
146 | components: clippy
147 |
148 | - name: install cargo-hack
149 | uses: taiki-e/install-action@v2
150 | with:
151 | tool: cargo-hack
152 |
153 | - name: restore cache
154 | uses: Swatinem/rust-cache@v2
155 | with:
156 | save-if: ${{ github.event_name != 'merge_group' }}
157 |
158 | - name: cargo clippy
159 | run: cargo hack clippy --workspace ${{ env.NO_WASM_PKGS }} --locked --profile ci --target wasm32-unknown-unknown --optional-deps --each-feature --skip review,oxipng,default -- -D warnings
160 |
161 | - name: cargo clippy (auxiliary)
162 | run: cargo hack clippy --workspace ${{ env.NO_WASM_PKGS }} --locked --profile ci --target wasm32-unknown-unknown --optional-deps --each-feature --skip review,oxipng,default --tests --benches --examples -- -D warnings
163 |
164 | - name: cargo clippy (no debug_assertions)
165 | if: env.USING_DEBUG_ASSERTIONS == 'true'
166 | run: cargo hack clippy --workspace ${{ env.NO_WASM_PKGS }} --locked --profile ci --target wasm32-unknown-unknown --optional-deps --each-feature --skip review,oxipng,default -- -D warnings
167 | env:
168 | CARGO_PROFILE_CI_DEBUG_ASSERTIONS: "false"
169 |
170 | - name: cargo clippy (auxiliary) (no debug_assertions)
171 | if: env.USING_DEBUG_ASSERTIONS == 'true'
172 | run: cargo hack clippy --workspace ${{ env.NO_WASM_PKGS }} --locked --profile ci --target wasm32-unknown-unknown --optional-deps --each-feature --skip review,oxipng,default --tests --benches --examples -- -D warnings
173 | env:
174 | CARGO_PROFILE_CI_DEBUG_ASSERTIONS: "false"
175 |
176 | test-stable:
177 | name: cargo test
178 | runs-on: ${{ matrix.os }}
179 | strategy:
180 | matrix:
181 | os: [ windows-latest, macos-latest, ubuntu-latest ]
182 | steps:
183 | - uses: actions/checkout@v4
184 |
185 | - name: install stable toolchain
186 | uses: dtolnay/rust-toolchain@master
187 | with:
188 | toolchain: ${{ env.RUST_STABLE_VER }}
189 |
190 | - name: install cargo-nextest
191 | uses: taiki-e/install-action@v2
192 | with:
193 | tool: cargo-nextest
194 |
195 | - name: restore cache
196 | uses: Swatinem/rust-cache@v2
197 | with:
198 | save-if: ${{ github.event_name != 'merge_group' }}
199 |
200 | - name: cargo nextest
201 | run: cargo nextest run --workspace --locked --all-features --no-fail-fast
202 |
203 | - name: cargo test --doc
204 | run: cargo test --doc --workspace --locked --all-features --no-fail-fast
205 |
206 | test-stable-wasm:
207 | name: cargo test (wasm32)
208 | runs-on: ubuntu-latest
209 | steps:
210 | - uses: actions/checkout@v4
211 |
212 | - name: install stable toolchain
213 | uses: dtolnay/rust-toolchain@master
214 | with:
215 | toolchain: ${{ env.RUST_STABLE_VER }}
216 | targets: wasm32-unknown-unknown
217 |
218 | - name: restore cache
219 | uses: Swatinem/rust-cache@v2
220 | with:
221 | save-if: ${{ github.event_name != 'merge_group' }}
222 |
223 | # TODO: Find a way to make tests work. Until then the tests are merely compiled.
224 | - name: cargo test compile
225 | run: cargo test --workspace ${{ env.NO_WASM_PKGS }} --locked --target wasm32-unknown-unknown --no-default-features --no-run
226 |
227 | check-msrv:
228 | name: cargo check (msrv)
229 | runs-on: ${{ matrix.os }}
230 | strategy:
231 | matrix:
232 | os: [ windows-latest, macos-latest, ubuntu-latest ]
233 | steps:
234 | - uses: actions/checkout@v4
235 |
236 | - name: install msrv toolchain
237 | uses: dtolnay/rust-toolchain@master
238 | with:
239 | toolchain: ${{ env.RUST_MIN_VER }}
240 |
241 | - name: install cargo-hack
242 | uses: taiki-e/install-action@v2
243 | with:
244 | tool: cargo-hack
245 |
246 | - name: restore cache
247 | uses: Swatinem/rust-cache@v2
248 | with:
249 | save-if: ${{ github.event_name != 'merge_group' }}
250 |
251 | - name: cargo check
252 | run: cargo hack check ${{ env.RUST_MIN_VER_PKGS }} --locked --profile ci --optional-deps --each-feature
253 |
254 | - name: cargo check (no debug_assertions)
255 | if: env.USING_DEBUG_ASSERTIONS == 'true'
256 | run: cargo hack check ${{ env.RUST_MIN_VER_PKGS }} --locked --profile ci --optional-deps --each-feature
257 | env:
258 | CARGO_PROFILE_CI_DEBUG_ASSERTIONS: "false"
259 |
260 | check-msrv-wasm:
261 | name: cargo check (msrv) (wasm32)
262 | runs-on: ubuntu-latest
263 | steps:
264 | - uses: actions/checkout@v4
265 |
266 | - name: install msrv toolchain
267 | uses: dtolnay/rust-toolchain@master
268 | with:
269 | toolchain: ${{ env.RUST_MIN_VER }}
270 | targets: wasm32-unknown-unknown
271 |
272 | - name: install cargo-hack
273 | uses: taiki-e/install-action@v2
274 | with:
275 | tool: cargo-hack
276 |
277 | - name: restore cache
278 | uses: Swatinem/rust-cache@v2
279 | with:
280 | save-if: ${{ github.event_name != 'merge_group' }}
281 |
282 | - name: cargo check
283 | run: cargo hack check ${{ env.RUST_MIN_VER_PKGS }} ${{ env.NO_WASM_PKGS }} --locked --profile ci --target wasm32-unknown-unknown --optional-deps --each-feature --skip review,oxipng,default
284 |
285 | - name: cargo check (no debug_assertions)
286 | if: env.USING_DEBUG_ASSERTIONS == 'true'
287 | run: cargo hack check ${{ env.RUST_MIN_VER_PKGS }} ${{ env.NO_WASM_PKGS }} --locked --profile ci --target wasm32-unknown-unknown --optional-deps --each-feature --skip review,oxipng,default
288 | env:
289 | CARGO_PROFILE_CI_DEBUG_ASSERTIONS: "false"
290 |
291 | doc:
292 | name: cargo doc
293 | # NOTE: We don't have any platform specific docs in this workspace, so we only run on Ubuntu.
294 | # If we get per-platform docs (win/macos/linux/wasm32/..) then doc jobs should match that.
295 | runs-on: ubuntu-latest
296 | steps:
297 | - uses: actions/checkout@v4
298 |
299 | - name: install nightly toolchain
300 | uses: dtolnay/rust-toolchain@nightly
301 |
302 | - name: restore cache
303 | uses: Swatinem/rust-cache@v2
304 | with:
305 | save-if: ${{ github.event_name != 'merge_group' }}
306 |
307 | # We test documentation using nightly to match docs.rs.
308 | - name: cargo doc
309 | run: cargo doc --workspace --locked --all-features --no-deps --document-private-items
310 | env:
311 | RUSTDOCFLAGS: '--cfg docsrs -D warnings'
312 |
313 | # If this fails, consider changing your text or adding something to .typos.toml.
314 | typos:
315 | runs-on: ubuntu-latest
316 | steps:
317 | - uses: actions/checkout@v4
318 |
319 | - name: check typos
320 | uses: crate-ci/typos@v1.27.0
321 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | report.html
3 |
--------------------------------------------------------------------------------
/.typos.toml:
--------------------------------------------------------------------------------
1 | # See the configuration reference at
2 | # https://github.com/crate-ci/typos/blob/master/docs/reference.md
3 |
4 | # Corrections take the form of a key/value pair. The key is the incorrect word
5 | # and the value is the correct word. If the key and value are the same, the
6 | # word is treated as always correct. If the value is an empty string, the word
7 | # is treated as always incorrect.
8 |
9 | # Match Identifier - Case Sensitive
10 | [default.extend-identifiers]
11 |
12 | # Match Inside a Word - Case Insensitive
13 | [default.extend-words]
14 |
15 | [files]
16 | # Include .github, .cargo, etc.
17 | ignore-hidden = false
18 | extend-exclude = [
19 | # /.git isn't in .gitignore, because git never tracks it.
20 | # Typos doesn't know that, though.
21 | "/.git",
22 | ]
23 |
--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
1 | # This is the list of Kompari's significant contributors.
2 | #
3 | # This does not necessarily list everyone who has contributed code,
4 | # especially since many employees of one corporation may be contributing.
5 | # To see the full list of contributors, see the revision history in
6 | # source control.
7 | Ada Böhm
8 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
8 |
9 | # Changelog
10 |
11 |
15 |
16 | ## [Unreleased]
17 |
18 | This release has an [MSRV][] of 1.78.
19 |
20 | - Initial release.
21 |
22 | [@spirali]: https://github.com/spirali
23 |
24 |
27 |
28 |
32 | [Unreleased]: https://github.com/linebender/kompari
33 |
34 | [MSRV]: README.md#minimum-supported-rust-version-msrv
35 |
--------------------------------------------------------------------------------
/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "addr2line"
7 | version = "0.24.2"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
10 | dependencies = [
11 | "gimli",
12 | ]
13 |
14 | [[package]]
15 | name = "adler2"
16 | version = "2.0.0"
17 | source = "registry+https://github.com/rust-lang/crates.io-index"
18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
19 |
20 | [[package]]
21 | name = "android-tzdata"
22 | version = "0.1.1"
23 | source = "registry+https://github.com/rust-lang/crates.io-index"
24 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
25 |
26 | [[package]]
27 | name = "android_system_properties"
28 | version = "0.1.5"
29 | source = "registry+https://github.com/rust-lang/crates.io-index"
30 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
31 | dependencies = [
32 | "libc",
33 | ]
34 |
35 | [[package]]
36 | name = "anstream"
37 | version = "0.6.18"
38 | source = "registry+https://github.com/rust-lang/crates.io-index"
39 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
40 | dependencies = [
41 | "anstyle",
42 | "anstyle-parse",
43 | "anstyle-query",
44 | "anstyle-wincon",
45 | "colorchoice",
46 | "is_terminal_polyfill",
47 | "utf8parse",
48 | ]
49 |
50 | [[package]]
51 | name = "anstyle"
52 | version = "1.0.10"
53 | source = "registry+https://github.com/rust-lang/crates.io-index"
54 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
55 |
56 | [[package]]
57 | name = "anstyle-parse"
58 | version = "0.2.6"
59 | source = "registry+https://github.com/rust-lang/crates.io-index"
60 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
61 | dependencies = [
62 | "utf8parse",
63 | ]
64 |
65 | [[package]]
66 | name = "anstyle-query"
67 | version = "1.1.2"
68 | source = "registry+https://github.com/rust-lang/crates.io-index"
69 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
70 | dependencies = [
71 | "windows-sys 0.59.0",
72 | ]
73 |
74 | [[package]]
75 | name = "anstyle-wincon"
76 | version = "3.0.7"
77 | source = "registry+https://github.com/rust-lang/crates.io-index"
78 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
79 | dependencies = [
80 | "anstyle",
81 | "once_cell",
82 | "windows-sys 0.59.0",
83 | ]
84 |
85 | [[package]]
86 | name = "assert_cmd"
87 | version = "2.0.16"
88 | source = "registry+https://github.com/rust-lang/crates.io-index"
89 | checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d"
90 | dependencies = [
91 | "anstyle",
92 | "bstr",
93 | "doc-comment",
94 | "libc",
95 | "predicates",
96 | "predicates-core",
97 | "predicates-tree",
98 | "wait-timeout",
99 | ]
100 |
101 | [[package]]
102 | name = "autocfg"
103 | version = "1.4.0"
104 | source = "registry+https://github.com/rust-lang/crates.io-index"
105 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
106 |
107 | [[package]]
108 | name = "axum"
109 | version = "0.8.1"
110 | source = "registry+https://github.com/rust-lang/crates.io-index"
111 | checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8"
112 | dependencies = [
113 | "axum-core",
114 | "bytes",
115 | "form_urlencoded",
116 | "futures-util",
117 | "http",
118 | "http-body",
119 | "http-body-util",
120 | "hyper",
121 | "hyper-util",
122 | "itoa",
123 | "matchit",
124 | "memchr",
125 | "mime",
126 | "percent-encoding",
127 | "pin-project-lite",
128 | "rustversion",
129 | "serde",
130 | "serde_json",
131 | "serde_path_to_error",
132 | "serde_urlencoded",
133 | "sync_wrapper",
134 | "tokio",
135 | "tower",
136 | "tower-layer",
137 | "tower-service",
138 | "tracing",
139 | ]
140 |
141 | [[package]]
142 | name = "axum-core"
143 | version = "0.5.0"
144 | source = "registry+https://github.com/rust-lang/crates.io-index"
145 | checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733"
146 | dependencies = [
147 | "bytes",
148 | "futures-util",
149 | "http",
150 | "http-body",
151 | "http-body-util",
152 | "mime",
153 | "pin-project-lite",
154 | "rustversion",
155 | "sync_wrapper",
156 | "tower-layer",
157 | "tower-service",
158 | "tracing",
159 | ]
160 |
161 | [[package]]
162 | name = "backtrace"
163 | version = "0.3.74"
164 | source = "registry+https://github.com/rust-lang/crates.io-index"
165 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
166 | dependencies = [
167 | "addr2line",
168 | "cfg-if",
169 | "libc",
170 | "miniz_oxide",
171 | "object",
172 | "rustc-demangle",
173 | "windows-targets",
174 | ]
175 |
176 | [[package]]
177 | name = "base64"
178 | version = "0.22.1"
179 | source = "registry+https://github.com/rust-lang/crates.io-index"
180 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
181 |
182 | [[package]]
183 | name = "bitflags"
184 | version = "1.3.2"
185 | source = "registry+https://github.com/rust-lang/crates.io-index"
186 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
187 |
188 | [[package]]
189 | name = "bitflags"
190 | version = "2.8.0"
191 | source = "registry+https://github.com/rust-lang/crates.io-index"
192 | checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
193 |
194 | [[package]]
195 | name = "bitvec"
196 | version = "1.0.1"
197 | source = "registry+https://github.com/rust-lang/crates.io-index"
198 | checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
199 | dependencies = [
200 | "funty",
201 | "radium",
202 | "tap",
203 | "wyz",
204 | ]
205 |
206 | [[package]]
207 | name = "bstr"
208 | version = "1.11.3"
209 | source = "registry+https://github.com/rust-lang/crates.io-index"
210 | checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0"
211 | dependencies = [
212 | "memchr",
213 | "regex-automata",
214 | "serde",
215 | ]
216 |
217 | [[package]]
218 | name = "bumpalo"
219 | version = "3.16.0"
220 | source = "registry+https://github.com/rust-lang/crates.io-index"
221 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
222 |
223 | [[package]]
224 | name = "bytemuck"
225 | version = "1.21.0"
226 | source = "registry+https://github.com/rust-lang/crates.io-index"
227 | checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3"
228 |
229 | [[package]]
230 | name = "byteorder-lite"
231 | version = "0.1.0"
232 | source = "registry+https://github.com/rust-lang/crates.io-index"
233 | checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
234 |
235 | [[package]]
236 | name = "bytes"
237 | version = "1.9.0"
238 | source = "registry+https://github.com/rust-lang/crates.io-index"
239 | checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
240 |
241 | [[package]]
242 | name = "cc"
243 | version = "1.2.10"
244 | source = "registry+https://github.com/rust-lang/crates.io-index"
245 | checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229"
246 | dependencies = [
247 | "shlex",
248 | ]
249 |
250 | [[package]]
251 | name = "cfg-if"
252 | version = "1.0.0"
253 | source = "registry+https://github.com/rust-lang/crates.io-index"
254 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
255 |
256 | [[package]]
257 | name = "chrono"
258 | version = "0.4.39"
259 | source = "registry+https://github.com/rust-lang/crates.io-index"
260 | checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
261 | dependencies = [
262 | "android-tzdata",
263 | "iana-time-zone",
264 | "js-sys",
265 | "num-traits",
266 | "wasm-bindgen",
267 | "windows-targets",
268 | ]
269 |
270 | [[package]]
271 | name = "clap"
272 | version = "4.5.27"
273 | source = "registry+https://github.com/rust-lang/crates.io-index"
274 | checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796"
275 | dependencies = [
276 | "clap_builder",
277 | "clap_derive",
278 | ]
279 |
280 | [[package]]
281 | name = "clap_builder"
282 | version = "4.5.27"
283 | source = "registry+https://github.com/rust-lang/crates.io-index"
284 | checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7"
285 | dependencies = [
286 | "anstream",
287 | "anstyle",
288 | "clap_lex",
289 | "strsim",
290 | ]
291 |
292 | [[package]]
293 | name = "clap_derive"
294 | version = "4.5.24"
295 | source = "registry+https://github.com/rust-lang/crates.io-index"
296 | checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c"
297 | dependencies = [
298 | "heck",
299 | "proc-macro2",
300 | "quote",
301 | "syn",
302 | ]
303 |
304 | [[package]]
305 | name = "clap_lex"
306 | version = "0.7.4"
307 | source = "registry+https://github.com/rust-lang/crates.io-index"
308 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
309 |
310 | [[package]]
311 | name = "colorchoice"
312 | version = "1.0.3"
313 | source = "registry+https://github.com/rust-lang/crates.io-index"
314 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
315 |
316 | [[package]]
317 | name = "console"
318 | version = "0.15.11"
319 | source = "registry+https://github.com/rust-lang/crates.io-index"
320 | checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
321 | dependencies = [
322 | "encode_unicode",
323 | "libc",
324 | "once_cell",
325 | "unicode-width",
326 | "windows-sys 0.59.0",
327 | ]
328 |
329 | [[package]]
330 | name = "core-foundation-sys"
331 | version = "0.8.7"
332 | source = "registry+https://github.com/rust-lang/crates.io-index"
333 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
334 |
335 | [[package]]
336 | name = "crc32fast"
337 | version = "1.4.2"
338 | source = "registry+https://github.com/rust-lang/crates.io-index"
339 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
340 | dependencies = [
341 | "cfg-if",
342 | ]
343 |
344 | [[package]]
345 | name = "crossbeam-channel"
346 | version = "0.5.14"
347 | source = "registry+https://github.com/rust-lang/crates.io-index"
348 | checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471"
349 | dependencies = [
350 | "crossbeam-utils",
351 | ]
352 |
353 | [[package]]
354 | name = "crossbeam-deque"
355 | version = "0.8.6"
356 | source = "registry+https://github.com/rust-lang/crates.io-index"
357 | checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
358 | dependencies = [
359 | "crossbeam-epoch",
360 | "crossbeam-utils",
361 | ]
362 |
363 | [[package]]
364 | name = "crossbeam-epoch"
365 | version = "0.9.18"
366 | source = "registry+https://github.com/rust-lang/crates.io-index"
367 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
368 | dependencies = [
369 | "crossbeam-utils",
370 | ]
371 |
372 | [[package]]
373 | name = "crossbeam-utils"
374 | version = "0.8.21"
375 | source = "registry+https://github.com/rust-lang/crates.io-index"
376 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
377 |
378 | [[package]]
379 | name = "difflib"
380 | version = "0.4.0"
381 | source = "registry+https://github.com/rust-lang/crates.io-index"
382 | checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
383 |
384 | [[package]]
385 | name = "doc-comment"
386 | version = "0.3.3"
387 | source = "registry+https://github.com/rust-lang/crates.io-index"
388 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
389 |
390 | [[package]]
391 | name = "either"
392 | version = "1.14.0"
393 | source = "registry+https://github.com/rust-lang/crates.io-index"
394 | checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d"
395 |
396 | [[package]]
397 | name = "encode_unicode"
398 | version = "1.0.0"
399 | source = "registry+https://github.com/rust-lang/crates.io-index"
400 | checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
401 |
402 | [[package]]
403 | name = "equivalent"
404 | version = "1.0.2"
405 | source = "registry+https://github.com/rust-lang/crates.io-index"
406 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
407 |
408 | [[package]]
409 | name = "errno"
410 | version = "0.3.10"
411 | source = "registry+https://github.com/rust-lang/crates.io-index"
412 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
413 | dependencies = [
414 | "libc",
415 | "windows-sys 0.59.0",
416 | ]
417 |
418 | [[package]]
419 | name = "fastrand"
420 | version = "2.3.0"
421 | source = "registry+https://github.com/rust-lang/crates.io-index"
422 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
423 |
424 | [[package]]
425 | name = "fdeflate"
426 | version = "0.3.7"
427 | source = "registry+https://github.com/rust-lang/crates.io-index"
428 | checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
429 | dependencies = [
430 | "simd-adler32",
431 | ]
432 |
433 | [[package]]
434 | name = "filetime"
435 | version = "0.2.25"
436 | source = "registry+https://github.com/rust-lang/crates.io-index"
437 | checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
438 | dependencies = [
439 | "cfg-if",
440 | "libc",
441 | "libredox",
442 | "windows-sys 0.59.0",
443 | ]
444 |
445 | [[package]]
446 | name = "flate2"
447 | version = "1.0.35"
448 | source = "registry+https://github.com/rust-lang/crates.io-index"
449 | checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
450 | dependencies = [
451 | "crc32fast",
452 | "miniz_oxide",
453 | ]
454 |
455 | [[package]]
456 | name = "fnv"
457 | version = "1.0.7"
458 | source = "registry+https://github.com/rust-lang/crates.io-index"
459 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
460 |
461 | [[package]]
462 | name = "form_urlencoded"
463 | version = "1.2.1"
464 | source = "registry+https://github.com/rust-lang/crates.io-index"
465 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
466 | dependencies = [
467 | "percent-encoding",
468 | ]
469 |
470 | [[package]]
471 | name = "funty"
472 | version = "2.0.0"
473 | source = "registry+https://github.com/rust-lang/crates.io-index"
474 | checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
475 |
476 | [[package]]
477 | name = "futures-channel"
478 | version = "0.3.31"
479 | source = "registry+https://github.com/rust-lang/crates.io-index"
480 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
481 | dependencies = [
482 | "futures-core",
483 | ]
484 |
485 | [[package]]
486 | name = "futures-core"
487 | version = "0.3.31"
488 | source = "registry+https://github.com/rust-lang/crates.io-index"
489 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
490 |
491 | [[package]]
492 | name = "futures-task"
493 | version = "0.3.31"
494 | source = "registry+https://github.com/rust-lang/crates.io-index"
495 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
496 |
497 | [[package]]
498 | name = "futures-util"
499 | version = "0.3.31"
500 | source = "registry+https://github.com/rust-lang/crates.io-index"
501 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
502 | dependencies = [
503 | "futures-core",
504 | "futures-task",
505 | "pin-project-lite",
506 | "pin-utils",
507 | ]
508 |
509 | [[package]]
510 | name = "getrandom"
511 | version = "0.2.15"
512 | source = "registry+https://github.com/rust-lang/crates.io-index"
513 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
514 | dependencies = [
515 | "cfg-if",
516 | "libc",
517 | "wasi",
518 | ]
519 |
520 | [[package]]
521 | name = "gimli"
522 | version = "0.31.1"
523 | source = "registry+https://github.com/rust-lang/crates.io-index"
524 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
525 |
526 | [[package]]
527 | name = "hashbrown"
528 | version = "0.15.2"
529 | source = "registry+https://github.com/rust-lang/crates.io-index"
530 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
531 |
532 | [[package]]
533 | name = "heck"
534 | version = "0.5.0"
535 | source = "registry+https://github.com/rust-lang/crates.io-index"
536 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
537 |
538 | [[package]]
539 | name = "http"
540 | version = "1.2.0"
541 | source = "registry+https://github.com/rust-lang/crates.io-index"
542 | checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
543 | dependencies = [
544 | "bytes",
545 | "fnv",
546 | "itoa",
547 | ]
548 |
549 | [[package]]
550 | name = "http-body"
551 | version = "1.0.1"
552 | source = "registry+https://github.com/rust-lang/crates.io-index"
553 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
554 | dependencies = [
555 | "bytes",
556 | "http",
557 | ]
558 |
559 | [[package]]
560 | name = "http-body-util"
561 | version = "0.1.2"
562 | source = "registry+https://github.com/rust-lang/crates.io-index"
563 | checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
564 | dependencies = [
565 | "bytes",
566 | "futures-util",
567 | "http",
568 | "http-body",
569 | "pin-project-lite",
570 | ]
571 |
572 | [[package]]
573 | name = "httparse"
574 | version = "1.9.5"
575 | source = "registry+https://github.com/rust-lang/crates.io-index"
576 | checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946"
577 |
578 | [[package]]
579 | name = "httpdate"
580 | version = "1.0.3"
581 | source = "registry+https://github.com/rust-lang/crates.io-index"
582 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
583 |
584 | [[package]]
585 | name = "humansize"
586 | version = "2.1.3"
587 | source = "registry+https://github.com/rust-lang/crates.io-index"
588 | checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
589 | dependencies = [
590 | "libm",
591 | ]
592 |
593 | [[package]]
594 | name = "hyper"
595 | version = "1.5.2"
596 | source = "registry+https://github.com/rust-lang/crates.io-index"
597 | checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0"
598 | dependencies = [
599 | "bytes",
600 | "futures-channel",
601 | "futures-util",
602 | "http",
603 | "http-body",
604 | "httparse",
605 | "httpdate",
606 | "itoa",
607 | "pin-project-lite",
608 | "smallvec",
609 | "tokio",
610 | ]
611 |
612 | [[package]]
613 | name = "hyper-util"
614 | version = "0.1.10"
615 | source = "registry+https://github.com/rust-lang/crates.io-index"
616 | checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4"
617 | dependencies = [
618 | "bytes",
619 | "futures-util",
620 | "http",
621 | "http-body",
622 | "hyper",
623 | "pin-project-lite",
624 | "tokio",
625 | "tower-service",
626 | ]
627 |
628 | [[package]]
629 | name = "iana-time-zone"
630 | version = "0.1.61"
631 | source = "registry+https://github.com/rust-lang/crates.io-index"
632 | checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
633 | dependencies = [
634 | "android_system_properties",
635 | "core-foundation-sys",
636 | "iana-time-zone-haiku",
637 | "js-sys",
638 | "wasm-bindgen",
639 | "windows-core",
640 | ]
641 |
642 | [[package]]
643 | name = "iana-time-zone-haiku"
644 | version = "0.1.2"
645 | source = "registry+https://github.com/rust-lang/crates.io-index"
646 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
647 | dependencies = [
648 | "cc",
649 | ]
650 |
651 | [[package]]
652 | name = "image"
653 | version = "0.25.5"
654 | source = "registry+https://github.com/rust-lang/crates.io-index"
655 | checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b"
656 | dependencies = [
657 | "bytemuck",
658 | "byteorder-lite",
659 | "num-traits",
660 | "png",
661 | ]
662 |
663 | [[package]]
664 | name = "imagesize"
665 | version = "0.13.0"
666 | source = "registry+https://github.com/rust-lang/crates.io-index"
667 | checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285"
668 |
669 | [[package]]
670 | name = "indexmap"
671 | version = "2.7.1"
672 | source = "registry+https://github.com/rust-lang/crates.io-index"
673 | checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
674 | dependencies = [
675 | "equivalent",
676 | "hashbrown",
677 | "rayon",
678 | ]
679 |
680 | [[package]]
681 | name = "indicatif"
682 | version = "0.17.11"
683 | source = "registry+https://github.com/rust-lang/crates.io-index"
684 | checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235"
685 | dependencies = [
686 | "console",
687 | "number_prefix",
688 | "portable-atomic",
689 | "unicode-width",
690 | "web-time",
691 | ]
692 |
693 | [[package]]
694 | name = "is_terminal_polyfill"
695 | version = "1.70.1"
696 | source = "registry+https://github.com/rust-lang/crates.io-index"
697 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
698 |
699 | [[package]]
700 | name = "itoa"
701 | version = "1.0.14"
702 | source = "registry+https://github.com/rust-lang/crates.io-index"
703 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
704 |
705 | [[package]]
706 | name = "js-sys"
707 | version = "0.3.77"
708 | source = "registry+https://github.com/rust-lang/crates.io-index"
709 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
710 | dependencies = [
711 | "once_cell",
712 | "wasm-bindgen",
713 | ]
714 |
715 | [[package]]
716 | name = "kompari"
717 | version = "0.1.0"
718 | dependencies = [
719 | "image",
720 | "log",
721 | "oxipng",
722 | "rayon",
723 | "thiserror",
724 | "walkdir",
725 | ]
726 |
727 | [[package]]
728 | name = "kompari-cli"
729 | version = "0.1.0"
730 | dependencies = [
731 | "assert_cmd",
732 | "clap",
733 | "kompari",
734 | "kompari-html",
735 | "kompari-tasks",
736 | "tempfile",
737 | ]
738 |
739 | [[package]]
740 | name = "kompari-html"
741 | version = "0.1.0"
742 | dependencies = [
743 | "axum",
744 | "base64",
745 | "chrono",
746 | "imagesize",
747 | "kompari",
748 | "maud",
749 | "rayon",
750 | "serde",
751 | "tokio",
752 | ]
753 |
754 | [[package]]
755 | name = "kompari-tasks"
756 | version = "0.1.0"
757 | dependencies = [
758 | "clap",
759 | "humansize",
760 | "indicatif",
761 | "kompari",
762 | "kompari-html",
763 | "rayon",
764 | "termcolor",
765 | ]
766 |
767 | [[package]]
768 | name = "libc"
769 | version = "0.2.169"
770 | source = "registry+https://github.com/rust-lang/crates.io-index"
771 | checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
772 |
773 | [[package]]
774 | name = "libdeflate-sys"
775 | version = "1.23.1"
776 | source = "registry+https://github.com/rust-lang/crates.io-index"
777 | checksum = "38b72ad3fbf5ac78f2df7b36075e48adf2459b57c150b9e63937d0204d0f9cd7"
778 | dependencies = [
779 | "cc",
780 | ]
781 |
782 | [[package]]
783 | name = "libdeflater"
784 | version = "1.23.1"
785 | source = "registry+https://github.com/rust-lang/crates.io-index"
786 | checksum = "013344b17f9dceddff4872559ae19378bd8ee0479eccdd266d2dd2e894b4792f"
787 | dependencies = [
788 | "libdeflate-sys",
789 | ]
790 |
791 | [[package]]
792 | name = "libm"
793 | version = "0.2.11"
794 | source = "registry+https://github.com/rust-lang/crates.io-index"
795 | checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
796 |
797 | [[package]]
798 | name = "libredox"
799 | version = "0.1.3"
800 | source = "registry+https://github.com/rust-lang/crates.io-index"
801 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
802 | dependencies = [
803 | "bitflags 2.8.0",
804 | "libc",
805 | "redox_syscall",
806 | ]
807 |
808 | [[package]]
809 | name = "linux-raw-sys"
810 | version = "0.4.15"
811 | source = "registry+https://github.com/rust-lang/crates.io-index"
812 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
813 |
814 | [[package]]
815 | name = "lockfree-object-pool"
816 | version = "0.1.6"
817 | source = "registry+https://github.com/rust-lang/crates.io-index"
818 | checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e"
819 |
820 | [[package]]
821 | name = "log"
822 | version = "0.4.25"
823 | source = "registry+https://github.com/rust-lang/crates.io-index"
824 | checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
825 |
826 | [[package]]
827 | name = "matchit"
828 | version = "0.8.4"
829 | source = "registry+https://github.com/rust-lang/crates.io-index"
830 | checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
831 |
832 | [[package]]
833 | name = "maud"
834 | version = "0.27.0"
835 | source = "registry+https://github.com/rust-lang/crates.io-index"
836 | checksum = "8156733e27020ea5c684db5beac5d1d611e1272ab17901a49466294b84fc217e"
837 | dependencies = [
838 | "itoa",
839 | "maud_macros",
840 | ]
841 |
842 | [[package]]
843 | name = "maud_macros"
844 | version = "0.27.0"
845 | source = "registry+https://github.com/rust-lang/crates.io-index"
846 | checksum = "7261b00f3952f617899bc012e3dbd56e4f0110a038175929fa5d18e5a19913ca"
847 | dependencies = [
848 | "proc-macro2",
849 | "proc-macro2-diagnostics",
850 | "quote",
851 | "syn",
852 | ]
853 |
854 | [[package]]
855 | name = "memchr"
856 | version = "2.7.4"
857 | source = "registry+https://github.com/rust-lang/crates.io-index"
858 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
859 |
860 | [[package]]
861 | name = "mime"
862 | version = "0.3.17"
863 | source = "registry+https://github.com/rust-lang/crates.io-index"
864 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
865 |
866 | [[package]]
867 | name = "miniz_oxide"
868 | version = "0.8.3"
869 | source = "registry+https://github.com/rust-lang/crates.io-index"
870 | checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924"
871 | dependencies = [
872 | "adler2",
873 | "simd-adler32",
874 | ]
875 |
876 | [[package]]
877 | name = "mio"
878 | version = "1.0.3"
879 | source = "registry+https://github.com/rust-lang/crates.io-index"
880 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
881 | dependencies = [
882 | "libc",
883 | "wasi",
884 | "windows-sys 0.52.0",
885 | ]
886 |
887 | [[package]]
888 | name = "num-traits"
889 | version = "0.2.19"
890 | source = "registry+https://github.com/rust-lang/crates.io-index"
891 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
892 | dependencies = [
893 | "autocfg",
894 | ]
895 |
896 | [[package]]
897 | name = "number_prefix"
898 | version = "0.4.0"
899 | source = "registry+https://github.com/rust-lang/crates.io-index"
900 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
901 |
902 | [[package]]
903 | name = "object"
904 | version = "0.36.7"
905 | source = "registry+https://github.com/rust-lang/crates.io-index"
906 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
907 | dependencies = [
908 | "memchr",
909 | ]
910 |
911 | [[package]]
912 | name = "once_cell"
913 | version = "1.20.2"
914 | source = "registry+https://github.com/rust-lang/crates.io-index"
915 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
916 |
917 | [[package]]
918 | name = "oxipng"
919 | version = "9.1.4"
920 | source = "registry+https://github.com/rust-lang/crates.io-index"
921 | checksum = "3bce05680d3f2ec3f0510f19608d56712fa7ea681b4ba293c3b74a04c2e55279"
922 | dependencies = [
923 | "bitvec",
924 | "crossbeam-channel",
925 | "filetime",
926 | "indexmap",
927 | "libdeflater",
928 | "log",
929 | "rayon",
930 | "rgb",
931 | "rustc-hash",
932 | "zopfli",
933 | ]
934 |
935 | [[package]]
936 | name = "percent-encoding"
937 | version = "2.3.1"
938 | source = "registry+https://github.com/rust-lang/crates.io-index"
939 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
940 |
941 | [[package]]
942 | name = "pin-project-lite"
943 | version = "0.2.16"
944 | source = "registry+https://github.com/rust-lang/crates.io-index"
945 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
946 |
947 | [[package]]
948 | name = "pin-utils"
949 | version = "0.1.0"
950 | source = "registry+https://github.com/rust-lang/crates.io-index"
951 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
952 |
953 | [[package]]
954 | name = "png"
955 | version = "0.17.16"
956 | source = "registry+https://github.com/rust-lang/crates.io-index"
957 | checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
958 | dependencies = [
959 | "bitflags 1.3.2",
960 | "crc32fast",
961 | "fdeflate",
962 | "flate2",
963 | "miniz_oxide",
964 | ]
965 |
966 | [[package]]
967 | name = "portable-atomic"
968 | version = "1.11.0"
969 | source = "registry+https://github.com/rust-lang/crates.io-index"
970 | checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
971 |
972 | [[package]]
973 | name = "predicates"
974 | version = "3.1.3"
975 | source = "registry+https://github.com/rust-lang/crates.io-index"
976 | checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573"
977 | dependencies = [
978 | "anstyle",
979 | "difflib",
980 | "predicates-core",
981 | ]
982 |
983 | [[package]]
984 | name = "predicates-core"
985 | version = "1.0.9"
986 | source = "registry+https://github.com/rust-lang/crates.io-index"
987 | checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa"
988 |
989 | [[package]]
990 | name = "predicates-tree"
991 | version = "1.0.12"
992 | source = "registry+https://github.com/rust-lang/crates.io-index"
993 | checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c"
994 | dependencies = [
995 | "predicates-core",
996 | "termtree",
997 | ]
998 |
999 | [[package]]
1000 | name = "proc-macro2"
1001 | version = "1.0.93"
1002 | source = "registry+https://github.com/rust-lang/crates.io-index"
1003 | checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
1004 | dependencies = [
1005 | "unicode-ident",
1006 | ]
1007 |
1008 | [[package]]
1009 | name = "proc-macro2-diagnostics"
1010 | version = "0.10.1"
1011 | source = "registry+https://github.com/rust-lang/crates.io-index"
1012 | checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
1013 | dependencies = [
1014 | "proc-macro2",
1015 | "quote",
1016 | "syn",
1017 | "version_check",
1018 | ]
1019 |
1020 | [[package]]
1021 | name = "quote"
1022 | version = "1.0.38"
1023 | source = "registry+https://github.com/rust-lang/crates.io-index"
1024 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
1025 | dependencies = [
1026 | "proc-macro2",
1027 | ]
1028 |
1029 | [[package]]
1030 | name = "radium"
1031 | version = "0.7.0"
1032 | source = "registry+https://github.com/rust-lang/crates.io-index"
1033 | checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
1034 |
1035 | [[package]]
1036 | name = "rayon"
1037 | version = "1.10.0"
1038 | source = "registry+https://github.com/rust-lang/crates.io-index"
1039 | checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
1040 | dependencies = [
1041 | "either",
1042 | "rayon-core",
1043 | ]
1044 |
1045 | [[package]]
1046 | name = "rayon-core"
1047 | version = "1.12.1"
1048 | source = "registry+https://github.com/rust-lang/crates.io-index"
1049 | checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
1050 | dependencies = [
1051 | "crossbeam-deque",
1052 | "crossbeam-utils",
1053 | ]
1054 |
1055 | [[package]]
1056 | name = "redox_syscall"
1057 | version = "0.5.10"
1058 | source = "registry+https://github.com/rust-lang/crates.io-index"
1059 | checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
1060 | dependencies = [
1061 | "bitflags 2.8.0",
1062 | ]
1063 |
1064 | [[package]]
1065 | name = "regex-automata"
1066 | version = "0.4.9"
1067 | source = "registry+https://github.com/rust-lang/crates.io-index"
1068 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
1069 |
1070 | [[package]]
1071 | name = "rgb"
1072 | version = "0.8.50"
1073 | source = "registry+https://github.com/rust-lang/crates.io-index"
1074 | checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a"
1075 | dependencies = [
1076 | "bytemuck",
1077 | ]
1078 |
1079 | [[package]]
1080 | name = "rustc-demangle"
1081 | version = "0.1.24"
1082 | source = "registry+https://github.com/rust-lang/crates.io-index"
1083 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
1084 |
1085 | [[package]]
1086 | name = "rustc-hash"
1087 | version = "2.1.1"
1088 | source = "registry+https://github.com/rust-lang/crates.io-index"
1089 | checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
1090 |
1091 | [[package]]
1092 | name = "rustix"
1093 | version = "0.38.44"
1094 | source = "registry+https://github.com/rust-lang/crates.io-index"
1095 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
1096 | dependencies = [
1097 | "bitflags 2.8.0",
1098 | "errno",
1099 | "libc",
1100 | "linux-raw-sys",
1101 | "windows-sys 0.59.0",
1102 | ]
1103 |
1104 | [[package]]
1105 | name = "rustversion"
1106 | version = "1.0.19"
1107 | source = "registry+https://github.com/rust-lang/crates.io-index"
1108 | checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
1109 |
1110 | [[package]]
1111 | name = "ryu"
1112 | version = "1.0.18"
1113 | source = "registry+https://github.com/rust-lang/crates.io-index"
1114 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
1115 |
1116 | [[package]]
1117 | name = "same-file"
1118 | version = "1.0.6"
1119 | source = "registry+https://github.com/rust-lang/crates.io-index"
1120 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
1121 | dependencies = [
1122 | "winapi-util",
1123 | ]
1124 |
1125 | [[package]]
1126 | name = "serde"
1127 | version = "1.0.217"
1128 | source = "registry+https://github.com/rust-lang/crates.io-index"
1129 | checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
1130 | dependencies = [
1131 | "serde_derive",
1132 | ]
1133 |
1134 | [[package]]
1135 | name = "serde_derive"
1136 | version = "1.0.217"
1137 | source = "registry+https://github.com/rust-lang/crates.io-index"
1138 | checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
1139 | dependencies = [
1140 | "proc-macro2",
1141 | "quote",
1142 | "syn",
1143 | ]
1144 |
1145 | [[package]]
1146 | name = "serde_json"
1147 | version = "1.0.137"
1148 | source = "registry+https://github.com/rust-lang/crates.io-index"
1149 | checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b"
1150 | dependencies = [
1151 | "itoa",
1152 | "memchr",
1153 | "ryu",
1154 | "serde",
1155 | ]
1156 |
1157 | [[package]]
1158 | name = "serde_path_to_error"
1159 | version = "0.1.16"
1160 | source = "registry+https://github.com/rust-lang/crates.io-index"
1161 | checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6"
1162 | dependencies = [
1163 | "itoa",
1164 | "serde",
1165 | ]
1166 |
1167 | [[package]]
1168 | name = "serde_urlencoded"
1169 | version = "0.7.1"
1170 | source = "registry+https://github.com/rust-lang/crates.io-index"
1171 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
1172 | dependencies = [
1173 | "form_urlencoded",
1174 | "itoa",
1175 | "ryu",
1176 | "serde",
1177 | ]
1178 |
1179 | [[package]]
1180 | name = "shlex"
1181 | version = "1.3.0"
1182 | source = "registry+https://github.com/rust-lang/crates.io-index"
1183 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
1184 |
1185 | [[package]]
1186 | name = "simd-adler32"
1187 | version = "0.3.7"
1188 | source = "registry+https://github.com/rust-lang/crates.io-index"
1189 | checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
1190 |
1191 | [[package]]
1192 | name = "smallvec"
1193 | version = "1.13.2"
1194 | source = "registry+https://github.com/rust-lang/crates.io-index"
1195 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
1196 |
1197 | [[package]]
1198 | name = "socket2"
1199 | version = "0.5.8"
1200 | source = "registry+https://github.com/rust-lang/crates.io-index"
1201 | checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
1202 | dependencies = [
1203 | "libc",
1204 | "windows-sys 0.52.0",
1205 | ]
1206 |
1207 | [[package]]
1208 | name = "strsim"
1209 | version = "0.11.1"
1210 | source = "registry+https://github.com/rust-lang/crates.io-index"
1211 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
1212 |
1213 | [[package]]
1214 | name = "syn"
1215 | version = "2.0.96"
1216 | source = "registry+https://github.com/rust-lang/crates.io-index"
1217 | checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
1218 | dependencies = [
1219 | "proc-macro2",
1220 | "quote",
1221 | "unicode-ident",
1222 | ]
1223 |
1224 | [[package]]
1225 | name = "sync_wrapper"
1226 | version = "1.0.2"
1227 | source = "registry+https://github.com/rust-lang/crates.io-index"
1228 | checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
1229 |
1230 | [[package]]
1231 | name = "tap"
1232 | version = "1.0.1"
1233 | source = "registry+https://github.com/rust-lang/crates.io-index"
1234 | checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
1235 |
1236 | [[package]]
1237 | name = "tempfile"
1238 | version = "3.15.0"
1239 | source = "registry+https://github.com/rust-lang/crates.io-index"
1240 | checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704"
1241 | dependencies = [
1242 | "cfg-if",
1243 | "fastrand",
1244 | "getrandom",
1245 | "once_cell",
1246 | "rustix",
1247 | "windows-sys 0.59.0",
1248 | ]
1249 |
1250 | [[package]]
1251 | name = "termcolor"
1252 | version = "1.4.1"
1253 | source = "registry+https://github.com/rust-lang/crates.io-index"
1254 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
1255 | dependencies = [
1256 | "winapi-util",
1257 | ]
1258 |
1259 | [[package]]
1260 | name = "termtree"
1261 | version = "0.5.1"
1262 | source = "registry+https://github.com/rust-lang/crates.io-index"
1263 | checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683"
1264 |
1265 | [[package]]
1266 | name = "thiserror"
1267 | version = "2.0.11"
1268 | source = "registry+https://github.com/rust-lang/crates.io-index"
1269 | checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
1270 | dependencies = [
1271 | "thiserror-impl",
1272 | ]
1273 |
1274 | [[package]]
1275 | name = "thiserror-impl"
1276 | version = "2.0.11"
1277 | source = "registry+https://github.com/rust-lang/crates.io-index"
1278 | checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
1279 | dependencies = [
1280 | "proc-macro2",
1281 | "quote",
1282 | "syn",
1283 | ]
1284 |
1285 | [[package]]
1286 | name = "tokio"
1287 | version = "1.43.0"
1288 | source = "registry+https://github.com/rust-lang/crates.io-index"
1289 | checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
1290 | dependencies = [
1291 | "backtrace",
1292 | "libc",
1293 | "mio",
1294 | "pin-project-lite",
1295 | "socket2",
1296 | "tokio-macros",
1297 | "windows-sys 0.52.0",
1298 | ]
1299 |
1300 | [[package]]
1301 | name = "tokio-macros"
1302 | version = "2.5.0"
1303 | source = "registry+https://github.com/rust-lang/crates.io-index"
1304 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
1305 | dependencies = [
1306 | "proc-macro2",
1307 | "quote",
1308 | "syn",
1309 | ]
1310 |
1311 | [[package]]
1312 | name = "tower"
1313 | version = "0.5.2"
1314 | source = "registry+https://github.com/rust-lang/crates.io-index"
1315 | checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
1316 | dependencies = [
1317 | "futures-core",
1318 | "futures-util",
1319 | "pin-project-lite",
1320 | "sync_wrapper",
1321 | "tokio",
1322 | "tower-layer",
1323 | "tower-service",
1324 | "tracing",
1325 | ]
1326 |
1327 | [[package]]
1328 | name = "tower-layer"
1329 | version = "0.3.3"
1330 | source = "registry+https://github.com/rust-lang/crates.io-index"
1331 | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
1332 |
1333 | [[package]]
1334 | name = "tower-service"
1335 | version = "0.3.3"
1336 | source = "registry+https://github.com/rust-lang/crates.io-index"
1337 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
1338 |
1339 | [[package]]
1340 | name = "tracing"
1341 | version = "0.1.41"
1342 | source = "registry+https://github.com/rust-lang/crates.io-index"
1343 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
1344 | dependencies = [
1345 | "log",
1346 | "pin-project-lite",
1347 | "tracing-core",
1348 | ]
1349 |
1350 | [[package]]
1351 | name = "tracing-core"
1352 | version = "0.1.33"
1353 | source = "registry+https://github.com/rust-lang/crates.io-index"
1354 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
1355 | dependencies = [
1356 | "once_cell",
1357 | ]
1358 |
1359 | [[package]]
1360 | name = "unicode-ident"
1361 | version = "1.0.15"
1362 | source = "registry+https://github.com/rust-lang/crates.io-index"
1363 | checksum = "11cd88e12b17c6494200a9c1b683a04fcac9573ed74cd1b62aeb2727c5592243"
1364 |
1365 | [[package]]
1366 | name = "unicode-width"
1367 | version = "0.2.0"
1368 | source = "registry+https://github.com/rust-lang/crates.io-index"
1369 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
1370 |
1371 | [[package]]
1372 | name = "utf8parse"
1373 | version = "0.2.2"
1374 | source = "registry+https://github.com/rust-lang/crates.io-index"
1375 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
1376 |
1377 | [[package]]
1378 | name = "version_check"
1379 | version = "0.9.5"
1380 | source = "registry+https://github.com/rust-lang/crates.io-index"
1381 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
1382 |
1383 | [[package]]
1384 | name = "wait-timeout"
1385 | version = "0.2.0"
1386 | source = "registry+https://github.com/rust-lang/crates.io-index"
1387 | checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
1388 | dependencies = [
1389 | "libc",
1390 | ]
1391 |
1392 | [[package]]
1393 | name = "walkdir"
1394 | version = "2.5.0"
1395 | source = "registry+https://github.com/rust-lang/crates.io-index"
1396 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
1397 | dependencies = [
1398 | "same-file",
1399 | "winapi-util",
1400 | ]
1401 |
1402 | [[package]]
1403 | name = "wasi"
1404 | version = "0.11.0+wasi-snapshot-preview1"
1405 | source = "registry+https://github.com/rust-lang/crates.io-index"
1406 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
1407 |
1408 | [[package]]
1409 | name = "wasm-bindgen"
1410 | version = "0.2.100"
1411 | source = "registry+https://github.com/rust-lang/crates.io-index"
1412 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
1413 | dependencies = [
1414 | "cfg-if",
1415 | "once_cell",
1416 | "rustversion",
1417 | "wasm-bindgen-macro",
1418 | ]
1419 |
1420 | [[package]]
1421 | name = "wasm-bindgen-backend"
1422 | version = "0.2.100"
1423 | source = "registry+https://github.com/rust-lang/crates.io-index"
1424 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
1425 | dependencies = [
1426 | "bumpalo",
1427 | "log",
1428 | "proc-macro2",
1429 | "quote",
1430 | "syn",
1431 | "wasm-bindgen-shared",
1432 | ]
1433 |
1434 | [[package]]
1435 | name = "wasm-bindgen-macro"
1436 | version = "0.2.100"
1437 | source = "registry+https://github.com/rust-lang/crates.io-index"
1438 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
1439 | dependencies = [
1440 | "quote",
1441 | "wasm-bindgen-macro-support",
1442 | ]
1443 |
1444 | [[package]]
1445 | name = "wasm-bindgen-macro-support"
1446 | version = "0.2.100"
1447 | source = "registry+https://github.com/rust-lang/crates.io-index"
1448 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
1449 | dependencies = [
1450 | "proc-macro2",
1451 | "quote",
1452 | "syn",
1453 | "wasm-bindgen-backend",
1454 | "wasm-bindgen-shared",
1455 | ]
1456 |
1457 | [[package]]
1458 | name = "wasm-bindgen-shared"
1459 | version = "0.2.100"
1460 | source = "registry+https://github.com/rust-lang/crates.io-index"
1461 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
1462 | dependencies = [
1463 | "unicode-ident",
1464 | ]
1465 |
1466 | [[package]]
1467 | name = "web-time"
1468 | version = "1.1.0"
1469 | source = "registry+https://github.com/rust-lang/crates.io-index"
1470 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
1471 | dependencies = [
1472 | "js-sys",
1473 | "wasm-bindgen",
1474 | ]
1475 |
1476 | [[package]]
1477 | name = "winapi-util"
1478 | version = "0.1.9"
1479 | source = "registry+https://github.com/rust-lang/crates.io-index"
1480 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
1481 | dependencies = [
1482 | "windows-sys 0.59.0",
1483 | ]
1484 |
1485 | [[package]]
1486 | name = "windows-core"
1487 | version = "0.52.0"
1488 | source = "registry+https://github.com/rust-lang/crates.io-index"
1489 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
1490 | dependencies = [
1491 | "windows-targets",
1492 | ]
1493 |
1494 | [[package]]
1495 | name = "windows-sys"
1496 | version = "0.52.0"
1497 | source = "registry+https://github.com/rust-lang/crates.io-index"
1498 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
1499 | dependencies = [
1500 | "windows-targets",
1501 | ]
1502 |
1503 | [[package]]
1504 | name = "windows-sys"
1505 | version = "0.59.0"
1506 | source = "registry+https://github.com/rust-lang/crates.io-index"
1507 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
1508 | dependencies = [
1509 | "windows-targets",
1510 | ]
1511 |
1512 | [[package]]
1513 | name = "windows-targets"
1514 | version = "0.52.6"
1515 | source = "registry+https://github.com/rust-lang/crates.io-index"
1516 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
1517 | dependencies = [
1518 | "windows_aarch64_gnullvm",
1519 | "windows_aarch64_msvc",
1520 | "windows_i686_gnu",
1521 | "windows_i686_gnullvm",
1522 | "windows_i686_msvc",
1523 | "windows_x86_64_gnu",
1524 | "windows_x86_64_gnullvm",
1525 | "windows_x86_64_msvc",
1526 | ]
1527 |
1528 | [[package]]
1529 | name = "windows_aarch64_gnullvm"
1530 | version = "0.52.6"
1531 | source = "registry+https://github.com/rust-lang/crates.io-index"
1532 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
1533 |
1534 | [[package]]
1535 | name = "windows_aarch64_msvc"
1536 | version = "0.52.6"
1537 | source = "registry+https://github.com/rust-lang/crates.io-index"
1538 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
1539 |
1540 | [[package]]
1541 | name = "windows_i686_gnu"
1542 | version = "0.52.6"
1543 | source = "registry+https://github.com/rust-lang/crates.io-index"
1544 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
1545 |
1546 | [[package]]
1547 | name = "windows_i686_gnullvm"
1548 | version = "0.52.6"
1549 | source = "registry+https://github.com/rust-lang/crates.io-index"
1550 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
1551 |
1552 | [[package]]
1553 | name = "windows_i686_msvc"
1554 | version = "0.52.6"
1555 | source = "registry+https://github.com/rust-lang/crates.io-index"
1556 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
1557 |
1558 | [[package]]
1559 | name = "windows_x86_64_gnu"
1560 | version = "0.52.6"
1561 | source = "registry+https://github.com/rust-lang/crates.io-index"
1562 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
1563 |
1564 | [[package]]
1565 | name = "windows_x86_64_gnullvm"
1566 | version = "0.52.6"
1567 | source = "registry+https://github.com/rust-lang/crates.io-index"
1568 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
1569 |
1570 | [[package]]
1571 | name = "windows_x86_64_msvc"
1572 | version = "0.52.6"
1573 | source = "registry+https://github.com/rust-lang/crates.io-index"
1574 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
1575 |
1576 | [[package]]
1577 | name = "wyz"
1578 | version = "0.5.1"
1579 | source = "registry+https://github.com/rust-lang/crates.io-index"
1580 | checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
1581 | dependencies = [
1582 | "tap",
1583 | ]
1584 |
1585 | [[package]]
1586 | name = "zopfli"
1587 | version = "0.8.1"
1588 | source = "registry+https://github.com/rust-lang/crates.io-index"
1589 | checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946"
1590 | dependencies = [
1591 | "bumpalo",
1592 | "crc32fast",
1593 | "lockfree-object-pool",
1594 | "log",
1595 | "once_cell",
1596 | "simd-adler32",
1597 | ]
1598 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | resolver = "2"
3 | members = ["kompari", "kompari-cli", "kompari-html", "kompari-tasks"]
4 |
5 | [workspace.package]
6 | version = "0.1.0"
7 | edition = "2021"
8 | # Keep in sync with RUST_MIN_VER in .github/workflows/ci.yml, with the relevant README.md files,
9 | # and with the MSRV in the Unreleased section of CHANGELOG.md.
10 | rust-version = "1.78"
11 | license = "Apache-2.0 OR MIT"
12 | repository = "https://github.com/linebender/kompari"
13 |
14 | [workspace.dependencies]
15 | image = { version = "0.25", default-features = false, features = ["png"] }
16 | thiserror = { version = "2" }
17 | clap = { version = "4.5", features = ["derive"] }
18 | oxipng = { version = "9.1", features = [
19 | "parallel",
20 | "zopfli",
21 | "filetime",
22 | ], default-features = false }
23 | rayon = "1.10" # Make sure that we are using the version as in oxipng
24 | log = "0.4"
25 |
26 | [workspace.lints]
27 | # This one may vary depending on the project.
28 | rust.unsafe_code = "forbid"
29 |
30 | # LINEBENDER LINT SET - Cargo.toml - v6
31 | # See https://linebender.org/wiki/canonical-lints/
32 | rust.keyword_idents_2024 = "forbid"
33 | rust.non_ascii_idents = "forbid"
34 | rust.non_local_definitions = "forbid"
35 | rust.unsafe_op_in_unsafe_fn = "forbid"
36 |
37 | rust.elided_lifetimes_in_paths = "warn"
38 | rust.missing_debug_implementations = "warn"
39 | # rust.missing_docs = "warn"
40 | # TODO: We should document things
41 | rust.trivial_numeric_casts = "warn"
42 | rust.unexpected_cfgs = "warn"
43 | rust.unnameable_types = "warn"
44 | rust.unreachable_pub = "warn"
45 | rust.unused_import_braces = "warn"
46 | rust.unused_lifetimes = "warn"
47 | rust.unused_macro_rules = "warn"
48 |
49 | clippy.too_many_arguments = "allow"
50 |
51 | clippy.allow_attributes_without_reason = "warn"
52 | clippy.cast_possible_truncation = "warn"
53 | clippy.collection_is_never_read = "warn"
54 | clippy.dbg_macro = "warn"
55 | clippy.debug_assert_with_mut_call = "warn"
56 | clippy.doc_markdown = "warn"
57 | clippy.fn_to_numeric_cast_any = "warn"
58 | clippy.infinite_loop = "warn"
59 | clippy.large_stack_arrays = "warn"
60 | clippy.mismatching_type_param_order = "warn"
61 | clippy.missing_assert_message = "warn"
62 | clippy.missing_fields_in_debug = "warn"
63 | clippy.same_functions_in_if_condition = "warn"
64 | clippy.semicolon_if_nothing_returned = "warn"
65 | clippy.should_panic_without_expect = "warn"
66 | clippy.todo = "warn"
67 | clippy.unseparated_literal_suffix = "warn"
68 | clippy.use_self = "warn"
69 |
70 | clippy.cargo_common_metadata = "warn"
71 | clippy.negative_feature_names = "warn"
72 | clippy.redundant_feature_names = "warn"
73 | clippy.wildcard_dependencies = "warn"
74 | # END LINEBENDER LINT SET
75 |
76 | [profile.ci]
77 | inherits = "dev"
78 | [profile.ci.package."*"]
79 | debug-assertions = true # Keep always on for dependencies for cache reuse.
80 |
--------------------------------------------------------------------------------
/LICENSE-APACHE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | Copyright 2020 the Kompari Authors
2 |
3 | Permission is hereby granted, free of charge, to any
4 | person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the
6 | Software without restriction, including without
7 | limitation the rights to use, copy, modify, merge,
8 | publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software
10 | is furnished to do so, subject to the following
11 | conditions:
12 |
13 | The above copyright notice and this permission notice
14 | shall be included in all copies or substantial portions
15 | of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 | DEALINGS IN THE SOFTWARE.
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # Kompari
6 |
7 | *Kompari* is a tool for reporting image differences. It is intended for use in snapshot testing.
8 | It can be used as a stand-alone CLI tool or as a Rust crate.
9 |
10 |
11 |
12 |
13 | ## CLI
14 |
15 | ### Local build
16 |
17 | ```commandline
18 | $ cargo build --release
19 | ```
20 |
21 | ### Usage
22 |
23 | Create static HTML report:
24 |
25 | ```commandline
26 | $ cargo run --release report
27 | ```
28 |
29 | Start HTTP server for interactive test blessing:
30 |
31 | ```commandline
32 | $ cargo run --release review
33 | ```
34 |
35 |
36 | ## Minimum supported Rust Version (MSRV)
37 |
38 | This version of Kompari has been verified to compile with **Rust 1.78** and later.
39 |
40 | Future versions of Kompari might increase the Rust version requirement.
41 | It will not be treated as a breaking change and as such can even happen with small patch releases.
42 |
43 |
44 | Click here if compiling fails.
45 |
46 | As time has passed, some of Kompari's dependencies could have released versions with a higher Rust requirement.
47 | If you encounter a compilation issue due to a dependency and don't want to upgrade your Rust toolchain, then you could downgrade the dependency.
48 |
49 | ```sh
50 | # Use the problematic dependency's name and version
51 | cargo update -p package_name --precise 0.1.1
52 | ```
53 |
54 |
55 |
56 | ## License
57 |
58 | Licensed under either of
59 |
60 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or )
61 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or )
62 |
63 | at your option.
64 |
--------------------------------------------------------------------------------
/docs/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linebender/kompari/4b851413e1b17307064aa48c50e59d7e29656543/docs/logo.png
--------------------------------------------------------------------------------
/docs/logo_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linebender/kompari/4b851413e1b17307064aa48c50e59d7e29656543/docs/logo_small.png
--------------------------------------------------------------------------------
/docs/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linebender/kompari/4b851413e1b17307064aa48c50e59d7e29656543/docs/screenshot.png
--------------------------------------------------------------------------------
/example/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | [alias]
2 | xtask = "run --package xtask_kompari --"
3 |
--------------------------------------------------------------------------------
/example/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 4
4 |
5 | [[package]]
6 | name = "addr2line"
7 | version = "0.24.2"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
10 | dependencies = [
11 | "gimli",
12 | ]
13 |
14 | [[package]]
15 | name = "adler2"
16 | version = "2.0.0"
17 | source = "registry+https://github.com/rust-lang/crates.io-index"
18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
19 |
20 | [[package]]
21 | name = "android-tzdata"
22 | version = "0.1.1"
23 | source = "registry+https://github.com/rust-lang/crates.io-index"
24 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
25 |
26 | [[package]]
27 | name = "android_system_properties"
28 | version = "0.1.5"
29 | source = "registry+https://github.com/rust-lang/crates.io-index"
30 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
31 | dependencies = [
32 | "libc",
33 | ]
34 |
35 | [[package]]
36 | name = "anstream"
37 | version = "0.6.18"
38 | source = "registry+https://github.com/rust-lang/crates.io-index"
39 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
40 | dependencies = [
41 | "anstyle",
42 | "anstyle-parse",
43 | "anstyle-query",
44 | "anstyle-wincon",
45 | "colorchoice",
46 | "is_terminal_polyfill",
47 | "utf8parse",
48 | ]
49 |
50 | [[package]]
51 | name = "anstyle"
52 | version = "1.0.10"
53 | source = "registry+https://github.com/rust-lang/crates.io-index"
54 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
55 |
56 | [[package]]
57 | name = "anstyle-parse"
58 | version = "0.2.6"
59 | source = "registry+https://github.com/rust-lang/crates.io-index"
60 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
61 | dependencies = [
62 | "utf8parse",
63 | ]
64 |
65 | [[package]]
66 | name = "anstyle-query"
67 | version = "1.1.2"
68 | source = "registry+https://github.com/rust-lang/crates.io-index"
69 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
70 | dependencies = [
71 | "windows-sys 0.59.0",
72 | ]
73 |
74 | [[package]]
75 | name = "anstyle-wincon"
76 | version = "3.0.6"
77 | source = "registry+https://github.com/rust-lang/crates.io-index"
78 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
79 | dependencies = [
80 | "anstyle",
81 | "windows-sys 0.59.0",
82 | ]
83 |
84 | [[package]]
85 | name = "autocfg"
86 | version = "1.4.0"
87 | source = "registry+https://github.com/rust-lang/crates.io-index"
88 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
89 |
90 | [[package]]
91 | name = "axum"
92 | version = "0.8.1"
93 | source = "registry+https://github.com/rust-lang/crates.io-index"
94 | checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8"
95 | dependencies = [
96 | "axum-core",
97 | "bytes",
98 | "form_urlencoded",
99 | "futures-util",
100 | "http",
101 | "http-body",
102 | "http-body-util",
103 | "hyper",
104 | "hyper-util",
105 | "itoa",
106 | "matchit",
107 | "memchr",
108 | "mime",
109 | "percent-encoding",
110 | "pin-project-lite",
111 | "rustversion",
112 | "serde",
113 | "serde_json",
114 | "serde_path_to_error",
115 | "serde_urlencoded",
116 | "sync_wrapper",
117 | "tokio",
118 | "tower",
119 | "tower-layer",
120 | "tower-service",
121 | "tracing",
122 | ]
123 |
124 | [[package]]
125 | name = "axum-core"
126 | version = "0.5.0"
127 | source = "registry+https://github.com/rust-lang/crates.io-index"
128 | checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733"
129 | dependencies = [
130 | "bytes",
131 | "futures-util",
132 | "http",
133 | "http-body",
134 | "http-body-util",
135 | "mime",
136 | "pin-project-lite",
137 | "rustversion",
138 | "sync_wrapper",
139 | "tower-layer",
140 | "tower-service",
141 | "tracing",
142 | ]
143 |
144 | [[package]]
145 | name = "backtrace"
146 | version = "0.3.74"
147 | source = "registry+https://github.com/rust-lang/crates.io-index"
148 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
149 | dependencies = [
150 | "addr2line",
151 | "cfg-if",
152 | "libc",
153 | "miniz_oxide",
154 | "object",
155 | "rustc-demangle",
156 | "windows-targets",
157 | ]
158 |
159 | [[package]]
160 | name = "base64"
161 | version = "0.22.1"
162 | source = "registry+https://github.com/rust-lang/crates.io-index"
163 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
164 |
165 | [[package]]
166 | name = "bitflags"
167 | version = "1.3.2"
168 | source = "registry+https://github.com/rust-lang/crates.io-index"
169 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
170 |
171 | [[package]]
172 | name = "bitflags"
173 | version = "2.9.0"
174 | source = "registry+https://github.com/rust-lang/crates.io-index"
175 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
176 |
177 | [[package]]
178 | name = "bitvec"
179 | version = "1.0.1"
180 | source = "registry+https://github.com/rust-lang/crates.io-index"
181 | checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
182 | dependencies = [
183 | "funty",
184 | "radium",
185 | "tap",
186 | "wyz",
187 | ]
188 |
189 | [[package]]
190 | name = "bumpalo"
191 | version = "3.16.0"
192 | source = "registry+https://github.com/rust-lang/crates.io-index"
193 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
194 |
195 | [[package]]
196 | name = "bytemuck"
197 | version = "1.20.0"
198 | source = "registry+https://github.com/rust-lang/crates.io-index"
199 | checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a"
200 |
201 | [[package]]
202 | name = "byteorder-lite"
203 | version = "0.1.0"
204 | source = "registry+https://github.com/rust-lang/crates.io-index"
205 | checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
206 |
207 | [[package]]
208 | name = "bytes"
209 | version = "1.9.0"
210 | source = "registry+https://github.com/rust-lang/crates.io-index"
211 | checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
212 |
213 | [[package]]
214 | name = "cc"
215 | version = "1.2.3"
216 | source = "registry+https://github.com/rust-lang/crates.io-index"
217 | checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d"
218 | dependencies = [
219 | "shlex",
220 | ]
221 |
222 | [[package]]
223 | name = "cfg-if"
224 | version = "1.0.0"
225 | source = "registry+https://github.com/rust-lang/crates.io-index"
226 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
227 |
228 | [[package]]
229 | name = "chrono"
230 | version = "0.4.39"
231 | source = "registry+https://github.com/rust-lang/crates.io-index"
232 | checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
233 | dependencies = [
234 | "android-tzdata",
235 | "iana-time-zone",
236 | "js-sys",
237 | "num-traits",
238 | "wasm-bindgen",
239 | "windows-targets",
240 | ]
241 |
242 | [[package]]
243 | name = "clap"
244 | version = "4.5.23"
245 | source = "registry+https://github.com/rust-lang/crates.io-index"
246 | checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
247 | dependencies = [
248 | "clap_builder",
249 | "clap_derive",
250 | ]
251 |
252 | [[package]]
253 | name = "clap_builder"
254 | version = "4.5.23"
255 | source = "registry+https://github.com/rust-lang/crates.io-index"
256 | checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
257 | dependencies = [
258 | "anstream",
259 | "anstyle",
260 | "clap_lex",
261 | "strsim",
262 | ]
263 |
264 | [[package]]
265 | name = "clap_derive"
266 | version = "4.5.18"
267 | source = "registry+https://github.com/rust-lang/crates.io-index"
268 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
269 | dependencies = [
270 | "heck",
271 | "proc-macro2",
272 | "quote",
273 | "syn",
274 | ]
275 |
276 | [[package]]
277 | name = "clap_lex"
278 | version = "0.7.4"
279 | source = "registry+https://github.com/rust-lang/crates.io-index"
280 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
281 |
282 | [[package]]
283 | name = "colorchoice"
284 | version = "1.0.3"
285 | source = "registry+https://github.com/rust-lang/crates.io-index"
286 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
287 |
288 | [[package]]
289 | name = "console"
290 | version = "0.15.11"
291 | source = "registry+https://github.com/rust-lang/crates.io-index"
292 | checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
293 | dependencies = [
294 | "encode_unicode",
295 | "libc",
296 | "once_cell",
297 | "unicode-width",
298 | "windows-sys 0.59.0",
299 | ]
300 |
301 | [[package]]
302 | name = "core-foundation-sys"
303 | version = "0.8.7"
304 | source = "registry+https://github.com/rust-lang/crates.io-index"
305 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
306 |
307 | [[package]]
308 | name = "crc32fast"
309 | version = "1.4.2"
310 | source = "registry+https://github.com/rust-lang/crates.io-index"
311 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
312 | dependencies = [
313 | "cfg-if",
314 | ]
315 |
316 | [[package]]
317 | name = "crossbeam-channel"
318 | version = "0.5.14"
319 | source = "registry+https://github.com/rust-lang/crates.io-index"
320 | checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471"
321 | dependencies = [
322 | "crossbeam-utils",
323 | ]
324 |
325 | [[package]]
326 | name = "crossbeam-deque"
327 | version = "0.8.6"
328 | source = "registry+https://github.com/rust-lang/crates.io-index"
329 | checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
330 | dependencies = [
331 | "crossbeam-epoch",
332 | "crossbeam-utils",
333 | ]
334 |
335 | [[package]]
336 | name = "crossbeam-epoch"
337 | version = "0.9.18"
338 | source = "registry+https://github.com/rust-lang/crates.io-index"
339 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
340 | dependencies = [
341 | "crossbeam-utils",
342 | ]
343 |
344 | [[package]]
345 | name = "crossbeam-utils"
346 | version = "0.8.21"
347 | source = "registry+https://github.com/rust-lang/crates.io-index"
348 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
349 |
350 | [[package]]
351 | name = "demolib"
352 | version = "0.1.0"
353 | dependencies = [
354 | "image",
355 | ]
356 |
357 | [[package]]
358 | name = "either"
359 | version = "1.14.0"
360 | source = "registry+https://github.com/rust-lang/crates.io-index"
361 | checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d"
362 |
363 | [[package]]
364 | name = "encode_unicode"
365 | version = "1.0.0"
366 | source = "registry+https://github.com/rust-lang/crates.io-index"
367 | checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
368 |
369 | [[package]]
370 | name = "equivalent"
371 | version = "1.0.2"
372 | source = "registry+https://github.com/rust-lang/crates.io-index"
373 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
374 |
375 | [[package]]
376 | name = "fdeflate"
377 | version = "0.3.7"
378 | source = "registry+https://github.com/rust-lang/crates.io-index"
379 | checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
380 | dependencies = [
381 | "simd-adler32",
382 | ]
383 |
384 | [[package]]
385 | name = "filetime"
386 | version = "0.2.25"
387 | source = "registry+https://github.com/rust-lang/crates.io-index"
388 | checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
389 | dependencies = [
390 | "cfg-if",
391 | "libc",
392 | "libredox",
393 | "windows-sys 0.59.0",
394 | ]
395 |
396 | [[package]]
397 | name = "flate2"
398 | version = "1.0.35"
399 | source = "registry+https://github.com/rust-lang/crates.io-index"
400 | checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
401 | dependencies = [
402 | "crc32fast",
403 | "miniz_oxide",
404 | ]
405 |
406 | [[package]]
407 | name = "fnv"
408 | version = "1.0.7"
409 | source = "registry+https://github.com/rust-lang/crates.io-index"
410 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
411 |
412 | [[package]]
413 | name = "form_urlencoded"
414 | version = "1.2.1"
415 | source = "registry+https://github.com/rust-lang/crates.io-index"
416 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
417 | dependencies = [
418 | "percent-encoding",
419 | ]
420 |
421 | [[package]]
422 | name = "funty"
423 | version = "2.0.0"
424 | source = "registry+https://github.com/rust-lang/crates.io-index"
425 | checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
426 |
427 | [[package]]
428 | name = "futures-channel"
429 | version = "0.3.31"
430 | source = "registry+https://github.com/rust-lang/crates.io-index"
431 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
432 | dependencies = [
433 | "futures-core",
434 | ]
435 |
436 | [[package]]
437 | name = "futures-core"
438 | version = "0.3.31"
439 | source = "registry+https://github.com/rust-lang/crates.io-index"
440 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
441 |
442 | [[package]]
443 | name = "futures-task"
444 | version = "0.3.31"
445 | source = "registry+https://github.com/rust-lang/crates.io-index"
446 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
447 |
448 | [[package]]
449 | name = "futures-util"
450 | version = "0.3.31"
451 | source = "registry+https://github.com/rust-lang/crates.io-index"
452 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
453 | dependencies = [
454 | "futures-core",
455 | "futures-task",
456 | "pin-project-lite",
457 | "pin-utils",
458 | ]
459 |
460 | [[package]]
461 | name = "gimli"
462 | version = "0.31.1"
463 | source = "registry+https://github.com/rust-lang/crates.io-index"
464 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
465 |
466 | [[package]]
467 | name = "hashbrown"
468 | version = "0.15.2"
469 | source = "registry+https://github.com/rust-lang/crates.io-index"
470 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
471 |
472 | [[package]]
473 | name = "heck"
474 | version = "0.5.0"
475 | source = "registry+https://github.com/rust-lang/crates.io-index"
476 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
477 |
478 | [[package]]
479 | name = "http"
480 | version = "1.2.0"
481 | source = "registry+https://github.com/rust-lang/crates.io-index"
482 | checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
483 | dependencies = [
484 | "bytes",
485 | "fnv",
486 | "itoa",
487 | ]
488 |
489 | [[package]]
490 | name = "http-body"
491 | version = "1.0.1"
492 | source = "registry+https://github.com/rust-lang/crates.io-index"
493 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
494 | dependencies = [
495 | "bytes",
496 | "http",
497 | ]
498 |
499 | [[package]]
500 | name = "http-body-util"
501 | version = "0.1.2"
502 | source = "registry+https://github.com/rust-lang/crates.io-index"
503 | checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
504 | dependencies = [
505 | "bytes",
506 | "futures-util",
507 | "http",
508 | "http-body",
509 | "pin-project-lite",
510 | ]
511 |
512 | [[package]]
513 | name = "httparse"
514 | version = "1.9.5"
515 | source = "registry+https://github.com/rust-lang/crates.io-index"
516 | checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946"
517 |
518 | [[package]]
519 | name = "httpdate"
520 | version = "1.0.3"
521 | source = "registry+https://github.com/rust-lang/crates.io-index"
522 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
523 |
524 | [[package]]
525 | name = "humansize"
526 | version = "2.1.3"
527 | source = "registry+https://github.com/rust-lang/crates.io-index"
528 | checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
529 | dependencies = [
530 | "libm",
531 | ]
532 |
533 | [[package]]
534 | name = "hyper"
535 | version = "1.5.2"
536 | source = "registry+https://github.com/rust-lang/crates.io-index"
537 | checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0"
538 | dependencies = [
539 | "bytes",
540 | "futures-channel",
541 | "futures-util",
542 | "http",
543 | "http-body",
544 | "httparse",
545 | "httpdate",
546 | "itoa",
547 | "pin-project-lite",
548 | "smallvec",
549 | "tokio",
550 | ]
551 |
552 | [[package]]
553 | name = "hyper-util"
554 | version = "0.1.10"
555 | source = "registry+https://github.com/rust-lang/crates.io-index"
556 | checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4"
557 | dependencies = [
558 | "bytes",
559 | "futures-util",
560 | "http",
561 | "http-body",
562 | "hyper",
563 | "pin-project-lite",
564 | "tokio",
565 | "tower-service",
566 | ]
567 |
568 | [[package]]
569 | name = "iana-time-zone"
570 | version = "0.1.61"
571 | source = "registry+https://github.com/rust-lang/crates.io-index"
572 | checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
573 | dependencies = [
574 | "android_system_properties",
575 | "core-foundation-sys",
576 | "iana-time-zone-haiku",
577 | "js-sys",
578 | "wasm-bindgen",
579 | "windows-core",
580 | ]
581 |
582 | [[package]]
583 | name = "iana-time-zone-haiku"
584 | version = "0.1.2"
585 | source = "registry+https://github.com/rust-lang/crates.io-index"
586 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
587 | dependencies = [
588 | "cc",
589 | ]
590 |
591 | [[package]]
592 | name = "image"
593 | version = "0.25.5"
594 | source = "registry+https://github.com/rust-lang/crates.io-index"
595 | checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b"
596 | dependencies = [
597 | "bytemuck",
598 | "byteorder-lite",
599 | "num-traits",
600 | "png",
601 | ]
602 |
603 | [[package]]
604 | name = "imagesize"
605 | version = "0.13.0"
606 | source = "registry+https://github.com/rust-lang/crates.io-index"
607 | checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285"
608 |
609 | [[package]]
610 | name = "indexmap"
611 | version = "2.7.1"
612 | source = "registry+https://github.com/rust-lang/crates.io-index"
613 | checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
614 | dependencies = [
615 | "equivalent",
616 | "hashbrown",
617 | "rayon",
618 | ]
619 |
620 | [[package]]
621 | name = "indicatif"
622 | version = "0.17.11"
623 | source = "registry+https://github.com/rust-lang/crates.io-index"
624 | checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235"
625 | dependencies = [
626 | "console",
627 | "number_prefix",
628 | "portable-atomic",
629 | "unicode-width",
630 | "web-time",
631 | ]
632 |
633 | [[package]]
634 | name = "is_terminal_polyfill"
635 | version = "1.70.1"
636 | source = "registry+https://github.com/rust-lang/crates.io-index"
637 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
638 |
639 | [[package]]
640 | name = "itoa"
641 | version = "1.0.14"
642 | source = "registry+https://github.com/rust-lang/crates.io-index"
643 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
644 |
645 | [[package]]
646 | name = "js-sys"
647 | version = "0.3.76"
648 | source = "registry+https://github.com/rust-lang/crates.io-index"
649 | checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7"
650 | dependencies = [
651 | "once_cell",
652 | "wasm-bindgen",
653 | ]
654 |
655 | [[package]]
656 | name = "kompari"
657 | version = "0.1.0"
658 | dependencies = [
659 | "image",
660 | "log",
661 | "oxipng",
662 | "rayon",
663 | "thiserror",
664 | "walkdir",
665 | ]
666 |
667 | [[package]]
668 | name = "kompari-html"
669 | version = "0.1.0"
670 | dependencies = [
671 | "axum",
672 | "base64",
673 | "chrono",
674 | "imagesize",
675 | "kompari",
676 | "maud",
677 | "rayon",
678 | "serde",
679 | "tokio",
680 | ]
681 |
682 | [[package]]
683 | name = "kompari-tasks"
684 | version = "0.1.0"
685 | dependencies = [
686 | "clap",
687 | "humansize",
688 | "indicatif",
689 | "kompari",
690 | "kompari-html",
691 | "rayon",
692 | "termcolor",
693 | ]
694 |
695 | [[package]]
696 | name = "libc"
697 | version = "0.2.168"
698 | source = "registry+https://github.com/rust-lang/crates.io-index"
699 | checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
700 |
701 | [[package]]
702 | name = "libdeflate-sys"
703 | version = "1.23.1"
704 | source = "registry+https://github.com/rust-lang/crates.io-index"
705 | checksum = "38b72ad3fbf5ac78f2df7b36075e48adf2459b57c150b9e63937d0204d0f9cd7"
706 | dependencies = [
707 | "cc",
708 | ]
709 |
710 | [[package]]
711 | name = "libdeflater"
712 | version = "1.23.1"
713 | source = "registry+https://github.com/rust-lang/crates.io-index"
714 | checksum = "013344b17f9dceddff4872559ae19378bd8ee0479eccdd266d2dd2e894b4792f"
715 | dependencies = [
716 | "libdeflate-sys",
717 | ]
718 |
719 | [[package]]
720 | name = "libm"
721 | version = "0.2.11"
722 | source = "registry+https://github.com/rust-lang/crates.io-index"
723 | checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
724 |
725 | [[package]]
726 | name = "libredox"
727 | version = "0.1.3"
728 | source = "registry+https://github.com/rust-lang/crates.io-index"
729 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
730 | dependencies = [
731 | "bitflags 2.9.0",
732 | "libc",
733 | "redox_syscall",
734 | ]
735 |
736 | [[package]]
737 | name = "lockfree-object-pool"
738 | version = "0.1.6"
739 | source = "registry+https://github.com/rust-lang/crates.io-index"
740 | checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e"
741 |
742 | [[package]]
743 | name = "log"
744 | version = "0.4.22"
745 | source = "registry+https://github.com/rust-lang/crates.io-index"
746 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
747 |
748 | [[package]]
749 | name = "matchit"
750 | version = "0.8.4"
751 | source = "registry+https://github.com/rust-lang/crates.io-index"
752 | checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
753 |
754 | [[package]]
755 | name = "maud"
756 | version = "0.27.0"
757 | source = "registry+https://github.com/rust-lang/crates.io-index"
758 | checksum = "8156733e27020ea5c684db5beac5d1d611e1272ab17901a49466294b84fc217e"
759 | dependencies = [
760 | "itoa",
761 | "maud_macros",
762 | ]
763 |
764 | [[package]]
765 | name = "maud_macros"
766 | version = "0.27.0"
767 | source = "registry+https://github.com/rust-lang/crates.io-index"
768 | checksum = "7261b00f3952f617899bc012e3dbd56e4f0110a038175929fa5d18e5a19913ca"
769 | dependencies = [
770 | "proc-macro2",
771 | "proc-macro2-diagnostics",
772 | "quote",
773 | "syn",
774 | ]
775 |
776 | [[package]]
777 | name = "memchr"
778 | version = "2.7.4"
779 | source = "registry+https://github.com/rust-lang/crates.io-index"
780 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
781 |
782 | [[package]]
783 | name = "mime"
784 | version = "0.3.17"
785 | source = "registry+https://github.com/rust-lang/crates.io-index"
786 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
787 |
788 | [[package]]
789 | name = "miniz_oxide"
790 | version = "0.8.0"
791 | source = "registry+https://github.com/rust-lang/crates.io-index"
792 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
793 | dependencies = [
794 | "adler2",
795 | "simd-adler32",
796 | ]
797 |
798 | [[package]]
799 | name = "mio"
800 | version = "1.0.3"
801 | source = "registry+https://github.com/rust-lang/crates.io-index"
802 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
803 | dependencies = [
804 | "libc",
805 | "wasi",
806 | "windows-sys 0.52.0",
807 | ]
808 |
809 | [[package]]
810 | name = "num-traits"
811 | version = "0.2.19"
812 | source = "registry+https://github.com/rust-lang/crates.io-index"
813 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
814 | dependencies = [
815 | "autocfg",
816 | ]
817 |
818 | [[package]]
819 | name = "number_prefix"
820 | version = "0.4.0"
821 | source = "registry+https://github.com/rust-lang/crates.io-index"
822 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
823 |
824 | [[package]]
825 | name = "object"
826 | version = "0.36.7"
827 | source = "registry+https://github.com/rust-lang/crates.io-index"
828 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
829 | dependencies = [
830 | "memchr",
831 | ]
832 |
833 | [[package]]
834 | name = "once_cell"
835 | version = "1.20.2"
836 | source = "registry+https://github.com/rust-lang/crates.io-index"
837 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
838 |
839 | [[package]]
840 | name = "oxipng"
841 | version = "9.1.4"
842 | source = "registry+https://github.com/rust-lang/crates.io-index"
843 | checksum = "3bce05680d3f2ec3f0510f19608d56712fa7ea681b4ba293c3b74a04c2e55279"
844 | dependencies = [
845 | "bitvec",
846 | "crossbeam-channel",
847 | "filetime",
848 | "indexmap",
849 | "libdeflater",
850 | "log",
851 | "rayon",
852 | "rgb",
853 | "rustc-hash",
854 | "zopfli",
855 | ]
856 |
857 | [[package]]
858 | name = "percent-encoding"
859 | version = "2.3.1"
860 | source = "registry+https://github.com/rust-lang/crates.io-index"
861 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
862 |
863 | [[package]]
864 | name = "pin-project-lite"
865 | version = "0.2.16"
866 | source = "registry+https://github.com/rust-lang/crates.io-index"
867 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
868 |
869 | [[package]]
870 | name = "pin-utils"
871 | version = "0.1.0"
872 | source = "registry+https://github.com/rust-lang/crates.io-index"
873 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
874 |
875 | [[package]]
876 | name = "png"
877 | version = "0.17.15"
878 | source = "registry+https://github.com/rust-lang/crates.io-index"
879 | checksum = "b67582bd5b65bdff614270e2ea89a1cf15bef71245cc1e5f7ea126977144211d"
880 | dependencies = [
881 | "bitflags 1.3.2",
882 | "crc32fast",
883 | "fdeflate",
884 | "flate2",
885 | "miniz_oxide",
886 | ]
887 |
888 | [[package]]
889 | name = "portable-atomic"
890 | version = "1.11.0"
891 | source = "registry+https://github.com/rust-lang/crates.io-index"
892 | checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
893 |
894 | [[package]]
895 | name = "proc-macro2"
896 | version = "1.0.92"
897 | source = "registry+https://github.com/rust-lang/crates.io-index"
898 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
899 | dependencies = [
900 | "unicode-ident",
901 | ]
902 |
903 | [[package]]
904 | name = "proc-macro2-diagnostics"
905 | version = "0.10.1"
906 | source = "registry+https://github.com/rust-lang/crates.io-index"
907 | checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
908 | dependencies = [
909 | "proc-macro2",
910 | "quote",
911 | "syn",
912 | "version_check",
913 | ]
914 |
915 | [[package]]
916 | name = "quote"
917 | version = "1.0.37"
918 | source = "registry+https://github.com/rust-lang/crates.io-index"
919 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
920 | dependencies = [
921 | "proc-macro2",
922 | ]
923 |
924 | [[package]]
925 | name = "radium"
926 | version = "0.7.0"
927 | source = "registry+https://github.com/rust-lang/crates.io-index"
928 | checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
929 |
930 | [[package]]
931 | name = "rayon"
932 | version = "1.10.0"
933 | source = "registry+https://github.com/rust-lang/crates.io-index"
934 | checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
935 | dependencies = [
936 | "either",
937 | "rayon-core",
938 | ]
939 |
940 | [[package]]
941 | name = "rayon-core"
942 | version = "1.12.1"
943 | source = "registry+https://github.com/rust-lang/crates.io-index"
944 | checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
945 | dependencies = [
946 | "crossbeam-deque",
947 | "crossbeam-utils",
948 | ]
949 |
950 | [[package]]
951 | name = "redox_syscall"
952 | version = "0.5.10"
953 | source = "registry+https://github.com/rust-lang/crates.io-index"
954 | checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
955 | dependencies = [
956 | "bitflags 2.9.0",
957 | ]
958 |
959 | [[package]]
960 | name = "rgb"
961 | version = "0.8.50"
962 | source = "registry+https://github.com/rust-lang/crates.io-index"
963 | checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a"
964 | dependencies = [
965 | "bytemuck",
966 | ]
967 |
968 | [[package]]
969 | name = "rustc-demangle"
970 | version = "0.1.24"
971 | source = "registry+https://github.com/rust-lang/crates.io-index"
972 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
973 |
974 | [[package]]
975 | name = "rustc-hash"
976 | version = "2.1.1"
977 | source = "registry+https://github.com/rust-lang/crates.io-index"
978 | checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
979 |
980 | [[package]]
981 | name = "rustversion"
982 | version = "1.0.19"
983 | source = "registry+https://github.com/rust-lang/crates.io-index"
984 | checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
985 |
986 | [[package]]
987 | name = "ryu"
988 | version = "1.0.18"
989 | source = "registry+https://github.com/rust-lang/crates.io-index"
990 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
991 |
992 | [[package]]
993 | name = "same-file"
994 | version = "1.0.6"
995 | source = "registry+https://github.com/rust-lang/crates.io-index"
996 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
997 | dependencies = [
998 | "winapi-util",
999 | ]
1000 |
1001 | [[package]]
1002 | name = "serde"
1003 | version = "1.0.217"
1004 | source = "registry+https://github.com/rust-lang/crates.io-index"
1005 | checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
1006 | dependencies = [
1007 | "serde_derive",
1008 | ]
1009 |
1010 | [[package]]
1011 | name = "serde_derive"
1012 | version = "1.0.217"
1013 | source = "registry+https://github.com/rust-lang/crates.io-index"
1014 | checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
1015 | dependencies = [
1016 | "proc-macro2",
1017 | "quote",
1018 | "syn",
1019 | ]
1020 |
1021 | [[package]]
1022 | name = "serde_json"
1023 | version = "1.0.137"
1024 | source = "registry+https://github.com/rust-lang/crates.io-index"
1025 | checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b"
1026 | dependencies = [
1027 | "itoa",
1028 | "memchr",
1029 | "ryu",
1030 | "serde",
1031 | ]
1032 |
1033 | [[package]]
1034 | name = "serde_path_to_error"
1035 | version = "0.1.16"
1036 | source = "registry+https://github.com/rust-lang/crates.io-index"
1037 | checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6"
1038 | dependencies = [
1039 | "itoa",
1040 | "serde",
1041 | ]
1042 |
1043 | [[package]]
1044 | name = "serde_urlencoded"
1045 | version = "0.7.1"
1046 | source = "registry+https://github.com/rust-lang/crates.io-index"
1047 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
1048 | dependencies = [
1049 | "form_urlencoded",
1050 | "itoa",
1051 | "ryu",
1052 | "serde",
1053 | ]
1054 |
1055 | [[package]]
1056 | name = "shlex"
1057 | version = "1.3.0"
1058 | source = "registry+https://github.com/rust-lang/crates.io-index"
1059 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
1060 |
1061 | [[package]]
1062 | name = "simd-adler32"
1063 | version = "0.3.7"
1064 | source = "registry+https://github.com/rust-lang/crates.io-index"
1065 | checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
1066 |
1067 | [[package]]
1068 | name = "smallvec"
1069 | version = "1.13.2"
1070 | source = "registry+https://github.com/rust-lang/crates.io-index"
1071 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
1072 |
1073 | [[package]]
1074 | name = "socket2"
1075 | version = "0.5.8"
1076 | source = "registry+https://github.com/rust-lang/crates.io-index"
1077 | checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
1078 | dependencies = [
1079 | "libc",
1080 | "windows-sys 0.52.0",
1081 | ]
1082 |
1083 | [[package]]
1084 | name = "strsim"
1085 | version = "0.11.1"
1086 | source = "registry+https://github.com/rust-lang/crates.io-index"
1087 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
1088 |
1089 | [[package]]
1090 | name = "syn"
1091 | version = "2.0.90"
1092 | source = "registry+https://github.com/rust-lang/crates.io-index"
1093 | checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
1094 | dependencies = [
1095 | "proc-macro2",
1096 | "quote",
1097 | "unicode-ident",
1098 | ]
1099 |
1100 | [[package]]
1101 | name = "sync_wrapper"
1102 | version = "1.0.2"
1103 | source = "registry+https://github.com/rust-lang/crates.io-index"
1104 | checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
1105 |
1106 | [[package]]
1107 | name = "tap"
1108 | version = "1.0.1"
1109 | source = "registry+https://github.com/rust-lang/crates.io-index"
1110 | checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
1111 |
1112 | [[package]]
1113 | name = "termcolor"
1114 | version = "1.4.1"
1115 | source = "registry+https://github.com/rust-lang/crates.io-index"
1116 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
1117 | dependencies = [
1118 | "winapi-util",
1119 | ]
1120 |
1121 | [[package]]
1122 | name = "thiserror"
1123 | version = "2.0.6"
1124 | source = "registry+https://github.com/rust-lang/crates.io-index"
1125 | checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47"
1126 | dependencies = [
1127 | "thiserror-impl",
1128 | ]
1129 |
1130 | [[package]]
1131 | name = "thiserror-impl"
1132 | version = "2.0.6"
1133 | source = "registry+https://github.com/rust-lang/crates.io-index"
1134 | checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312"
1135 | dependencies = [
1136 | "proc-macro2",
1137 | "quote",
1138 | "syn",
1139 | ]
1140 |
1141 | [[package]]
1142 | name = "tokio"
1143 | version = "1.43.0"
1144 | source = "registry+https://github.com/rust-lang/crates.io-index"
1145 | checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
1146 | dependencies = [
1147 | "backtrace",
1148 | "libc",
1149 | "mio",
1150 | "pin-project-lite",
1151 | "socket2",
1152 | "tokio-macros",
1153 | "windows-sys 0.52.0",
1154 | ]
1155 |
1156 | [[package]]
1157 | name = "tokio-macros"
1158 | version = "2.5.0"
1159 | source = "registry+https://github.com/rust-lang/crates.io-index"
1160 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
1161 | dependencies = [
1162 | "proc-macro2",
1163 | "quote",
1164 | "syn",
1165 | ]
1166 |
1167 | [[package]]
1168 | name = "tower"
1169 | version = "0.5.2"
1170 | source = "registry+https://github.com/rust-lang/crates.io-index"
1171 | checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
1172 | dependencies = [
1173 | "futures-core",
1174 | "futures-util",
1175 | "pin-project-lite",
1176 | "sync_wrapper",
1177 | "tokio",
1178 | "tower-layer",
1179 | "tower-service",
1180 | "tracing",
1181 | ]
1182 |
1183 | [[package]]
1184 | name = "tower-layer"
1185 | version = "0.3.3"
1186 | source = "registry+https://github.com/rust-lang/crates.io-index"
1187 | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
1188 |
1189 | [[package]]
1190 | name = "tower-service"
1191 | version = "0.3.3"
1192 | source = "registry+https://github.com/rust-lang/crates.io-index"
1193 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
1194 |
1195 | [[package]]
1196 | name = "tracing"
1197 | version = "0.1.41"
1198 | source = "registry+https://github.com/rust-lang/crates.io-index"
1199 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
1200 | dependencies = [
1201 | "log",
1202 | "pin-project-lite",
1203 | "tracing-core",
1204 | ]
1205 |
1206 | [[package]]
1207 | name = "tracing-core"
1208 | version = "0.1.33"
1209 | source = "registry+https://github.com/rust-lang/crates.io-index"
1210 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
1211 | dependencies = [
1212 | "once_cell",
1213 | ]
1214 |
1215 | [[package]]
1216 | name = "unicode-ident"
1217 | version = "1.0.14"
1218 | source = "registry+https://github.com/rust-lang/crates.io-index"
1219 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
1220 |
1221 | [[package]]
1222 | name = "unicode-width"
1223 | version = "0.2.0"
1224 | source = "registry+https://github.com/rust-lang/crates.io-index"
1225 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
1226 |
1227 | [[package]]
1228 | name = "utf8parse"
1229 | version = "0.2.2"
1230 | source = "registry+https://github.com/rust-lang/crates.io-index"
1231 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
1232 |
1233 | [[package]]
1234 | name = "version_check"
1235 | version = "0.9.5"
1236 | source = "registry+https://github.com/rust-lang/crates.io-index"
1237 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
1238 |
1239 | [[package]]
1240 | name = "walkdir"
1241 | version = "2.5.0"
1242 | source = "registry+https://github.com/rust-lang/crates.io-index"
1243 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
1244 | dependencies = [
1245 | "same-file",
1246 | "winapi-util",
1247 | ]
1248 |
1249 | [[package]]
1250 | name = "wasi"
1251 | version = "0.11.0+wasi-snapshot-preview1"
1252 | source = "registry+https://github.com/rust-lang/crates.io-index"
1253 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
1254 |
1255 | [[package]]
1256 | name = "wasm-bindgen"
1257 | version = "0.2.99"
1258 | source = "registry+https://github.com/rust-lang/crates.io-index"
1259 | checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
1260 | dependencies = [
1261 | "cfg-if",
1262 | "once_cell",
1263 | "wasm-bindgen-macro",
1264 | ]
1265 |
1266 | [[package]]
1267 | name = "wasm-bindgen-backend"
1268 | version = "0.2.99"
1269 | source = "registry+https://github.com/rust-lang/crates.io-index"
1270 | checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
1271 | dependencies = [
1272 | "bumpalo",
1273 | "log",
1274 | "proc-macro2",
1275 | "quote",
1276 | "syn",
1277 | "wasm-bindgen-shared",
1278 | ]
1279 |
1280 | [[package]]
1281 | name = "wasm-bindgen-macro"
1282 | version = "0.2.99"
1283 | source = "registry+https://github.com/rust-lang/crates.io-index"
1284 | checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
1285 | dependencies = [
1286 | "quote",
1287 | "wasm-bindgen-macro-support",
1288 | ]
1289 |
1290 | [[package]]
1291 | name = "wasm-bindgen-macro-support"
1292 | version = "0.2.99"
1293 | source = "registry+https://github.com/rust-lang/crates.io-index"
1294 | checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
1295 | dependencies = [
1296 | "proc-macro2",
1297 | "quote",
1298 | "syn",
1299 | "wasm-bindgen-backend",
1300 | "wasm-bindgen-shared",
1301 | ]
1302 |
1303 | [[package]]
1304 | name = "wasm-bindgen-shared"
1305 | version = "0.2.99"
1306 | source = "registry+https://github.com/rust-lang/crates.io-index"
1307 | checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
1308 |
1309 | [[package]]
1310 | name = "web-time"
1311 | version = "1.1.0"
1312 | source = "registry+https://github.com/rust-lang/crates.io-index"
1313 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
1314 | dependencies = [
1315 | "js-sys",
1316 | "wasm-bindgen",
1317 | ]
1318 |
1319 | [[package]]
1320 | name = "winapi-util"
1321 | version = "0.1.9"
1322 | source = "registry+https://github.com/rust-lang/crates.io-index"
1323 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
1324 | dependencies = [
1325 | "windows-sys 0.59.0",
1326 | ]
1327 |
1328 | [[package]]
1329 | name = "windows-core"
1330 | version = "0.52.0"
1331 | source = "registry+https://github.com/rust-lang/crates.io-index"
1332 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
1333 | dependencies = [
1334 | "windows-targets",
1335 | ]
1336 |
1337 | [[package]]
1338 | name = "windows-sys"
1339 | version = "0.52.0"
1340 | source = "registry+https://github.com/rust-lang/crates.io-index"
1341 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
1342 | dependencies = [
1343 | "windows-targets",
1344 | ]
1345 |
1346 | [[package]]
1347 | name = "windows-sys"
1348 | version = "0.59.0"
1349 | source = "registry+https://github.com/rust-lang/crates.io-index"
1350 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
1351 | dependencies = [
1352 | "windows-targets",
1353 | ]
1354 |
1355 | [[package]]
1356 | name = "windows-targets"
1357 | version = "0.52.6"
1358 | source = "registry+https://github.com/rust-lang/crates.io-index"
1359 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
1360 | dependencies = [
1361 | "windows_aarch64_gnullvm",
1362 | "windows_aarch64_msvc",
1363 | "windows_i686_gnu",
1364 | "windows_i686_gnullvm",
1365 | "windows_i686_msvc",
1366 | "windows_x86_64_gnu",
1367 | "windows_x86_64_gnullvm",
1368 | "windows_x86_64_msvc",
1369 | ]
1370 |
1371 | [[package]]
1372 | name = "windows_aarch64_gnullvm"
1373 | version = "0.52.6"
1374 | source = "registry+https://github.com/rust-lang/crates.io-index"
1375 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
1376 |
1377 | [[package]]
1378 | name = "windows_aarch64_msvc"
1379 | version = "0.52.6"
1380 | source = "registry+https://github.com/rust-lang/crates.io-index"
1381 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
1382 |
1383 | [[package]]
1384 | name = "windows_i686_gnu"
1385 | version = "0.52.6"
1386 | source = "registry+https://github.com/rust-lang/crates.io-index"
1387 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
1388 |
1389 | [[package]]
1390 | name = "windows_i686_gnullvm"
1391 | version = "0.52.6"
1392 | source = "registry+https://github.com/rust-lang/crates.io-index"
1393 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
1394 |
1395 | [[package]]
1396 | name = "windows_i686_msvc"
1397 | version = "0.52.6"
1398 | source = "registry+https://github.com/rust-lang/crates.io-index"
1399 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
1400 |
1401 | [[package]]
1402 | name = "windows_x86_64_gnu"
1403 | version = "0.52.6"
1404 | source = "registry+https://github.com/rust-lang/crates.io-index"
1405 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
1406 |
1407 | [[package]]
1408 | name = "windows_x86_64_gnullvm"
1409 | version = "0.52.6"
1410 | source = "registry+https://github.com/rust-lang/crates.io-index"
1411 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
1412 |
1413 | [[package]]
1414 | name = "windows_x86_64_msvc"
1415 | version = "0.52.6"
1416 | source = "registry+https://github.com/rust-lang/crates.io-index"
1417 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
1418 |
1419 | [[package]]
1420 | name = "wyz"
1421 | version = "0.5.1"
1422 | source = "registry+https://github.com/rust-lang/crates.io-index"
1423 | checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
1424 | dependencies = [
1425 | "tap",
1426 | ]
1427 |
1428 | [[package]]
1429 | name = "xtask_kompari"
1430 | version = "0.1.0"
1431 | dependencies = [
1432 | "clap",
1433 | "demolib",
1434 | "kompari",
1435 | "kompari-tasks",
1436 | ]
1437 |
1438 | [[package]]
1439 | name = "zopfli"
1440 | version = "0.8.1"
1441 | source = "registry+https://github.com/rust-lang/crates.io-index"
1442 | checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946"
1443 | dependencies = [
1444 | "bumpalo",
1445 | "crc32fast",
1446 | "lockfree-object-pool",
1447 | "log",
1448 | "once_cell",
1449 | "simd-adler32",
1450 | ]
1451 |
--------------------------------------------------------------------------------
/example/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | resolver = "2"
3 | members = ["demolib", "xtask_kompari"]
4 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # Kompari Xtask example
2 |
3 | This is an example that demonstrates how to integrate `kompari` as xtask into a test.
4 |
5 | * `demolib` is an example library that we are testing with snapshot tests
6 | * `xtask-kompari` is xtask that integrates kompari into the example project
7 | * `tests` is a directory with snapshots and image for the current tests
8 |
9 | ## Demo
10 |
11 | Try to modify the test in `demolib/src/lib.rs` to make it fail,
12 | for example try to modify a parameter of the tested function as it
13 | is shown in the comment of the test.
14 |
15 | Run tests via `cargo test`. The test fails because snapshot is different.
16 |
17 | Run `cargo xtask-kompari report` to generate a report
18 | with differences. It will create a `report.html`.
19 |
--------------------------------------------------------------------------------
/example/demolib/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "demolib"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | image = { version = "0.25", default-features = false, features = ["png"] }
8 |
--------------------------------------------------------------------------------
/example/demolib/src/lib.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2024 the Kompari Authors
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT
3 |
4 | use image::{Rgb, RgbImage};
5 |
6 | /// Create an image with rectangle
7 | pub fn create_rectangle(x1: u32, y1: u32, x2: u32, y2: u32, color: Rgb) -> RgbImage {
8 | RgbImage::from_fn(100, 100, |x, y| {
9 | if x1 <= x && x < x2 && y1 <= y && y < y2 {
10 | color
11 | } else {
12 | Rgb([255, 255, 255])
13 | }
14 | })
15 | }
16 |
17 | #[cfg(test)]
18 | mod test_utils;
19 |
20 | #[cfg(test)]
21 | mod tests {
22 | use super::*;
23 | use crate::test_utils::check_snapshot;
24 |
25 | #[test]
26 | fn test_create_rectangle() {
27 | // If you want to make this test fails, change something
28 | //
29 | // For example:
30 | // Change the value ----\
31 | // here to e.g. 25 |
32 | // v
33 | let image = create_rectangle(10, 5, 50, 70, Rgb([255, 0, 0]));
34 | check_snapshot(image, "create_rectangle.png");
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/example/demolib/src/test_utils.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2024 the Kompari Authors
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT
3 |
4 | use image::RgbImage;
5 | use std::path::{Path, PathBuf};
6 |
7 | /// Directory where current tests creates images
8 | fn current_dir() -> PathBuf {
9 | Path::new(env!("CARGO_MANIFEST_DIR"))
10 | .parent()
11 | .unwrap()
12 | .join("tests")
13 | .join("current")
14 | }
15 |
16 | /// Directory with blessed snapshots
17 | fn snapshot_dir() -> PathBuf {
18 | Path::new(env!("CARGO_MANIFEST_DIR"))
19 | .parent()
20 | .unwrap()
21 | .join("tests")
22 | .join("snapshots")
23 | }
24 |
25 | fn is_generate_all_mode() -> bool {
26 | std::env::var("DEMOLIB_TEST")
27 | .map(|x| x.to_ascii_lowercase() == "generate-all")
28 | .unwrap_or(false)
29 | }
30 |
31 | /// Check an image against snapshot
32 | pub(crate) fn check_snapshot(image: RgbImage, image_name: &str) {
33 | let snapshot_dir = snapshot_dir();
34 | let snapshot = image::ImageReader::open(snapshot_dir.join(image_name))
35 | .map_err(|e| e.to_string())
36 | .and_then(|x| x.decode().map_err(|e| e.to_string()))
37 | .map(|x| x.to_rgb8());
38 | if let Ok(snapshot) = snapshot {
39 | if snapshot != image {
40 | image.save(current_dir().join(image_name)).unwrap();
41 | panic!("Snapshot is different; run 'cargo xtask-test report' for report")
42 | }
43 | } else {
44 | println!("{}", current_dir().join(image_name).display());
45 | image.save(current_dir().join(image_name)).unwrap();
46 | snapshot.unwrap();
47 | }
48 | if is_generate_all_mode() {
49 | image.save(current_dir().join(image_name)).unwrap();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/example/tests/current/.gitignore:
--------------------------------------------------------------------------------
1 | *.png
--------------------------------------------------------------------------------
/example/tests/snapshots/create_rectangle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linebender/kompari/4b851413e1b17307064aa48c50e59d7e29656543/example/tests/snapshots/create_rectangle.png
--------------------------------------------------------------------------------
/example/xtask_kompari/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "xtask_kompari"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | demolib = { path = "../demolib" }
8 | clap = { version = "4.5", features = ["derive"] }
9 | kompari = { path = "../../kompari" }
10 | kompari-tasks = { path = "../../kompari-tasks" }
11 |
--------------------------------------------------------------------------------
/example/xtask_kompari/src/main.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2024 the Kompari Authors
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT
3 |
4 | use clap::Parser;
5 | use kompari::DirDiffConfig;
6 | use kompari_tasks::{Actions, Args, Task};
7 | use std::path::Path;
8 | use std::process::Command;
9 |
10 | struct ActionsImpl();
11 |
12 | impl Actions for ActionsImpl {
13 | fn generate_all_tests(&self) -> kompari::Result<()> {
14 | let cargo = std::env::var("CARGO").unwrap();
15 | Command::new(&cargo)
16 | .arg("test")
17 | .env("DEMOLIB_TEST", "generate-all")
18 | .status()?;
19 | Ok(())
20 | }
21 | }
22 |
23 | fn main() -> kompari::Result<()> {
24 | let tests_path = Path::new(env!("CARGO_MANIFEST_DIR"))
25 | .parent()
26 | .unwrap()
27 | .join("tests");
28 |
29 | let snapshots_path = tests_path.join("snapshots");
30 | let current_path = tests_path.join("current");
31 |
32 | let args = Args::parse();
33 | let diff_config = DirDiffConfig::new(snapshots_path, current_path);
34 | let actions = ActionsImpl();
35 | let mut task = Task::new(diff_config, Box::new(actions));
36 | task.run(&args)?;
37 | Ok(())
38 | }
39 |
--------------------------------------------------------------------------------
/kompari-cli/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "kompari-cli"
3 | description = "CLI for reporting tool of image differences for snapshot testing."
4 | keywords = ["image", "report", "diff", "tests"]
5 | categories = ["graphics", "multimedia::images", "development-tools::testing"]
6 |
7 |
8 | version.workspace = true
9 | edition.workspace = true
10 | rust-version.workspace = true
11 | license.workspace = true
12 | repository.workspace = true
13 |
14 | [dependencies]
15 | kompari = { path = "../kompari" }
16 | kompari-html = { path = "../kompari-html" }
17 | clap = { workspace = true }
18 | kompari-tasks = { path = "../kompari-tasks" }
19 |
20 | [dev-dependencies]
21 | assert_cmd = "2.0"
22 | tempfile = "3.15"
23 |
--------------------------------------------------------------------------------
/kompari-cli/README.md:
--------------------------------------------------------------------------------
1 | # Kompari CLI
2 |
3 | Command line interface for Kompari
4 |
--------------------------------------------------------------------------------
/kompari-cli/src/main.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2024 the Kompari Authors
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT
3 |
4 | // LINEBENDER LINT SET - lib.rs - v3
5 | // See https://linebender.org/wiki/canonical-lints/
6 | // These lints shouldn't apply to examples or tests.
7 | #![cfg_attr(not(test), warn(unused_crate_dependencies))]
8 | // These lints shouldn't apply to examples.
9 | // #![warn(clippy::print_stdout, clippy::print_stderr)]
10 | // Targeting e.g. 32-bit means structs containing usize can give false positives for 64-bit.
11 | #![cfg_attr(target_pointer_width = "64", warn(clippy::trivially_copy_pass_by_ref))]
12 | // END LINEBENDER LINT SET
13 | #![cfg_attr(docsrs, feature(doc_auto_cfg))]
14 |
15 | use clap::Parser;
16 | use kompari::DirDiffConfig;
17 | use kompari_html::{render_html_report, start_review_server, ReportConfig};
18 | use kompari_tasks::check_size_optimizations;
19 | use std::path::PathBuf;
20 |
21 | #[derive(Parser, Debug)]
22 | pub struct CliReportArgs {
23 | #[clap(flatten)]
24 | diff_args: DiffArgs,
25 | #[clap(flatten)]
26 | args: kompari_tasks::args::ReportArgs,
27 | }
28 |
29 | #[derive(Parser, Debug)]
30 | pub struct CliReviewArgs {
31 | #[clap(flatten)]
32 | diff_args: DiffArgs,
33 | #[clap(flatten)]
34 | args: kompari_tasks::args::ReviewArgs,
35 | }
36 |
37 | #[derive(Parser, Debug, Clone)]
38 | struct DiffArgs {
39 | /// Path to "left" images
40 | left_path: PathBuf,
41 |
42 | /// Path to "right" images
43 | right_path: PathBuf,
44 |
45 | /// Left title
46 | #[arg(long, default_value = "Left image")]
47 | left_title: String,
48 |
49 | /// Right title
50 | #[arg(long, default_value = "Right image")]
51 | right_title: String,
52 |
53 | /// Ignore left missing files
54 | #[arg(long, default_value_t = false)]
55 | ignore_left_missing: bool,
56 |
57 | /// Ignore right missing files
58 | #[arg(long, default_value_t = false)]
59 | ignore_right_missing: bool,
60 |
61 | /// Ignore match
62 | #[arg(long, default_value_t = false)]
63 | ignore_match: bool,
64 |
65 | /// Filter filenames by name
66 | #[arg(long)]
67 | filter: Option,
68 | }
69 |
70 | #[derive(Parser, Debug)]
71 | pub struct CliSizeCheckArgs {
72 | path: PathBuf,
73 |
74 | #[clap(flatten)]
75 | args: kompari_tasks::args::SizeCheckArgs,
76 | }
77 |
78 | #[derive(Parser, Debug)]
79 | #[command(version, about, long_about = None)]
80 | pub enum Args {
81 | Report(CliReportArgs),
82 | Review(CliReviewArgs),
83 | SizeCheck(CliSizeCheckArgs),
84 | }
85 |
86 | fn make_diff_config(args: DiffArgs) -> (DirDiffConfig, ReportConfig) {
87 | let mut diff_config = DirDiffConfig::new(args.left_path, args.right_path);
88 | diff_config.set_ignore_left_missing(args.ignore_left_missing);
89 | diff_config.set_ignore_right_missing(args.ignore_right_missing);
90 | diff_config.set_filter_name(args.filter);
91 |
92 | let mut report_config = ReportConfig::default();
93 | report_config.set_left_title(args.left_title);
94 | report_config.set_right_title(args.right_title);
95 |
96 | (diff_config, report_config)
97 | }
98 |
99 | fn main() -> kompari::Result<()> {
100 | let args = Args::parse();
101 |
102 | match args {
103 | Args::Report(args) => {
104 | let (diff_config, mut report_config) = make_diff_config(args.diff_args);
105 | let diff = diff_config.create_diff()?;
106 | report_config.set_embed_images(args.args.embed_images);
107 | report_config.set_size_optimization(args.args.optimize_size.to_level());
108 | let report = render_html_report(&report_config, diff.results())?;
109 | let output = args.args.output.unwrap_or("report.html".into());
110 | std::fs::write(&output, report)?;
111 | println!("Report written into '{}'", output.display());
112 | }
113 | Args::Review(args) => {
114 | let (diff_config, mut report_config) = make_diff_config(args.diff_args);
115 | report_config.set_size_optimization(args.args.optimize_size.to_level());
116 | start_review_server(&diff_config, &report_config, args.args.port)?
117 | }
118 | Args::SizeCheck(args) => {
119 | check_size_optimizations(&args.path, &args.args)?;
120 | }
121 | }
122 | Ok(())
123 | }
124 |
--------------------------------------------------------------------------------
/kompari-cli/tests/tests.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2025 the Kompari Authors
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT
3 |
4 | use assert_cmd::Command;
5 | use std::path::{Path, PathBuf};
6 | use tempfile::TempDir;
7 |
8 | fn test_assets_dir() -> PathBuf {
9 | Path::new(env!("CARGO_MANIFEST_DIR"))
10 | .parent()
11 | .unwrap()
12 | .join("tests")
13 | }
14 |
15 | #[test]
16 | fn test_create_report() {
17 | let workdir = TempDir::new().unwrap();
18 | std::env::set_current_dir(workdir.path()).unwrap();
19 |
20 | let test_dir = test_assets_dir();
21 | let left = test_dir.join("left");
22 | let right = test_dir.join("right");
23 | let mut cmd = Command::cargo_bin("kompari-cli").unwrap();
24 | cmd.arg("report").arg(&left).arg(&right);
25 | cmd.current_dir(&workdir);
26 | cmd.assert().success();
27 | let result: Vec<_> = std::fs::read_dir(&workdir)
28 | .unwrap()
29 | .map(|e| e.unwrap().file_name().to_str().unwrap().to_owned())
30 | .collect();
31 | assert_eq!(result, vec!["report.html"]);
32 | let report = std::fs::read_to_string(workdir.path().join("report.html")).unwrap();
33 | for name in [
34 | "bright",
35 | "changetext",
36 | "right_missing",
37 | "left_missing",
38 | "shift",
39 | "size_error",
40 | ] {
41 | assert!(report.contains(&format!("{}.png", name)));
42 | }
43 | assert!(!report.contains("same.png"));
44 | }
45 |
46 | #[test]
47 | fn test_filter_filenames() {
48 | let workdir = TempDir::new().unwrap();
49 | std::env::set_current_dir(workdir.path()).unwrap();
50 |
51 | let test_dir = test_assets_dir();
52 | let left = test_dir.join("left");
53 | let right = test_dir.join("right");
54 | let mut cmd = Command::cargo_bin("kompari-cli").unwrap();
55 | cmd.arg("report").arg("--filter").arg("change");
56 | cmd.arg(&left).arg(&right);
57 | cmd.current_dir(&workdir);
58 | cmd.assert().success();
59 | let report = std::fs::read_to_string(workdir.path().join("report.html")).unwrap();
60 | assert!(report.contains("changetext.png"));
61 | for name in [
62 | "bright",
63 | "right_missing",
64 | "left_missing",
65 | "shift",
66 | "size_error",
67 | ] {
68 | assert!(!report.contains(&format!("{}.png", name)));
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/kompari-html/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "kompari-html"
3 |
4 | description = "HTML reports for image differences for snapshot testing."
5 | keywords = ["image", "report", "diff", "tests"]
6 | categories = ["graphics", "multimedia::images", "development-tools::testing"]
7 |
8 |
9 | version.workspace = true
10 | edition.workspace = true
11 | rust-version.workspace = true
12 | license.workspace = true
13 | repository.workspace = true
14 |
15 | [dependencies]
16 | kompari = { path = "../kompari", features = ["oxipng"] }
17 | rayon = { workspace = true }
18 | base64 = "0.22"
19 | chrono = "0.4"
20 | maud = "0.27"
21 | imagesize = "0.13"
22 | serde = { version = "1.0.217", features = ["derive"] }
23 | tokio = "1.43"
24 | axum = "0.8"
25 |
--------------------------------------------------------------------------------
/kompari-html/README.md:
--------------------------------------------------------------------------------
1 | # Kompari HTML
2 |
3 | Generates a HTML report from a failing set of snapshot tests.
4 |
--------------------------------------------------------------------------------
/kompari-html/src/lib.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2025 the Kompari Authors
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT
3 |
4 | // LINEBENDER LINT SET - lib.rs - v3
5 | // See https://linebender.org/wiki/canonical-lints/
6 | // These lints shouldn't apply to examples or tests.
7 | #![cfg_attr(not(test), warn(unused_crate_dependencies))]
8 | // These lints shouldn't apply to examples.
9 | // #![warn(clippy::print_stdout, clippy::print_stderr)]
10 | // Targeting e.g. 32-bit means structs containing usize can give false positives for 64-bit.
11 | #![cfg_attr(target_pointer_width = "64", warn(clippy::trivially_copy_pass_by_ref))]
12 | // END LINEBENDER LINT SET
13 | #![cfg_attr(docsrs, feature(doc_auto_cfg))]
14 |
15 | mod pageconsts;
16 | mod report;
17 | mod review;
18 |
19 | #[derive(Debug, Clone)]
20 | pub struct ReportConfig {
21 | left_title: String,
22 | right_title: String,
23 | embed_images: bool,
24 | is_review: bool,
25 | size_optimization: SizeOptimizationLevel,
26 | }
27 |
28 | impl Default for ReportConfig {
29 | fn default() -> Self {
30 | ReportConfig {
31 | left_title: "Left image".to_string(),
32 | right_title: "Right image".to_string(),
33 | embed_images: false,
34 | is_review: false,
35 | size_optimization: SizeOptimizationLevel::None,
36 | }
37 | }
38 | }
39 |
40 | impl ReportConfig {
41 | pub fn set_left_title(&mut self, value: impl ToString) {
42 | self.left_title = value.to_string()
43 | }
44 | pub fn set_right_title(&mut self, value: impl ToString) {
45 | self.right_title = value.to_string()
46 | }
47 | pub fn set_embed_images(&mut self, value: bool) {
48 | self.embed_images = value
49 | }
50 | pub fn set_size_optimization(&mut self, value: SizeOptimizationLevel) {
51 | self.size_optimization = value
52 | }
53 | pub fn set_review(&mut self, value: bool) {
54 | self.is_review = value
55 | }
56 | }
57 |
58 | use kompari::SizeOptimizationLevel;
59 | pub use report::render_html_report;
60 | pub use review::start_review_server;
61 |
--------------------------------------------------------------------------------
/kompari-html/src/pageconsts.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2024 the Kompari Authors
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT
3 |
4 | pub(crate) const ICON: &[u8] = include_bytes!("../../docs/logo_small.png");
5 |
6 | pub(crate) const CSS_STYLE: &str = "
7 | body {
8 | font-family: Roboto, sans-serif;
9 | margin: 0;
10 | padding: 20px;
11 | background: #f5f5f5;
12 | color: #333;
13 | }
14 |
15 | .header {
16 | background: #fff;
17 | padding: 20px;
18 | border-radius: 8px;
19 | margin-bottom: 20px;
20 | box-shadow: 0 2px 4px rgba(0,0,0,0.1);
21 | }
22 |
23 | .logo {
24 | vertical-align: -10%;
25 | }
26 |
27 | .header h1 {
28 | margin: 0;
29 | color: #2d3748;
30 | }
31 |
32 | .summary {
33 | margin-bottom: 20px;
34 | padding: 15px;
35 | background: #fff;
36 | border-radius: 8px;
37 | box-shadow: 0 2px 4px rgba(0,0,0,0.1);
38 | }
39 |
40 | .diff-entry {
41 | background: #fff;
42 | margin-bottom: 30px;
43 | padding: 20px;
44 | border-radius: 8px;
45 | box-shadow: 0 2px 4px rgba(0,0,0,0.1);
46 | }
47 |
48 | .diff-entry h2 {
49 | margin-top: 0;
50 | color: #2d3748;
51 | border-bottom: 2px solid #edf2f7;
52 | padding-bottom: 10px;
53 | }
54 |
55 | .comparison-container {
56 | display: flex;
57 | gap: 20px;
58 | margin-top: 15px;
59 | }
60 |
61 | .image-container {
62 | display: flex;
63 | gap: 20px;
64 | flex-wrap: wrap;
65 | flex: 1;
66 | }
67 |
68 | .image-box {
69 | flex: 1;
70 | min-width: 250px;
71 | max-width: 400px;
72 | }
73 |
74 | .image-box h3 {
75 | margin: 0 0 10px 0;
76 | color: #4a5568;
77 | font-size: 1rem;
78 | }
79 |
80 | .image-box img {
81 | max-width: 100%;
82 | border: 1px solid #e2e8f0;
83 | border-radius: 4px;
84 | }
85 |
86 | .stats-container {
87 | width: 200px;
88 | flex-shrink: 0;
89 | background: #f8fafc;
90 | padding: 15px;
91 | border-radius: 6px;
92 | border: 1px solid #e2e8f0;
93 | }
94 |
95 | .stat-item {
96 | margin-bottom: 15px;
97 | }
98 |
99 | .stat-label {
100 | font-size: 0.875rem;
101 | color: #64748b;
102 | margin-bottom: 4px;
103 | }
104 |
105 | .stat-value {
106 | font-size: 1.25rem;
107 | font-weight: 600;
108 | color: #2d3748;
109 | }
110 |
111 | .stat-value.ok {
112 | color: #77d906;
113 | }
114 |
115 | .stat-value.warning {
116 | color: #d97706;
117 | }
118 |
119 | .stat-value.error {
120 | color: #dc2626;
121 | }
122 |
123 | @media (max-width: 1200px) {
124 | .comparison-container {
125 | flex-direction: column-reverse;
126 | }
127 |
128 | .stats-container {
129 | width: auto;
130 | display: flex;
131 | flex-wrap: wrap;
132 | gap: 20px;
133 | }
134 |
135 | .stat-item {
136 | flex: 1;
137 | min-width: 150px;
138 | margin-bottom: 0;
139 | }
140 | }
141 |
142 | @media (max-width: 768px) {
143 | .image-box {
144 | min-width: 100%;
145 | }
146 | }
147 |
148 | img.zoom:hover {
149 | cursor: pointer;
150 | transform: scale(1.05);
151 | }
152 |
153 | dialog {
154 | width: 80%;
155 | height: 80%;
156 | max-width: 800px;
157 | max-height: 820px;
158 | padding: 0;
159 | border: none;
160 | border-radius: 10px;
161 | box-shadow: 0 0 15px rgba(0, 0, 0, 0.3);
162 | }
163 |
164 | .zoomed-image {
165 | object-fit: contain;
166 | }
167 |
168 | .zoomed-image-small {
169 | image-rendering: pixelated;
170 | }
171 |
172 | .toggle-switch {
173 | position: relative;
174 | display: inline-block;
175 | width: 60px;
176 | height: 30px;
177 | margin-right: 1em;
178 | }
179 | .toggle-switch input {
180 | opacity: 0;
181 | width: 0;
182 | height: 0;
183 | }
184 | .slider {
185 | position: absolute;
186 | cursor: pointer;
187 | top: 0;
188 | left: 0;
189 | right: 0;
190 | bottom: 0;
191 | background-color: #ccc;
192 | transition: .1s;
193 | border-radius: 34px;
194 | }
195 | .slider:before {
196 | position: absolute;
197 | content: \"\";
198 | height: 22px;
199 | width: 22px;
200 | left: 4px;
201 | bottom: 4px;
202 | background-color: white;
203 | transition: .1s;
204 | border-radius: 50%;
205 | }
206 | input:checked + .slider {
207 | background-color: #3c3;
208 | }
209 | input:checked + .slider:before {
210 | transform: translateX(30px);
211 | }
212 |
213 | .accept-button {
214 | padding: 12px 24px;
215 | margin-bottom: 1em;
216 | font-size: 16px;
217 | font-weight: 500;
218 | color: white;
219 | background-color: #4CAF50;
220 | border: none;
221 | border-radius: 6px;
222 | cursor: pointer;
223 | transition: all 0.3s ease;
224 | display: flex;
225 | align-items: center;
226 | gap: 8px;
227 | }
228 | .accept-button:hover {
229 | background-color: #45A049;
230 | transform: translateY(-1px);
231 | box-shadow: 0 2px 4px rgba(0,0,0,0.1);
232 | }
233 | .accept-button:active {
234 | transform: translateY(0px);
235 | box-shadow: none;
236 | }
237 | .accept-button:disabled {
238 | background-color: #CCCCCC;
239 | cursor: not-allowed;
240 | transform: none;
241 | }
242 | #errorMsg {
243 | background-color: #fef2f2;
244 | border: 1px solid #f87171;
245 | border-radius: 6px;
246 | padding: 16px;
247 | margin: 12px 0;
248 | display: none;
249 | align-items: flex-start;
250 | gap: 12px;
251 | max-width: 600px;
252 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
253 | }
254 | .tabs {
255 | margin-top: 8px;
256 | display: flex;
257 | }
258 | .tab {
259 | margin-left: 10px;
260 | margin-right: 10px;
261 | background: none;
262 | cursor: pointer;
263 | font-size: 16px;
264 | color: #666;
265 | }
266 | .tab.active {
267 | color: #444;
268 | border-bottom: 2px solid #444;
269 | }
270 |
271 | .hint {
272 | color: #666;
273 | font-size: 80%;
274 | }
275 | ";
276 |
277 | pub(crate) const JS_CODE: &str = "
278 | function openImageDialog(img, pixelize) {
279 | const dialog = document.getElementById('imageDialog');
280 | const zoomedImg = document.getElementById('zoomedImage');
281 |
282 | if (pixelize) {
283 | zoomedImg.classList.add(\"zoomed-image-small\");
284 | } else {
285 | zoomedImg.classList.remove(\"zoomed-image-small\");
286 | }
287 |
288 | zoomedImg.src = img.src;
289 | if (img.width < img.height) {
290 | zoomedImg.style.width = \"100%\";
291 | zoomedImg.style.height = \"auto\";
292 | } else {
293 | zoomedImg.style.width = \"auto\";
294 | zoomedImg.style.height = \"100%\";
295 | }
296 | dialog.showModal();
297 | }
298 |
299 | function closeImageDialog() {
300 | const dialog = document.getElementById('imageDialog');
301 | dialog.close();
302 | }
303 |
304 | document.getElementById('imageDialog').addEventListener('click', function(event) {
305 | closeImageDialog();
306 | });
307 |
308 | var selected = new Set();
309 | function toggle(event) {
310 | let node = event.target.parentNode.parentNode;
311 | let name = node.childNodes[2].textContent;
312 | if (event.target.checked) {
313 | selected.add(name);
314 | node.style.color = \"#3a3\";
315 | } else {
316 | selected.delete(name);
317 | node.style.color = \"#333\";
318 | }
319 | updateAcceptButton()
320 | }
321 |
322 | function updateAcceptButton() {
323 | let text = document.getElementById('acceptText');
324 | text.textContent = \"Accept selected cases (\" + selected.size + \" / \" + nTests + \")\";
325 | let button = document.getElementById('acceptButton');
326 | button.disabled = (selected.size === 0);
327 | }
328 |
329 | function switchDiffTab(id, selected, n) {
330 | for (let idx = 0; idx < n; idx++) {
331 | document.getElementById(`tab-diff-${id}-${idx}`).classList.remove('active');
332 | document.getElementById(`img-diff-${id}-${idx}`).style.display = 'none';
333 | }
334 | document.getElementById(`tab-diff-${id}-${selected}`).classList.add('active');
335 | document.getElementById(`img-diff-${id}-${selected}`).style.display = 'inline';
336 | }
337 |
338 | async function acceptTests() {
339 | let text = document.getElementById('acceptText');
340 | text.textContent = \"Updating \" + selected.size + \" cases ...\";
341 | let button = document.getElementById('acceptButton');
342 | button.disabled = true;
343 |
344 | try {
345 | const url = '/update';
346 | const response = await fetch(url, {
347 | method: 'POST',
348 | headers: {
349 | \"Content-Type\": \"application/json\",
350 | },
351 | body: JSON.stringify({ accepted_names: Array.from(selected) })
352 | });
353 | if (!response.ok) {
354 | throw new Error(`Response status: ${response.status}`);
355 | } else {
356 | for (i = 0; i < nTests; i++) {
357 | document.getElementById(\"t\" + i).checked = false;
358 | }
359 | location.reload();
360 | }
361 | } catch (e) {
362 | let error = document.getElementById('errorMsg');
363 | error.textContent = e.message;
364 | error.style.display = \"flex\";
365 | text.textContent = \"Try update again\";
366 | button.disabled = false;
367 | }
368 | }
369 | ";
370 |
--------------------------------------------------------------------------------
/kompari-html/src/report.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2024 the Kompari Authors
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT
3 |
4 | use crate::pageconsts::{CSS_STYLE, ICON, JS_CODE};
5 | use crate::ReportConfig;
6 | use base64::prelude::*;
7 | use chrono::SubsecRound;
8 | use kompari::{ImageDifference, LeftRightError, PairResult};
9 | use maud::{html, Markup, PreEscaped, DOCTYPE};
10 | use rayon::iter::IndexedParallelIterator;
11 | use rayon::iter::IntoParallelRefIterator;
12 | use rayon::iter::ParallelIterator;
13 | use std::cmp::min;
14 | use std::path::Path;
15 |
16 | const IMAGE_SIZE_LIMIT: u32 = 400;
17 | const IMAGE_PIXELIZE_LIMIT: u32 = 400;
18 |
19 | fn embed_png_url(data: &[u8]) -> String {
20 | let mut url = "data:image/png;base64,".to_string();
21 | url.push_str(&base64::engine::general_purpose::STANDARD.encode(data));
22 | url
23 | }
24 |
25 | fn render_image(
26 | config: &ReportConfig,
27 | path: &Path,
28 | error: Option<&kompari::Error>,
29 | ) -> kompari::Result {
30 | Ok(match error {
31 | None => {
32 | let (path, size) = if config.embed_images {
33 | let image_data =
34 | kompari::optimize_png(std::fs::read(path)?, config.size_optimization);
35 | (
36 | embed_png_url(&image_data),
37 | imagesize::blob_size(&image_data)
38 | .map_err(|e| kompari::Error::GenericError(e.to_string()))?,
39 | )
40 | } else {
41 | (
42 | path.display().to_string(),
43 | imagesize::size(path)
44 | .map_err(|e| kompari::Error::GenericError(e.to_string()))?,
45 | )
46 | };
47 | let (w, h) = html_size(size.width as u32, size.height as u32, IMAGE_SIZE_LIMIT);
48 | html! {
49 | img class="zoom" src=(path)
50 | width=[w] height=[h]
51 | onclick=(open_image_dialog(size.width as u32, size.height as u32));
52 | }
53 | }
54 | Some(kompari::Error::FileNotFound(_)) => {
55 | html! { "File is missing" }
56 | }
57 | Some(err) => {
58 | html! { "Error: " (err) }
59 | }
60 | })
61 | }
62 |
63 | pub fn html_size(width: u32, height: u32, size_limit: u32) -> (Option, Option) {
64 | if width > height {
65 | (Some(width.min(size_limit)), None)
66 | } else {
67 | (None, Some(height.min(size_limit)))
68 | }
69 | }
70 |
71 | fn open_image_dialog(width: u32, height: u32) -> &'static str {
72 | if min(width, height) < IMAGE_PIXELIZE_LIMIT {
73 | "openImageDialog(this, true)"
74 | } else {
75 | "openImageDialog(this, false)"
76 | }
77 | }
78 |
79 | fn render_difference_image(
80 | config: &ReportConfig,
81 | id: usize,
82 | difference: &Result,
83 | ) -> Markup {
84 | match difference {
85 | Ok(ImageDifference::Content { diff_images, .. }) => {
86 | html! {
87 | @for (idx, di) in diff_images.iter().enumerate() {
88 | @let (w, h, data) = {
89 | let (w, h) = html_size(
90 | di.image.width(),
91 | di.image.height(),
92 | IMAGE_SIZE_LIMIT,
93 | );
94 | let data = kompari::image_to_png(&di.image, config.size_optimization);
95 | (w, h, data)
96 | };
97 | @let style = if idx == 0 { None } else { Some("display: none") };
98 | img id=(format!("img-diff-{id}-{idx}"))
99 | style=[style]
100 | class="zoom"
101 | src=(embed_png_url(&data))
102 | width=[w] height=[h]
103 | onclick=(open_image_dialog(di.image.width(), di.image.height()));
104 | }
105 | div class="tabs" {
106 | @for (idx, img) in diff_images.iter().enumerate() {
107 | @let class = if idx == 0 { "tab active" } else { "tab" };
108 | div id=(format!("tab-diff-{id}-{idx}")) class=(class) {(img.method.to_string())};
109 | }
110 | }
111 | script {
112 | @for idx in 0..diff_images.len() {
113 | (PreEscaped(format!("document.getElementById('tab-diff-{id}-{idx}').addEventListener('click', () => switchDiffTab({id}, {idx}, {}));", diff_images.len())))
114 | }
115 | }
116 | }
117 | }
118 | _ => html!("N/A"),
119 | }
120 | }
121 |
122 | fn render_stat_item(label: &str, value_type: &str, value: &str) -> Markup {
123 | html! {
124 | div .stat-item {
125 | div .stat-label {
126 | (label)
127 | }
128 | @let value_class = format!("stat-value {}", value_type);
129 | div class=(value_class) {
130 | (value)
131 | }
132 | }
133 | }
134 | }
135 |
136 | fn render_difference_info(
137 | config: &ReportConfig,
138 | difference: &Result,
139 | ) -> Markup {
140 | match difference {
141 | Ok(ImageDifference::None) => render_stat_item("Status", "ok", "Match"),
142 | Ok(ImageDifference::SizeMismatch {
143 | left_size,
144 | right_size,
145 | }) => html! {
146 | (render_stat_item("Status", "error", "Size mismatch"))
147 | (render_stat_item(&format!("{} size", config.left_title), "", &format!("{}x{}", left_size.0, left_size.1)))
148 | (render_stat_item(&format!("{} size", config.right_title), "", &format!("{}x{}", right_size.0, right_size.1)))
149 | },
150 | Ok(ImageDifference::Content {
151 | n_pixels,
152 | n_different_pixels,
153 | distance_sum,
154 | ..
155 | }) => {
156 | let n_pixels = (*n_pixels) as f32;
157 | let pct = *n_different_pixels as f32 / n_pixels * 100.0;
158 | let distance_sum = *distance_sum as f32 / 255.0; // Normalize
159 | let avg_color_distance = distance_sum / n_pixels;
160 | html! {
161 | (render_stat_item("Different pixels", "warning", &format!("{n_different_pixels} ({pct:.1}%)")))
162 | (render_stat_item("Color distance", "", &format!("{distance_sum:.3}")))
163 | (render_stat_item("Avg. color distance", "", &format!("{avg_color_distance:.4}")))
164 | }
165 | }
166 | Err(e) if e.is_missing_file_error() => render_stat_item("Status", "error", "Missing file"),
167 | Err(_) => render_stat_item("Status", "error", "Loading error"),
168 | }
169 | }
170 |
171 | fn render_pair_diff(
172 | config: &ReportConfig,
173 | id: usize,
174 | pair_diff: &PairResult,
175 | ) -> kompari::Result {
176 | Ok(html! {
177 | div class="diff-entry" {
178 | h2 {
179 | @if config.is_review {
180 | label class="toggle-switch" {
181 | input type="checkbox" id=(format!("t{id}"));
182 | span class="slider";
183 | }
184 | script {
185 | (format!("document.getElementById('t{id}').addEventListener('change', toggle)"))
186 | }
187 | }
188 | (pair_diff.title)};
189 | div class="comparison-container" {
190 | div class="image-container" {
191 | div class="stats-container" {
192 | (render_difference_info(config, &pair_diff.image_diff))
193 | }
194 | div class="image-box" {
195 | h3 { (config.left_title) }
196 | (render_image(config, &pair_diff.left, if let Err(e) = &pair_diff.image_diff { e.left() } else { None })?)
197 | }
198 | div class="image-box" {
199 | h3 { (config.right_title) }
200 | (render_image(config, &pair_diff.right, if let Err(e) = &pair_diff.image_diff { e.right() } else { None })?)
201 | }
202 | div class="image-box" {
203 | h3 { "Difference"}
204 | (render_difference_image(config, id, &pair_diff.image_diff))
205 | }
206 | }
207 | }
208 | }
209 | })
210 | }
211 |
212 | pub fn render_html_report(config: &ReportConfig, diffs: &[PairResult]) -> kompari::Result {
213 | let now = chrono::Local::now().round_subsecs(0);
214 | let rendered_diffs: Vec = diffs
215 | .par_iter()
216 | .enumerate()
217 | .map(|(id, pair_diff)| render_pair_diff(config, id, pair_diff))
218 | .collect::>>()?;
219 | let title = PreEscaped(if config.is_review {
220 | "Kompari review"
221 | } else {
222 | "Kompari report"
223 | });
224 | let report = html! {
225 | (DOCTYPE)
226 | html {
227 | head {
228 | meta charset="utf-8";
229 | meta name="viewport" content="width=device-width, initial-scale=1.0";
230 | meta name="generator" content=(format!("Kompari {}", env!("CARGO_PKG_VERSION")));
231 | title { (title) }
232 | style { (PreEscaped(CSS_STYLE)) }
233 | link rel="icon" type="image/png" href=(embed_png_url(&ICON));
234 | }
235 | body {
236 | div class="header" {
237 | h1 { img class="logo" src=(embed_png_url(ICON)) width="32" height="32"; (title) }
238 | p { "Generated on " (now) }
239 | }
240 | dialog id="imageDialog" {
241 | img id="zoomedImage" class="zoomed-image" src="" alt="Zoomed Image";
242 | }
243 | @if config.is_review {
244 | script { (format!("const nTests = {};", diffs.len())) }
245 | button class="accept-button" id="acceptButton" disabled onClick="acceptTests()" {
246 | span class="button-text" id="acceptText" { (format!("Accept selected cases (0 / {})", diffs.len())) }
247 | }
248 | span class="hint" { "Accepting a case copies '" (config.right_title) "' to '" (config.left_title) "'" }
249 | span id="errorMsg" {};
250 | }
251 | script { (PreEscaped(JS_CODE)) }
252 | @for chunk in rendered_diffs {
253 | (chunk)
254 | }
255 | }
256 | }
257 | };
258 | Ok(report.into_string())
259 | }
260 |
--------------------------------------------------------------------------------
/kompari-html/src/review.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2025 the Kompari Authors
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT
3 |
4 | use crate::{render_html_report, ReportConfig};
5 | use axum::extract::State;
6 | use axum::http::StatusCode;
7 | use axum::response::{Html, IntoResponse};
8 | use axum::routing::post;
9 | use axum::{routing::get, Json, Router};
10 | use kompari::{bless_image, DirDiffConfig};
11 | use serde::Deserialize;
12 | use std::path::PathBuf;
13 | use std::sync::Arc;
14 |
15 | struct AppState {
16 | report_config: ReportConfig,
17 | diff_builder: DirDiffConfig,
18 | }
19 |
20 | pub fn start_review_server(
21 | diff_builder: &DirDiffConfig,
22 | report_config: &ReportConfig,
23 | port: u16,
24 | ) -> kompari::Result<()> {
25 | let mut report_config = report_config.clone();
26 | report_config.set_review(true);
27 | report_config.set_embed_images(true);
28 | let shared_state = Arc::new(AppState {
29 | report_config,
30 | diff_builder: diff_builder.clone(),
31 | });
32 | println!("Running at http://localhost:{port}");
33 | tokio::runtime::Builder::new_current_thread()
34 | .enable_all()
35 | .build()?
36 | .block_on(async {
37 | let app = Router::new()
38 | .route("/", get(index))
39 | .route("/update", post(update))
40 | .with_state(shared_state);
41 | let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{port}"))
42 | .await
43 | .unwrap();
44 | axum::serve(listener, app).await.unwrap();
45 | });
46 | Ok(())
47 | }
48 |
49 | fn result_to_response(result: kompari::Result) -> (StatusCode, Html) {
50 | match result {
51 | Ok(s) => (StatusCode::OK, Html::from(s)),
52 | Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, Html::from(e.to_string())),
53 | }
54 | }
55 |
56 | async fn index(State(state): State>) -> impl IntoResponse {
57 | result_to_response((|| {
58 | let diff = state.diff_builder.create_diff()?;
59 | render_html_report(&state.report_config, diff.results())
60 | })())
61 | }
62 |
63 | #[derive(Deserialize, Debug)]
64 | struct UpdateParams {
65 | accepted_names: Vec,
66 | }
67 |
68 | async fn update(
69 | State(state): State>,
70 | Json(params): Json,
71 | ) -> StatusCode {
72 | let paths: Vec<_> = params
73 | .accepted_names
74 | .into_iter()
75 | .map(PathBuf::from)
76 | .collect();
77 | if paths.iter().any(|p| !p.is_relative()) {
78 | return StatusCode::BAD_REQUEST;
79 | }
80 | for path in paths {
81 | let left = state.diff_builder.left_path().join(&path);
82 | let right = state.diff_builder.right_path().join(&path);
83 | println!("Updating {} -> {}", right.display(), left.display());
84 | if let Err(e) = bless_image(&right, &left) {
85 | eprintln!("Failed to rename {}: {}", right.display(), e);
86 | return StatusCode::INTERNAL_SERVER_ERROR;
87 | }
88 | }
89 | StatusCode::OK
90 | }
91 |
--------------------------------------------------------------------------------
/kompari-tasks/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "kompari-tasks"
3 |
4 | description = "Supportive code for Xtasks based on Kompari"
5 | keywords = ["image", "report", "diff", "tests"]
6 | categories = ["graphics", "multimedia::images", "development-tools::testing"]
7 |
8 |
9 | version.workspace = true
10 | edition.workspace = true
11 | rust-version.workspace = true
12 | license.workspace = true
13 | repository.workspace = true
14 |
15 | [dependencies]
16 | kompari = { path = "../kompari" }
17 | kompari-html = { path = "../kompari-html" }
18 | clap = { workspace = true }
19 | rayon = { workspace = true }
20 | termcolor = "1.4"
21 | humansize = "2.1"
22 | indicatif = "0.17"
23 |
--------------------------------------------------------------------------------
/kompari-tasks/README.md:
--------------------------------------------------------------------------------
1 | # Kompari Tasks
2 |
3 | Supportive code for Xtasks based on Kompari
4 |
--------------------------------------------------------------------------------
/kompari-tasks/src/args.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2024 the Kompari Authors
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT
3 |
4 | use clap::{Parser, ValueEnum};
5 | use kompari::SizeOptimizationLevel;
6 | use std::path::PathBuf;
7 |
8 | #[derive(Parser, Debug)]
9 | #[command(version, about, long_about = None)]
10 | pub struct Args {
11 | #[clap(subcommand)]
12 | pub command: Command,
13 | }
14 |
15 | #[derive(Parser, Debug)]
16 | pub enum Command {
17 | Report(ReportArgs),
18 | Review(ReviewArgs),
19 | Clean,
20 | DeadSnapshots(DeadSnapshotArgs),
21 | SizeCheck(SizeCheckArgs),
22 | }
23 |
24 | #[derive(ValueEnum, Debug, Clone, Copy)]
25 | #[clap(rename_all = "lowercase")]
26 | pub enum SizeOptimization {
27 | None,
28 | Fast,
29 | High,
30 | }
31 |
32 | impl SizeOptimization {
33 | pub fn to_level(&self) -> SizeOptimizationLevel {
34 | match self {
35 | SizeOptimization::None => SizeOptimizationLevel::None,
36 | SizeOptimization::Fast => SizeOptimizationLevel::Fast,
37 | SizeOptimization::High => SizeOptimizationLevel::High,
38 | }
39 | }
40 | }
41 |
42 | #[derive(Parser, Debug)]
43 | pub struct ReportArgs {
44 | /// Output filename
45 | #[arg(long)]
46 | pub output: Option,
47 |
48 | /// Embed images into the report
49 | #[arg(long, default_value_t = false)]
50 | pub embed_images: bool,
51 |
52 | /// Optimize image sizes in HTML report
53 | #[arg(long, default_value = "none")]
54 | pub optimize_size: SizeOptimization,
55 | }
56 |
57 | #[derive(Parser, Debug)]
58 | pub struct ReviewArgs {
59 | /// Port for web server
60 | #[arg(long, default_value_t = 7200)]
61 | pub port: u16,
62 |
63 | /// Optimize image sizes in generated HTML
64 | #[arg(long, default_value = "none")]
65 | pub optimize_size: SizeOptimization,
66 | }
67 |
68 | #[derive(Parser, Debug)]
69 | pub struct DeadSnapshotArgs {
70 | #[arg(long, default_value_t = false)]
71 | pub remove_files: bool,
72 | }
73 |
74 | #[derive(Parser, Debug)]
75 | pub struct SizeCheckArgs {
76 | /// If enabled, images on file system are replaced with optimized version
77 | #[arg(long, default_value_t = false)]
78 | pub optimize: bool,
79 |
80 | /// Command will fail if at least one image can be optimized by more than given ratio.
81 | /// E.g. --improvement-limit=0.8 means that error is signaled when an image can be optimized
82 | /// more than 80% of its original size
83 | /// (that is, the optimized image's size is 20% or less of the original).
84 | #[arg(long)]
85 | pub improvement_limit: Option,
86 |
87 | /// Command will fail if at least one image has a size larger than the given limit (in KiB).
88 | /// If --optimize is used then limit is computed from target size, otherwise the limit is applied
89 | /// on the original size
90 | #[arg(long)]
91 | pub size_limit: Option,
92 | }
93 |
--------------------------------------------------------------------------------
/kompari-tasks/src/lib.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2024 the Kompari Authors
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT
3 |
4 | // LINEBENDER LINT SET - lib.rs - v3
5 | // See https://linebender.org/wiki/canonical-lints/
6 | // These lints shouldn't apply to examples or tests.
7 | #![cfg_attr(not(test), warn(unused_crate_dependencies))]
8 | // These lints shouldn't apply to examples.
9 | // #![warn(clippy::print_stdout, clippy::print_stderr)]
10 | // Targeting e.g. 32-bit means structs containing usize can give false positives for 64-bit.
11 | #![cfg_attr(target_pointer_width = "64", warn(clippy::trivially_copy_pass_by_ref))]
12 | // END LINEBENDER LINT SET
13 | #![cfg_attr(docsrs, feature(doc_auto_cfg))]
14 |
15 | pub mod args;
16 | mod optimizations;
17 | mod task;
18 |
19 | pub use args::Args;
20 | pub use optimizations::{check_size_optimizations, OptimizationResult};
21 | pub use task::{Actions, Task};
22 |
--------------------------------------------------------------------------------
/kompari-tasks/src/optimizations.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2025 the Kompari Authors
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT
3 |
4 | use crate::args::SizeCheckArgs;
5 | use humansize::{format_size, DECIMAL};
6 | use kompari::{list_image_dir, optimize_png, SizeOptimizationLevel};
7 | use rayon::iter::{IntoParallelIterator, ParallelIterator};
8 | use std::cmp::min;
9 | use std::io::Write;
10 | use std::path::{Path, PathBuf};
11 | use termcolor::{Color, ColorSpec, WriteColor};
12 |
13 | #[derive(Debug)]
14 | pub struct OptimizationResult {
15 | pub path: PathBuf,
16 | pub old_size: usize,
17 | pub new_size: usize,
18 | pub improvement: f32,
19 | pub size_limit_breached: bool,
20 | pub improvement_limit_breached: bool,
21 | }
22 |
23 | pub fn check_file_optimizations(
24 | path: PathBuf,
25 | args: &SizeCheckArgs,
26 | ) -> kompari::Result