From 6bebab4cccc13b94daa82f7e95320ffb92e7a9bf Mon Sep 17 00:00:00 2001 From: Orion Gonzalez Date: Fri, 25 Oct 2024 03:52:14 +0200 Subject: [PATCH] first commit --- .github/dependabot.yml | 17 + .github/workflows/ci.yml | 89 ++++ .gitignore | 2 + .vscode/launch.json | 46 ++ Cargo.lock | 924 +++++++++++++++++++++++++++++++++++++++ Cargo.toml | 16 + LICENSE | 19 + README.md | 9 + src/app.rs | 558 +++++++++++++++++++++++ src/main.rs | 110 +++++ src/view.rs | 103 +++++ 11 files changed, 1893 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 src/app.rs create mode 100644 src/main.rs create mode 100644 src/view.rs diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..c2fabe1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + # Maintain dependencies for Cargo + - package-ecosystem: "cargo" + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + # Maintain dependencies for GitHub Actions + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..eebd111 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,89 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + - master + - develop + +env: + CARGO_TERM_COLOR: always + +# ensure that the workflow is only triggered once per PR, subsequent pushes to the PR will cancel +# and restart the workflow. See https://docs.github.com/en/actions/using-jobs/using-concurrency +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + fmt: + name: fmt + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: check formatting + run: cargo fmt -- --check + - name: Cache Cargo dependencies + uses: Swatinem/rust-cache@v2 + clippy: + name: clippy + runs-on: ubuntu-latest + permissions: + checks: write + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - name: Run clippy action + uses: clechasseur/rs-clippy-check@v3 + - name: Cache Cargo dependencies + uses: Swatinem/rust-cache@v2 + doc: + # run docs generation on nightly rather than stable. This enables features like + # https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html which allows an + # API be documented as only available in some specific platforms. + name: doc + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Rust nightly + uses: dtolnay/rust-toolchain@nightly + - name: Run cargo doc + run: cargo doc --no-deps --all-features + env: + RUSTDOCFLAGS: --cfg docsrs + test: + runs-on: ${{ matrix.os }} + name: test ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [macos-latest, windows-latest] + steps: + # if your project needs OpenSSL, uncomment this to fix Windows builds. + # it's commented out by default as the install command takes 5-10m. + # - run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append + # if: runner.os == 'Windows' + # - run: vcpkg install openssl:x64-windows-static-md + # if: runner.os == 'Windows' + - uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + # enable this ci template to run regardless of whether the lockfile is checked in or not + - name: cargo generate-lockfile + if: hashFiles('Cargo.lock') == '' + run: cargo generate-lockfile + - name: cargo test --locked + run: cargo test --locked --all-features --all-targets + - name: Cache Cargo dependencies + uses: Swatinem/rust-cache@v2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..de358ff --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +.vscode/ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..6c00d5a --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,46 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ +{ + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'compiletest-differ'", + "cargo": { + "args": [ + "build", + "--bin=compiletest-differ", + "--package=compiletest-differ" + ], + "filter": { + "name": "compiletest-differ", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'compiletest-differ'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=compiletest-differ", + "--package=compiletest-differ" + ], + "filter": { + "name": "compiletest-differ", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..8ad735f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,924 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cc" +version = "1.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cobs" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" + +[[package]] +name = "color-eyre" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "compact_str" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + +[[package]] +name = "compiletest-differ" +version = "0.1.0" +dependencies = [ + "color-eyre", + "crossterm", + "postcard", + "ratatui", + "ron", + "serde", + "serde_json", + "similar", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags", + "crossterm_winapi", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + +[[package]] +name = "instability" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.161" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "postcard" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7f0a8d620d71c457dd1d47df76bb18960378da56af4527aaa10f515eee732e" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "heapless", + "serde", +] + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ratatui" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +dependencies = [ + "bitflags", + "cassowary", + "compact_str", + "crossterm", + "indoc", + "instability", + "itertools", + "lru", + "paste", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width 0.2.0", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64", + "bitflags", + "serde", + "serde_derive", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.213" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.213" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "similar" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01680f5d178a369f817f43f3d399650272873a8e7588a7872f7e90edc71d60a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools", + "unicode-segmentation", + "unicode-width 0.1.14", +] + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..5d3a142 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "compiletest-differ" +version = "0.1.0" +authors = ["Orion Gonzalez "] +license = "MIT" +edition = "2021" + +[dependencies] +crossterm = "0.28.1" +ratatui = "0.29.0" +color-eyre = "0.6.3" +similar = "2.6.0" +serde = { version = "1.0.213", features = ["derive"] } +ron = "0.8.1" +postcard = { version = "1.0.10", features = ["alloc"] } +serde_json = "1.0.132" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d13cc4b --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..48124b0 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# compiletest-differ + +Very wip tui to help with ui tests for the rust compiler. + +## License + +This project is licensed under the MIT license ([LICENSE] or ) + +[LICENSE]: ./LICENSE diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..2d02c97 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,558 @@ +use std::{ + fs::read_to_string, + mem, + path::{Path, PathBuf}, + process::exit, +}; + +use color_eyre::Result; +use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}; +use ratatui::{ + style::Stylize, + text::{Line, Span, Text}, + widgets::{Block, Paragraph}, + DefaultTerminal, Frame, +}; + +use crate::{ + view::{DiffShow, FullLayout, ShowMode}, + Stats, Stream, +}; + +#[derive(Debug, Clone)] +pub struct TestData { + pub test_code: String, + pub actual: String, + pub expect: String, + pub stream: Stream, + pub test_name: String, + // TODO: I don't know how to extract it from stderr... probably need to modify compiletest + pub rustc_args: String, + // Used for blessing + pub expected_path: PathBuf, +} + +#[derive(Debug, Default)] +pub struct App { + /// Is the application running? + pub running: bool, + pub config: Config, + pub prev_view: ShowMode, + pub current_test: usize, + pub current_stream: Stream, + pub stats: Stats, + pub paths: Vec<&'static str>, + pub cached_streams: CachedStreams, // This caches the loading of the test data + pub rust_path: PathBuf, + + pub scroll_pos_diff: u16, + pub scroll_pos_code: u16, +} + +#[derive(Debug, Clone, Default)] +enum CachedData { + #[default] + Unloaded, + Missing, + Present(TestData), +} + +#[derive(Debug, Clone, Default)] +pub struct CachedStreams { + stderr: CachedData, + stdout: CachedData, +} + +impl App { + pub fn load_curr_data(&mut self) { + let path_str = self.paths[self.current_test]; + let path = Path::new(path_str); + let test_code = self.rust_path.join(path); + let expected_stderr_path = test_code.with_extension("stderr"); + let expected_stdout_path = test_code.with_extension("stdout"); + let target_path = path + // In the build it has the path test instead of tests + .strip_prefix("tests/") + .expect("Path didn't start with tests/"); + // FIXME: get the actual triplet + let actual_path = self + .rust_path + .join("build/x86_64-unknown-linux-gnu/test") + .join(target_path) + .with_extension("") + .join(path.file_stem().unwrap()); + let actual_stderr = actual_path.with_extension("stderr"); + let actual_stdout = actual_path.with_extension("stdout"); + + let Ok(test_code) = read_to_string(&test_code) else { + // TODO: Handle this + self.cached_streams.stderr = CachedData::Missing; + self.cached_streams.stdout = CachedData::Missing; + return; + }; + let expected_stderr = read_to_string(&expected_stderr_path).ok(); + let expected_stdout = read_to_string(&expected_stdout_path).ok(); + let actual_stderr = read_to_string(actual_stderr).ok(); + let actual_stdout = read_to_string(actual_stdout).ok(); + + if expected_stderr.is_some() || actual_stderr.is_some() && expected_stderr != actual_stderr + { + let actual = actual_stderr.unwrap_or_default(); + let expect = expected_stderr.unwrap_or_default(); + let stream = TestData { + test_code: test_code.clone(), + actual, + expect, + stream: Stream::Stderr, + test_name: path_str.to_owned(), + rustc_args: "TODO".to_owned(), + expected_path: expected_stderr_path, + // TODO: Where do I get this info + // number_of_errs: 1, + }; + self.cached_streams.stderr = CachedData::Present(stream); + } else { + self.cached_streams.stderr = CachedData::Missing; + } + + if expected_stdout.is_some() || actual_stdout.is_some() && expected_stdout != actual_stdout + { + let actual = actual_stdout.unwrap_or_default(); + let expect = expected_stdout.unwrap_or_default(); + let stream = TestData { + test_code: test_code.clone(), + actual, + expect, + stream: Stream::Stdout, + test_name: path_str.to_owned(), + rustc_args: "TODO".to_owned(), + expected_path: expected_stdout_path, + // TODO: Where do I get this info + // number_of_outs: 1, + }; + self.cached_streams.stdout = CachedData::Present(stream); + } else { + self.cached_streams.stdout = CachedData::Missing; + // if matches!(self.cached_streams.stderr, CachedData::Missing) { + // unreachable!("what"); + // } + } + } + + pub fn advance_test(&mut self) { + self.reset_scroll(); + self.current_stream = Stream::Stderr; + self.current_test += 1; + self.cached_streams = Default::default(); + if self.current_test == self.paths.len() { + ratatui::restore(); + exit(0); + } + } + + pub fn reset_scroll(&mut self) { + self.scroll_pos_code = 0; + self.scroll_pos_diff = 0; + } + + pub fn advance_stream(&mut self) { + self.reset_scroll(); + match self.current_stream { + Stream::Stderr => self.current_stream = Stream::Stdout, + Stream::Stdout => self.advance_test(), + } + } + + pub fn previous_test(&mut self) { + self.reset_scroll(); + self.current_stream = Stream::Stderr; + self.current_test = self.current_test.saturating_sub(1); + self.cached_streams = Default::default(); + } + + pub fn request_curr_test(&mut self) -> &TestData { + match self.current_stream { + // We do this one first, then the other + Stream::Stderr => match self.cached_streams.stderr { + CachedData::Unloaded => { + self.load_curr_data(); + self.request_curr_test() + } + CachedData::Missing => { + self.current_stream = Stream::Stdout; + self.request_curr_test() + } + CachedData::Present(ref test_data) => test_data, + }, + Stream::Stdout => match self.cached_streams.stdout { + CachedData::Missing => { + self.advance_test(); + self.request_curr_test() + } + CachedData::Present(ref test_data) => test_data, + CachedData::Unloaded => unreachable!(), + }, + } + } + + /// Run the application's main loop. + pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { + self.running = true; + while self.running { + terminal.draw(|frame| self.draw(frame))?; + self.handle_crossterm_events()?; + } + Ok(()) + } + + /// Renders the user interface. + /// + /// This is where you add new widgets. See the following resources for more information: + /// - + /// - + fn draw(&mut self, frame: &mut Frame) { + fn mk_paragraph<'a>(title: &'a str, text: impl Into>) -> Paragraph<'a> { + let line = Line::from(title).bold().centered(); + let block = Block::bordered().title_top(line); + Paragraph::new(text).block(block) + } + + let layout = FullLayout::new(&self.config, frame.area()); + let current_test = self.current_test; + let total_tests = self.stats.failed; + let ok = self.stats.passed; + let ignored = self.stats.ignored; + let diff_mode = self.config.diff_mode; + let scroll_code = (self.scroll_pos_code, 0); + let scroll_diff = (self.scroll_pos_diff, 0); + let diff_mode = self.config.diff_mode; + + if let Some(rect) = layout.help_bar { + frame.render_widget(Paragraph::new(self.config.help_string()).centered(), rect); + } + + let TestData { + actual, + expect, + stream, + test_name, + rustc_args, + test_code, + expected_path, + } = self.request_curr_test(); + + let top_bar_text = format!("Showing {test_name} {stream:?}. {current_test}/{total_tests}. Ok: {ok}, Ignored: {ignored}"); + frame.render_widget(Paragraph::new(top_bar_text).centered(), layout.top_bar); + + match layout.diff_show { + DiffShow::SideBySide { code, lhs, rhs } => { + let (expect, actual) = diff_vertical(expect, actual, diff_mode); + frame.render_widget( + mk_paragraph("code", test_code.as_str()).scroll(scroll_code), + code, + ); + frame.render_widget(mk_paragraph("expected", expect).scroll(scroll_diff), lhs); + frame.render_widget(mk_paragraph("actual", actual).scroll(scroll_diff), rhs); + } + DiffShow::SideBySideOnly { rhs, lhs } => { + let (expect, actual) = diff_vertical(expect, actual, diff_mode); + frame.render_widget(mk_paragraph("expected", expect).scroll(scroll_diff), lhs); + frame.render_widget(mk_paragraph("actual", actual).scroll(scroll_diff), rhs); + } + DiffShow::Vertical { code, diff } => { + let tx_diff = diff_horizontal(expect, actual, diff_mode); + frame.render_widget( + mk_paragraph("code", test_code.as_str()).scroll(scroll_code), + code, + ); + frame.render_widget(mk_paragraph("diff", tx_diff).scroll(scroll_diff), diff); + } + DiffShow::VerticalOnly { diff } => { + let tx_diff = diff_horizontal(expect, actual, diff_mode); + frame.render_widget(mk_paragraph("diff", tx_diff).scroll(scroll_diff), diff); + } + DiffShow::RustcArgs { args, oneline } => { + // This could be done inline but idc. + let text = if oneline { + rustc_args.to_owned() + } else { + rustc_args.replace(' ', "\n") + }; + + frame.render_widget(mk_paragraph("rustc arguments", text.as_str()), args); + } + }; + } + + /// Reads the crossterm events and updates the state of [`App`]. + /// + /// If your application needs to perform work in between handling events, you can use the + /// [`event::poll`] function to check if there are any events available with a timeout. + fn handle_crossterm_events(&mut self) -> Result<()> { + match event::read()? { + // it's important to check KeyEventKind::Press to avoid handling key release events + Event::Key(key) if key.kind == KeyEventKind::Press => self.on_key_event(key), + Event::Mouse(_) => {} + Event::Resize(_, _) => {} + _ => {} + } + Ok(()) + } + + /// Handles the key events and updates the state of [`App`]. + fn on_key_event(&mut self, key: KeyEvent) { + match key.code { + KeyCode::Esc | KeyCode::Char('q') => self.quit(), + KeyCode::Char('d') => { + self.config.diff_mode.rotate_next(); + } + KeyCode::Char('r') => match self.config.show_mode { + ShowMode::RustcArgs { oneline } => { + self.config.show_mode = self.prev_view; + self.prev_view = ShowMode::RustcArgs { oneline } + } + _ => { + self.prev_view = self.config.show_mode; + self.config.show_mode = ShowMode::RustcArgs { oneline: false } + } + }, + KeyCode::Char('o') => { + if let ShowMode::RustcArgs { oneline } = self.config.show_mode { + self.config.show_mode = ShowMode::RustcArgs { oneline: !oneline } + } + } + KeyCode::Char('p') => { + mem::swap(&mut self.config.show_mode, &mut self.prev_view); + } + KeyCode::Char('h') => { + self.config.hide_help = !self.config.hide_help; + } + KeyCode::Char('b') => { + self.bless(); + } + KeyCode::Char('n') => { + self.advance_stream(); + } + KeyCode::Char('N') => { + self.previous_test(); + } + KeyCode::Char('j') => { + self.scroll_pos_diff += 1; + } + KeyCode::Char('k') => { + self.scroll_pos_diff = self.scroll_pos_diff.saturating_sub(1); + } + KeyCode::Char('J') => { + self.scroll_pos_code += 1; + } + KeyCode::Char('K') => { + self.scroll_pos_code = self.scroll_pos_code.saturating_sub(1); + } + KeyCode::Char('c') => match self.config.show_mode { + ShowMode::SideBySide => self.config.show_mode = ShowMode::SideBySideOnly, + ShowMode::SideBySideOnly => self.config.show_mode = ShowMode::SideBySide, + ShowMode::Vertical => self.config.show_mode = ShowMode::VerticalOnly, + ShowMode::VerticalOnly => self.config.show_mode = ShowMode::Vertical, + _ => {} + }, + KeyCode::Char('s') => match self.config.show_mode { + ShowMode::SideBySide => self.config.show_mode = ShowMode::Vertical, + ShowMode::SideBySideOnly => self.config.show_mode = ShowMode::VerticalOnly, + ShowMode::Vertical => self.config.show_mode = ShowMode::SideBySide, + ShowMode::VerticalOnly => self.config.show_mode = ShowMode::SideBySideOnly, + _ => {} + }, + _ => {} + }; + } + + /// Set running to false to quit the application. + fn quit(&mut self) { + self.running = false; + } + + fn bless(&mut self) { + let data = match self.current_stream { + Stream::Stderr => &self.cached_streams.stderr, + Stream::Stdout => &self.cached_streams.stdout, + }; + if let CachedData::Present(data) = data { + std::fs::write(&data.expected_path, &data.actual).unwrap(); + } + self.advance_stream(); + } +} + +#[derive(Debug, Clone, Copy, Default)] +pub struct Config { + pub diff_mode: DiffMode, + pub show_mode: ShowMode, + pub hide_help: bool, +} + +impl Config { + pub fn help_string(&self) -> String { + // FIXME: the colors don't work! + // Need to use proper ratatui spans to fix this.. + let bless = format!("{}less", "b".blue().bold()); + let help = format!("{}elp toggle", "h".green().bold()); + let previous_mode = format!("{}revious mode", "p".yellow().bold()); + let rustc_args = format!("{}ustc args", "r".green().bold()); + let next_diff = format!( + "next {}iff mode: {}", + "d".red().bold(), + self.diff_mode.next_text() + ); + + let vertical = format!("vertical {}how mode", "s".red().bold()); + let horizontal = format!("horizontal {}how mode", "s".red().bold()); + let show_code = format!("show {}ode", "c".magenta().bold()); + let hide_code = format!("hide {}ode", "c".magenta().bold()); + + let show_mode_specific = match self.show_mode { + ShowMode::SideBySide => { + format!("{vertical} | {hide_code} | {next_diff} | {rustc_args}") + } + ShowMode::SideBySideOnly => { + format!("{vertical} | {show_code} | {next_diff} | {rustc_args}") + } + ShowMode::Vertical => { + format!("{horizontal} | {hide_code} | {next_diff} | {rustc_args}") + } + ShowMode::VerticalOnly => { + format!("{horizontal} | {show_code} | {next_diff} | {rustc_args}") + } + ShowMode::RustcArgs { oneline } => { + format!( + "{} {}neline", + "o".red(), + if oneline { "disable" } else { "enable" } + ) + } + }; + + format!("{bless} | {show_mode_specific} | {previous_mode} | {help}") + } +} + +#[derive(Debug, Clone, Copy, Default)] +pub enum DiffMode { + #[default] + Line, + Word, // TODO: These are buggy + Char, +} + +impl DiffMode { + pub const fn rotate_next(&mut self) { + match self { + DiffMode::Char => *self = DiffMode::Word, + DiffMode::Word => *self = DiffMode::Line, + DiffMode::Line => *self = DiffMode::Char, + } + } + + pub const fn next_text(&self) -> &'static str { + match self { + DiffMode::Char => "word", + DiffMode::Word => "line", + DiffMode::Line => "char", + } + } +} + +fn diff_vertical_linewise<'a>(lhs: &'a str, rhs: &'a str) -> (Text<'a>, Text<'a>) { + let diff = similar::TextDiff::from_lines(lhs, rhs); + + let mut lhs: Vec> = vec![]; + let mut rhs: Vec> = vec![]; + for hunk in diff.iter_all_changes() { + match hunk.tag() { + similar::ChangeTag::Equal => { + lhs.push(hunk.value().into()); + rhs.push(hunk.value().into()); + } + similar::ChangeTag::Delete => { + lhs.push(hunk.value().red().into()); + } + similar::ChangeTag::Insert => { + rhs.push(hunk.value().green().bold().into()); + } + } + } + + (lhs.into(), rhs.into()) +} + +fn diff_vertical<'a>(lhs: &'a str, rhs: &'a str, diffmode: DiffMode) -> (Text<'a>, Text<'a>) { + let diff = match diffmode { + DiffMode::Char => similar::TextDiff::from_chars(lhs, rhs), + DiffMode::Word => similar::TextDiff::from_words(lhs, rhs), + DiffMode::Line => return diff_vertical_linewise(lhs, rhs), + }; + + let diff = similar::TextDiffConfig::default() + .newline_terminated(true) + .diff_words(lhs, rhs); + + let mut lhs: Vec> = vec![]; + let mut rhs: Vec> = vec![]; + for hunk in diff.iter_all_changes() { + match hunk.tag() { + similar::ChangeTag::Equal => { + lhs.push(hunk.value().into()); + rhs.push(hunk.value().into()); + } + similar::ChangeTag::Delete => { + lhs.push(hunk.value().red()); + } + similar::ChangeTag::Insert => { + rhs.push(hunk.value().green()); + } + } + } + (Line::from(rhs).into(), Line::from(lhs).into()) +} + +fn diff_horizontal_linewise<'a>(lhs: &'a str, rhs: &'a str) -> Text<'a> { + let diff = similar::TextDiff::from_lines(lhs, rhs); + + let mut text: Vec> = vec![]; + for hunk in diff.iter_all_changes() { + match hunk.tag() { + similar::ChangeTag::Equal => { + text.push(hunk.value().into()); + } + similar::ChangeTag::Delete => { + text.push(hunk.value().red().into()); + } + similar::ChangeTag::Insert => { + text.push(hunk.value().green().into()); + } + } + } + text.into() +} + +fn diff_horizontal<'a>(lhs: &'a str, rhs: &'a str, diffmode: DiffMode) -> Text<'a> { + let diff = match diffmode { + DiffMode::Char => similar::TextDiff::from_chars(lhs, rhs), + DiffMode::Word => similar::TextDiff::from_words(lhs, rhs), + DiffMode::Line => return diff_horizontal_linewise(lhs, rhs), + }; + let mut text: Vec> = vec![]; + for hunk in diff.iter_all_changes() { + match hunk.tag() { + similar::ChangeTag::Equal => { + text.push(hunk.value().into()); + } + similar::ChangeTag::Delete => { + text.push(hunk.value().red()); + } + similar::ChangeTag::Insert => { + text.push(hunk.value().green()); + } + } + } + Line::from(text).into() +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..6cd8be0 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,110 @@ +use std::{fs::read_to_string, path::PathBuf}; + +pub use app::App; +use serde::Deserialize; + +pub mod app; +mod view; + +#[derive(Debug, Clone, Copy, Default)] +pub enum Stream { + #[default] + Stderr, + Stdout, +} + +#[derive(Deserialize)] +#[serde(tag = "type")] +enum Item<'a> { + #[serde(rename = "test")] + Test { name: &'a str, event: &'a str }, + #[serde(rename = "suite")] + Suite { + failed: u32, + passed: u32, + ignored: u32, + }, +} + +#[derive(Debug, Clone, Copy, Default)] +pub struct Stats { + failed: u32, + passed: u32, + ignored: u32, +} + +fn parse_events(events: &str) -> (Vec<&str>, Stats) { + let lines = events + .lines() + .filter_map(|x| serde_json::from_str::(x).ok()) + .skip(1); + + let mut failed = vec![]; + let mut stats = None; + let mut ok_count = 0; + + for event in lines { + match event { + Item::Test { name, event } => { + if event != "failed" { + ok_count += 1; + continue; + } + let Some((_, path)) = name.split_once("[ui] ") else { + // It's not UI test + continue; + }; + failed.push(path); + } + Item::Suite { + failed, + passed, + ignored, + } => { + stats = Some(Stats { + failed, + passed, + ignored, + }) + } + } + } + + let stats = stats.unwrap_or(Stats { + failed: failed.len() as u32, + passed: ok_count, + ignored: 0, + }); + + (failed, stats) +} + +fn main() -> color_eyre::Result<()> { + color_eyre::install()?; + let file = std::env::args() + .nth(1) + .unwrap_or("/home/ardi/repos/rust/blah.json".to_owned()); + let test_data = read_to_string(&file) + .expect("Can't find json output") + .leak(); + let (paths, stats) = parse_events(test_data); + + if paths.is_empty() { + println!( + "No failed tests: {} ok and {} ignored", + stats.passed, stats.ignored + ); + return Ok(()); + } + + let terminal = ratatui::init(); + let app = App { + paths, + stats, + rust_path: PathBuf::from("/home/ardi/repos/rust"), + ..Default::default() + }; + let result = app.run(terminal); + ratatui::restore(); + result +} diff --git a/src/view.rs b/src/view.rs new file mode 100644 index 0000000..001db33 --- /dev/null +++ b/src/view.rs @@ -0,0 +1,103 @@ +use ratatui::prelude::*; +use ratatui::widgets::{Block, Paragraph}; + +use crate::app::{Config, DiffMode, TestData}; +use crate::Stats; + +pub struct FullLayout { + pub top_bar: Rect, + pub diff_show: DiffShow, + pub help_bar: Option, +} + +impl FullLayout { + pub fn new(cfg: &Config, area: Rect) -> Self { + let rects = Layout::default() + .direction(Direction::Vertical) + .constraints(if !cfg.hide_help { + [ + Constraint::Min(1), + Constraint::Percentage(100), + Constraint::Min(1), + ] + .as_slice() + } else { + [Constraint::Min(1), Constraint::Percentage(100)].as_slice() + }) + .split(area); + let diff_show = DiffShow::new(cfg.show_mode, rects[1]); + Self { + top_bar: rects[0], + diff_show, + help_bar: if !cfg.hide_help { Some(rects[2]) } else { None }, + } + } +} + +#[derive(Debug, Clone, Copy, Default)] +pub enum ShowMode { + #[default] + Vertical, + VerticalOnly, + SideBySide, + SideBySideOnly, + RustcArgs { + oneline: bool, + }, +} + +#[derive(Debug, Clone)] +pub enum DiffShow { + Vertical { code: Rect, diff: Rect }, + VerticalOnly { diff: Rect }, + SideBySide { code: Rect, rhs: Rect, lhs: Rect }, + SideBySideOnly { rhs: Rect, lhs: Rect }, + RustcArgs { args: Rect, oneline: bool }, +} + +impl DiffShow { + pub fn new(mode: ShowMode, rect: Rect) -> Self { + match mode { + ShowMode::SideBySide => { + let layout = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) + .split(rect); + let diff_layout = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) + .split(layout[1]); + Self::SideBySide { + code: layout[0], + rhs: diff_layout[0], + lhs: diff_layout[1], + } + } + ShowMode::SideBySideOnly => { + let diff_layout = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) + .split(rect); + Self::SideBySideOnly { + rhs: diff_layout[0], + lhs: diff_layout[1], + } + } + ShowMode::Vertical => { + let layout = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) + .split(rect); + Self::Vertical { + code: layout[0], + diff: layout[1], + } + } + ShowMode::VerticalOnly => DiffShow::VerticalOnly { diff: rect }, + ShowMode::RustcArgs { oneline } => DiffShow::RustcArgs { + args: rect, + oneline, + }, + } + } +}