diff --git a/.forgejo/workflows/ci.yaml b/.forgejo/workflows/ci.yaml index a8bfcfc..cda59f0 100644 --- a/.forgejo/workflows/ci.yaml +++ b/.forgejo/workflows/ci.yaml @@ -16,13 +16,7 @@ jobs: - name: ๐Ÿ“Ž Clippy run: cargo clippy --all -- -D warnings - name: ๐Ÿงช Test - run: cargo nextest run --config-file ~/.config/nextest.toml --profile ci - - name: ๐Ÿ’Œ Upload test report - if: always() - uses: https://code.forgejo.org/actions/upload-artifact - with: - name: test - path: target/nextest/ci/junit.xml + run: cargo test release: runs-on: cimaster-latest diff --git a/.gitignore b/.gitignore index 4644368..a237387 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,3 @@ /dist /.env *.snap.new -/tests/testfiles/sites_data diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0aca880..c77c173 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,11 +10,3 @@ repos: - id: cargo-fmt - id: cargo-clippy args: ["--all", "--tests", "--", "-D", "warnings"] - - - repo: local - hooks: - - id: compress-res - name: Compress resources - language: system - entry: zopfli - files: "^resources/.+.css$" diff --git a/Cargo.lock b/Cargo.lock index db4c1c1..7238e51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -146,7 +146,6 @@ dependencies = [ "async_zip", "axum", "axum-extra", - "axum-test", "comrak", "dotenvy", "envy", @@ -154,10 +153,9 @@ dependencies = [ "futures-lite", "governor", "headers", - "http 1.1.0", - "httpdate", + "hex", + "http", "humansize", - "insta", "junit-parser", "mime", "mime_guess", @@ -172,14 +170,12 @@ dependencies = [ "regex", "reqwest", "rstest", - "scraper", "serde", "serde-env", "serde-hex", "serde_json", "serde_urlencoded", "syntect", - "temp_testdir", "thiserror", "tokio", "tokio-util", @@ -242,12 +238,6 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "auto-future" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c1e7e457ea78e524f48639f551fd79703ac3f2237f5ecccdf4708f8a75ad373" - [[package]] name = "autocfg" version = "1.3.0" @@ -264,7 +254,7 @@ dependencies = [ "axum-core", "bytes", "futures-util", - "http 1.1.0", + "http", "http-body", "http-body-util", "hyper", @@ -279,7 +269,6 @@ dependencies = [ "serde", "serde_json", "serde_path_to_error", - "serde_urlencoded", "sync_wrapper 1.0.1", "tokio", "tower", @@ -297,7 +286,7 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.1.0", + "http", "http-body", "http-body-util", "mime", @@ -320,7 +309,7 @@ dependencies = [ "bytes", "futures-util", "headers", - "http 1.1.0", + "http", "http-body", "http-body-util", "mime", @@ -332,34 +321,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "axum-test" -version = "15.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6eaf3651cc3b15185c6033db9cc40676b02ffaa69278679c5f018de2bcae598" -dependencies = [ - "anyhow", - "auto-future", - "axum", - "bytes", - "cookie", - "http 1.1.0", - "http-body-util", - "hyper", - "hyper-util", - "mime", - "pretty_assertions", - "reserve-port", - "rust-multipart-rfc7578_2", - "serde", - "serde_json", - "serde_urlencoded", - "smallvec 1.13.2", - "tokio", - "tower", - "url", -] - [[package]] name = "backtrace" version = "0.3.72" @@ -562,16 +523,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" -[[package]] -name = "cookie" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" -dependencies = [ - "time", - "version_check", -] - [[package]] name = "core-foundation" version = "0.9.4" @@ -622,29 +573,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "cssparser" -version = "0.31.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b3df4f93e5fbbe73ec01ec8d3f68bba73107993a5b1e7519273c32db9b0d5be" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa", - "phf 0.11.2", - "smallvec 1.13.2", -] - -[[package]] -name = "cssparser-macros" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" -dependencies = [ - "quote", - "syn 2.0.66", -] - [[package]] name = "darling" version = "0.20.9" @@ -759,12 +687,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00" -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - [[package]] name = "digest" version = "0.10.7" @@ -788,21 +710,6 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" -[[package]] -name = "dtoa-short" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" -dependencies = [ - "dtoa", -] - -[[package]] -name = "ego-tree" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a68a4904193147e0a8dec3314640e6db742afd5f6e634f428a6af230d9b3591" - [[package]] name = "encode_unicode" version = "0.3.6" @@ -907,16 +814,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "futf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" -dependencies = [ - "mac", - "new_debug_unreachable", -] - [[package]] name = "futures" version = "0.3.30" @@ -1025,15 +922,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -1044,15 +932,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "getopts" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" -dependencies = [ - "unicode-width", -] - [[package]] name = "getrandom" version = "0.2.15" @@ -1107,7 +986,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.1.0", + "http", "indexmap", "slab", "tokio", @@ -1130,7 +1009,7 @@ dependencies = [ "base64 0.21.7", "bytes", "headers-core", - "http 1.1.0", + "http", "httpdate", "mime", "sha1", @@ -1142,7 +1021,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" dependencies = [ - "http 1.1.0", + "http", ] [[package]] @@ -1151,6 +1030,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hmac" version = "0.12.1" @@ -1160,31 +1045,6 @@ dependencies = [ "digest", ] -[[package]] -name = "html5ever" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" -dependencies = [ - "log", - "mac", - "markup5ever", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - [[package]] name = "http" version = "1.1.0" @@ -1203,7 +1063,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" dependencies = [ "bytes", - "http 1.1.0", + "http", ] [[package]] @@ -1214,7 +1074,7 @@ checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" dependencies = [ "bytes", "futures-core", - "http 1.1.0", + "http", "http-body", "pin-project-lite", ] @@ -1256,7 +1116,7 @@ dependencies = [ "futures-channel", "futures-util", "h2", - "http 1.1.0", + "http", "http-body", "httparse", "httpdate", @@ -1274,7 +1134,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" dependencies = [ "futures-util", - "http 1.1.0", + "http", "hyper", "hyper-util", "rustls", @@ -1309,7 +1169,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.1.0", + "http", "http-body", "hyper", "pin-project-lite", @@ -1497,26 +1357,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "mac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" - -[[package]] -name = "markup5ever" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" -dependencies = [ - "log", - "phf 0.10.1", - "phf_codegen", - "string_cache", - "string_cache_codegen", - "tendril", -] - [[package]] name = "matchit" version = "0.7.3" @@ -1588,12 +1428,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - [[package]] name = "no-std-compat" version = "0.4.1" @@ -1799,86 +1633,6 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "phf" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" -dependencies = [ - "phf_shared 0.10.0", -] - -[[package]] -name = "phf" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" -dependencies = [ - "phf_macros", - "phf_shared 0.11.2", -] - -[[package]] -name = "phf_codegen" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", -] - -[[package]] -name = "phf_generator" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" -dependencies = [ - "phf_shared 0.10.0", - "rand", -] - -[[package]] -name = "phf_generator" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" -dependencies = [ - "phf_shared 0.11.2", - "rand", -] - -[[package]] -name = "phf_macros" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" -dependencies = [ - "phf_generator 0.11.2", - "phf_shared 0.11.2", - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher", -] - -[[package]] -name = "phf_shared" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" -dependencies = [ - "siphasher", -] - [[package]] name = "pin-project" version = "1.1.5" @@ -1935,22 +1689,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "pretty_assertions" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" -dependencies = [ - "diff", - "yansi", -] - [[package]] name = "prettyplease" version = "0.1.25" @@ -2152,7 +1890,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http 1.1.0", + "http", "http-body", "http-body-util", "hyper", @@ -2189,16 +1927,6 @@ dependencies = [ "winreg", ] -[[package]] -name = "reserve-port" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9838134a2bfaa8e1f40738fcc972ac799de6e0e06b5157acb95fc2b05a0ea283" -dependencies = [ - "lazy_static", - "thiserror", -] - [[package]] name = "ring" version = "0.17.8" @@ -2242,22 +1970,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "rust-multipart-rfc7578_2" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b748410c0afdef2ebbe3685a6a862e2ee937127cdaae623336a459451c8d57" -dependencies = [ - "bytes", - "futures-core", - "futures-util", - "http 0.2.12", - "mime", - "mime_guess", - "rand", - "thiserror", -] - [[package]] name = "rustc-demangle" version = "0.1.24" @@ -2388,22 +2100,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "scraper" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b80b33679ff7a0ea53d37f3b39de77ea0c75b12c5805ac43ec0c33b3051af1b" -dependencies = [ - "ahash", - "cssparser", - "ego-tree", - "getopts", - "html5ever", - "once_cell", - "selectors", - "tendril", -] - [[package]] name = "security-framework" version = "2.11.0" @@ -2427,25 +2123,6 @@ dependencies = [ "libc", ] -[[package]] -name = "selectors" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06" -dependencies = [ - "bitflags 2.5.0", - "cssparser", - "derive_more", - "fxhash", - "log", - "new_debug_unreachable", - "phf 0.10.1", - "phf_codegen", - "precomputed-hash", - "servo_arc", - "smallvec 1.13.2", -] - [[package]] name = "semver" version = "1.0.23" @@ -2527,15 +2204,6 @@ dependencies = [ "serde", ] -[[package]] -name = "servo_arc" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d036d71a959e00c77a63538b90a6c2390969f9772b096ea837205c6bd0491a44" -dependencies = [ - "stable_deref_trait", -] - [[package]] name = "sha1" version = "0.10.6" @@ -2582,12 +2250,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - [[package]] name = "slab" version = "0.4.9" @@ -2647,38 +2309,6 @@ 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 = "string_cache" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" -dependencies = [ - "new_debug_unreachable", - "once_cell", - "parking_lot", - "phf_shared 0.10.0", - "precomputed-hash", - "serde", -] - -[[package]] -name = "string_cache_codegen" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", - "proc-macro2", - "quote", -] - [[package]] name = "strsim" version = "0.11.1" @@ -2745,12 +2375,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "temp_testdir" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "921f1e9c427802414907a48b21a6504ff6b3a15a1a3cf37e699590949ad9befc" - [[package]] name = "tempfile" version = "3.10.1" @@ -2763,17 +2387,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "tendril" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" -dependencies = [ - "futf", - "mac", - "utf-8", -] - [[package]] name = "thiserror" version = "1.0.61" @@ -2852,9 +2465,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -2871,9 +2484,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", @@ -2954,7 +2567,6 @@ dependencies = [ "tokio", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -2965,7 +2577,7 @@ checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ "bitflags 2.5.0", "bytes", - "http 1.1.0", + "http", "http-body", "http-body-util", "pin-project-lite", @@ -2992,7 +2604,6 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -3133,12 +2744,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - [[package]] name = "utf8parse" version = "0.2.1" @@ -3518,12 +3123,6 @@ dependencies = [ "lzma-sys", ] -[[package]] -name = "yansi" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" - [[package]] name = "yansi-term" version = "0.1.2" diff --git a/Cargo.toml b/Cargo.toml index 45c65f1..8e097e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ flate2 = "1.0.30" futures-lite = "2.3.0" governor = "0.6.3" headers = "0.4.0" +hex = "0.4.3" http = "1.1.0" humansize = "2.1.3" junit-parser = { path = "crates/junit-parser" } @@ -78,14 +79,8 @@ yarte = "0.15.7" yarte_helpers = "0.15.8" [dev-dependencies] -axum-test = "15.0.1" -flate2 = "1.0.30" -httpdate = "1.0.3" -insta = { version = "1.39.0", features = ["json"] } proptest = "1.4.0" rstest = { version = "0.20.0", default-features = false } -scraper = "0.19.0" -temp_testdir = "0.2.3" [workspace] members = [".", "crates/*"] diff --git a/Justfile b/Justfile index f4ecb96..7e99822 100644 --- a/Justfile +++ b/Justfile @@ -1,9 +1,6 @@ test: cargo test -compress-res: - cd resources && zopfli *.css - release: #!/usr/bin/env bash set -e diff --git a/crates/junit-parser/src/lib.rs b/crates/junit-parser/src/lib.rs index a74f94d..b6a3a56 100644 --- a/crates/junit-parser/src/lib.rs +++ b/crates/junit-parser/src/lib.rs @@ -317,47 +317,6 @@ impl TestCase { } Ok(tc) } - - pub fn status_txt(&self) -> Cow<'static, str> { - match self.status { - TestStatus::Success => "Success".into(), - TestStatus::Error(_) => "Error".into(), - TestStatus::Failure(_) => { - if self.retries.is_empty() { - "Failure".into() - } else { - format!("Failure (after {} retries)", self.retries.len()).into() - } - } - TestStatus::Flaky => format!( - "Flaky (passed after {} failed attempt{})", - self.retries.len(), - if self.retries.len() == 1 { "s" } else { "" } - ) - .into(), - TestStatus::Skipped => "Skipped".into(), - } - } -} - -impl TestStatus { - pub fn id(&self) -> &'static str { - match self { - TestStatus::Success => "success", - TestStatus::Error(_) => "error", - TestStatus::Failure(_) => "failure", - TestStatus::Flaky => "flaky", - TestStatus::Skipped => "skipped", - } - } - - pub fn message(&self) -> Option<&Message> { - match self { - TestStatus::Error(msg) => Some(msg), - TestStatus::Failure(msg) => Some(msg), - _ => None, - } - } } impl Message { diff --git a/resources/content.css b/resources/content.css index 68f7ffa..fdb494c 100644 --- a/resources/content.css +++ b/resources/content.css @@ -3,11 +3,9 @@ .viewer > pre { padding: 10px 20px; font-size: 14px; - overflow-x: auto; } -pre, -code { +pre, code { color: #cccccc; background-color: #1c1c1c; } @@ -52,12 +50,12 @@ code { font-size: inherit; } .prose h1 { - border-bottom: 1px solid var(--color-border2); + border-bottom: 1px solid var(--color-secondary); padding-bottom: 0.3em; font-size: 2em; } .prose h2 { - border-bottom: 1px solid var(--color-border2); + border-bottom: 1px solid var(--color-secondary); padding-bottom: 0.3em; font-size: 1.5em; } @@ -458,242 +456,3 @@ code { .markup.untracked.git_gutter { color: #696d70; } - -.junit { - display: flex; - overflow: hidden; -} -@media (max-width: 1000px) { - .junit { - flex-wrap: wrap; - } - #preview-margin { - display: none; - } -} -.junit > div:not(:last-child) { - border-right: solid 1px var(--color-border); -} -#junit-suites, -#junit-cases { - min-width: 300px; - width: 300px; -} -#junit-preview { - flex-grow: 1; - margin-bottom: 40px; - overflow-x: auto; -} - -#junit-preview h2 { - border-bottom: 2px solid var(--color-status); -} - -#junit-preview h2 i { - color: var(--color-status); -} - -.junit ul { - list-style-type: none; -} - -.junit ul > li, -.colsubtitle { - border-bottom: 1px dashed var(--color-border2); -} - -.colsubtitle > button { - display: inline-flex; - align-items: center; - gap: 4px; - margin: 2px 0; - padding: 4px 8px; - border: 2px solid var(--color-status, var(--color-btn)); -} -.colsubtitle button.active { - background-color: var(--color-status, var(--color-btn)); - color: #fff; -} - -.coltitle { - font-size: 14px; - margin: 8px; -} - -.colsubtitle { - padding: 0 8px 8px 8px; - margin: 0; -} - -.junit li > button { - width: 100%; - padding: 4px 8px; - text-align: left; - background-color: transparent; - overflow: hidden; - text-overflow: ellipsis; -} -.junit li button.active { - text-decoration: underline; - color: var(--color-a-hov); -} - -#junit-cases.filtered li > button > span { - display: none; -} - -.badges > *:not(:last-child):after { - content: "โ€ข"; - margin: 0 0.4em; -} - -.pvcontent { - display: none; -} - -.junit li[data-status="success"] { - background-color: #00800035; -} -.junit li[data-status="failure"] { - background-color: #a6000035; -} -.junit li[data-status="error"] { - background-color: #67000035; -} -.junit li[data-status="skipped"] { - background-color: #33333335; -} -.junit li[data-status="flaky"] { - background-color: #d3641a35; -} - -[data-status="success"] { - --color-status: #008000; -} -[data-status="failure"] { - --color-status: #a60000; -} -[data-status="error"] { - --color-status: #670000; -} -[data-status="flaky"] { - --color-status: #d3641a; -} -[data-status="skipped"] { - --color-status: #333; -} - -/* Icons from https://css.gg */ -.gg-check-o { - box-sizing: border-box; - position: relative; - display: inline-block; - transform: scale(var(--ggs, 1)); - width: 22px; - height: 22px; - border: 2px solid; - border-radius: 100px; -} -.gg-check-o::after { - content: ""; - display: block; - box-sizing: border-box; - position: absolute; - left: 3px; - top: -1px; - width: 6px; - height: 10px; - border-color: currentColor; - border-width: 0 2px 2px 0; - border-style: solid; - transform-origin: bottom left; - transform: rotate(45deg); -} - -.gg-close-o { - box-sizing: border-box; - position: relative; - display: inline-block; - transform: scale(var(--ggs, 1)); - width: 22px; - height: 22px; - border: 2px solid; - border-radius: 40px; -} - -.gg-close-o::after, -.gg-close-o::before { - content: ""; - display: block; - box-sizing: border-box; - position: absolute; - width: 12px; - height: 2px; - background: currentColor; - transform: rotate(45deg); - border-radius: 5px; - top: 8px; - left: 3px; -} - -.gg-close-o::after { - transform: rotate(-45deg); -} - -.gg-block { - box-sizing: border-box; - position: relative; - display: inline-block; - transform: scale(var(--ggs, 1)); - width: 16px; - height: 16px; - border: 2px solid; - border-radius: 100%; -} - -.gg-block::before { - content: ""; - display: block; - box-sizing: border-box; - position: absolute; - width: 10px; - height: 2px; - background: currentColor; - border-radius: 5px; - transform: rotate(-45deg); - top: 5px; - left: 1px; -} - -.gg-danger { - box-sizing: border-box; - position: relative; - display: inline-block; - transform: scale(var(--ggs, 1)); - width: 20px; - height: 20px; - border: 2px solid; - border-radius: 40px; -} - -.gg-danger::after, -.gg-danger::before { - content: ""; - display: block; - box-sizing: border-box; - position: absolute; - border-radius: 3px; - width: 2px; - background: currentColor; - left: 7px; -} - -.gg-danger::after { - top: 2px; - height: 8px; -} - -.gg-danger::before { - height: 2px; - bottom: 2px; -} diff --git a/resources/content.css.gz b/resources/content.css.gz deleted file mode 100644 index 8bfaea9..0000000 Binary files a/resources/content.css.gz and /dev/null differ diff --git a/resources/style.css b/resources/style.css index 1516292..df823db 100644 --- a/resources/style.css +++ b/resources/style.css @@ -6,10 +6,6 @@ --color-text: #000; --color-text-light: #888; --color-border: #ccc; - --color-border2: #bbb; - --color-btn: #006ed3; - --color-a: #006ed3; - --color-a-hov: #319cff; } body { font-family: sans-serif; @@ -18,11 +14,11 @@ body { color: var(--color-text); } a { - color: var(--color-a); + color: #006ed3; text-decoration: none; } a:hover, a.selected { - color: var(--color-a-hov); + color: #319cff; } header, #summary, .content { padding: 0 20px; @@ -45,15 +41,16 @@ header h1 { } header h1 a { color: var(--color-text); -} -header h1 .sep { - margin: 0 0.2em; + margin: 0 4px; } footer a:hover, header h1 a:hover, a.selected { text-decoration: underline; } +header h1 a:first-child { + margin: 0; +} main { display: block; } @@ -78,7 +75,7 @@ main { border-collapse: collapse; } #list tr { - border-bottom: 1px dashed var(--color-border2); + border-bottom: 1px dashed #dadada; } #list tbody tr:hover { background-color: #ffffec; @@ -132,25 +129,19 @@ main { .query-input { color: inherit; font-size: 16px; + height: 32px; border: 1px solid var(--color-border); padding: 4px 8px; } button { + background-color: #006ed3; + color: #fff; + padding: 4px 8px; border: none; cursor: pointer; - font-size: 14px; - background-color: unset; - color: unset; -} -.btn { - background-color: var(--color-btn); - padding: 4px 8px; } button:hover { - filter: brightness(80%); -} -button:active { - filter: brightness(70%); + opacity: 0.7; } footer { padding: 40px 20px; @@ -188,6 +179,9 @@ p { th:nth-child(2) { display: none; } + h1 a { + margin: 0; + } #filter { max-width: 100px; } @@ -195,17 +189,11 @@ p { .expired { filter: grayscale(100%); } -.hidden { - display: none !important; -} @media (prefers-color-scheme: dark) { * { --color-secondary: #082437; - --color-text: #ddd; + --color-text: #dddddd; --color-border: #212121; - --color-border2: #333; - --color-a: #009dff; - --color-a-hov: #62b2fd; } body { background-color: #101010; @@ -219,6 +207,17 @@ p { #list tbody tr:hover { background-color: #252525; } + a { + color: #5796d1; + text-decoration: none; + } + a:hover, + h1 a:hover, a.selected { + color: #62b2fd; + } + #list tr { + border-bottom: 1px dashed rgba(255, 255, 255, 0.12); + } #filter { background-color: #151515; color: #ffffff; diff --git a/resources/style.css.gz b/resources/style.css.gz deleted file mode 100644 index 2efabdc..0000000 Binary files a/resources/style.css.gz and /dev/null differ diff --git a/src/app.rs b/src/app.rs index 0e84191..50036c2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -36,12 +36,11 @@ use crate::{ templates::{self, ArtifactItem, LinkItem}, util::{self, ErrorJson, ResponseBuilderExt}, viewer::Viewers, + App, }; -pub struct App; - #[derive(Clone)] -pub struct AppState { +struct AppState { i: Arc, } @@ -68,17 +67,12 @@ pub(crate) const VERSION: &str = env!("CARGO_PKG_VERSION"); // Stylesheets are saved with immutable cache header. If they are changed in the future, // the number in the path should be incremented -pub(crate) const STYLE_MAIN_PATH: &str = "/style2.css"; -pub(crate) const STYLE_CONTENT_PATH: &str = "/content2.css"; +pub(crate) const STYLE_MAIN_PATH: &str = "/style1.css"; +pub(crate) const STYLE_CONTENT_PATH: &str = "/content1.css"; -const FAVICON_BYTES: &[u8] = include_bytes!("../resources/favicon.ico"); -const STYLE_MAIN_BYTES: &[u8] = include_bytes!("../resources/style.css"); -const STYLE_CONTENT_BYTES: &[u8] = include_bytes!("../resources/content.css"); - -#[allow(unused_variables)] -const STYLE_MAIN_BYTES_GZ: &[u8] = include_bytes!("../resources/style.css.gz"); -#[allow(unused_variables)] -const STYLE_CONTENT_BYTES_GZ: &[u8] = include_bytes!("../resources/content.css.gz"); +const FAVICON_BYTES: &[u8; 268] = include_bytes!("../resources/favicon.ico"); +const STYLE_MAIN_BYTES: &[u8; 4057] = include_bytes!("../resources/style.css"); +const STYLE_CONTENT_BYTES: &[u8; 10079] = include_bytes!("../resources/content.css"); impl App { pub fn new() -> Self { @@ -100,26 +94,13 @@ impl App { .await?; tracing::info!("Listening on port {port}"); - let router = Self::router(state); - - axum::serve( - listener, - router.into_make_service_with_connect_info::(), - ) - .await?; - Ok(()) - } - - pub fn router(state: AppState) -> Router { let real_ip_header = state.i.cfg.load().real_ip_header.clone(); - Router::new() + let router = Router::new() // Prevent search indexing since artifactview serves temporary artifacts - .route("/robots.txt", get(|| async { - Response::builder() - .typed_header(headers::ContentType::text_utf8()) - .cache() - .body::("# PLEASE dont scrape this website.\n# All of the data here is fetched from the public GitHub/Gitea APIs, this app is open source and it is not running on some Fortune 500 company server. \n\nUser-agent: *\nDisallow: /\n".into()).unwrap() - })) + .route( + "/robots.txt", + get(|| async { "# PLEASE dont scrape this website.\n# All of the data here is fetched from the public GitHub/Gitea APIs, this app is open source and it is not running on some Fortune 500 company server. \n\nUser-agent: *\nDisallow: /\n" }), + ) // Put the API in the .well-known folder, since it is disabled for pages .route("/.well-known/api/artifacts", get(Self::get_artifacts)) .route("/.well-known/api/artifact", get(Self::get_artifact)) @@ -135,21 +116,18 @@ impl App { .layer( TraceLayer::new_for_http() .make_span_with(move |request: &Request| { - let ip = util::get_ip_address(request, real_ip_header.as_deref()) - .map(|ip| ip.to_string()) - .unwrap_or_default(); - tracing::error_span!( - "request", - url = util::full_url_from_request(request), - ip - ) + let ip = util::get_ip_address(request, real_ip_header.as_deref()).map(|ip| ip.to_string()).unwrap_or_default(); + tracing::error_span!("request", url = util::full_url_from_request(request), ip) }) .on_response(DefaultOnResponse::new().level(tracing::Level::INFO)), ) - .layer(SetResponseHeaderLayer::appending( - http::header::X_CONTENT_TYPE_OPTIONS, - http::HeaderValue::from_static("nosniff"), - )) + .layer(SetResponseHeaderLayer::appending(http::header::X_CONTENT_TYPE_OPTIONS, http::HeaderValue::from_static("nosniff"))); + axum::serve( + listener, + router.into_make_service_with_connect_info::(), + ) + .await?; + Ok(()) } async fn get_page( @@ -161,7 +139,7 @@ impl App { let subdomain = util::get_subdomain(&host, &state.i.cfg.load().root_domain)?; if subdomain.is_empty() { - Self::get_homepage(state, uri, request.headers()).await + Self::get_homepage(state, uri).await } else { let query = ArtifactQuery::from_subdomain(subdomain, &state.i.cfg.load().site_aliases)?; state.i.cfg.check_filterlist(&query)?; @@ -251,19 +229,15 @@ impl App { } } - async fn get_homepage( - state: AppState, - uri: Uri, - hdrs: &HeaderMap, - ) -> Result, Error> { + async fn get_homepage(state: AppState, uri: Uri) -> Result, Error> { if uri.path() == FAVICON_PATH { return Self::favicon(); } if uri.path() == STYLE_MAIN_PATH { - return Self::stylesheet(hdrs, STYLE_MAIN_BYTES, STYLE_MAIN_BYTES_GZ); + return Self::stylesheet(STYLE_MAIN_BYTES.as_slice()); } if uri.path() == STYLE_CONTENT_PATH { - return Self::stylesheet(hdrs, STYLE_CONTENT_BYTES, STYLE_CONTENT_BYTES_GZ); + return Self::stylesheet(STYLE_CONTENT_BYTES.as_slice()); } if uri.path() != "/" { return Err(Error::NotFound("path".into())); @@ -380,7 +354,6 @@ impl App { Ok(Response::builder() .typed_header(ContentType::html()) .typed_header(headers::LastModified::from(entry.last_modified)) - .cache() .body(tmpl.to_string().into())?) } @@ -554,51 +527,34 @@ impl App { Ok(Response::builder() .typed_header(headers::ContentType::from_str("image/x-icon").unwrap()) .cache_immutable() - .body(FAVICON_BYTES.into())?) + .body(FAVICON_BYTES.as_slice().into())?) } - #[allow(unused_variables)] - fn stylesheet( - hdrs: &HeaderMap, - content: &'static [u8], - content_gz: &'static [u8], - ) -> Result, Error> { - let resp = Response::builder() + fn stylesheet(content: &'static [u8]) -> Result, Error> { + Ok(Response::builder() .typed_header(headers::ContentType::from(mime::TEXT_CSS)) - .cache_immutable(); - - // Dont serve compressed stylesheets in debug mode to allow live changes - #[cfg(not(debug_assertions))] - if util::accepts_gzip(hdrs) { - return Ok(resp - .typed_header(headers::ContentEncoding::gzip()) - .body(content_gz.into())?); - } - Ok(resp.body(content.into())?) + .cache_immutable() + .body(content.into())?) } } impl AppState { pub fn new() -> Result { let cfg = Config::new()?; - Ok(Self::from_cfg(cfg)) - } - - pub fn from_cfg(cfg: Config) -> Self { let cache = Cache::new(cfg.clone()); let api = ArtifactApi::new(cfg.clone()); - Self { + Ok(Self { i: Arc::new(AppInner { cfg, cache, api, viewers: Viewers::new(), }), - } + }) } /// Run garbage collection in the background if necessary - fn garbage_collect(&self) { + pub fn garbage_collect(&self) { let state = self.clone(); tokio::spawn(async move { if let Err(e) = state.i.cache.garbage_collect().await { diff --git a/src/artifact_api.rs b/src/artifact_api.rs index f57d9ab..7a48b76 100644 --- a/src/artifact_api.rs +++ b/src/artifact_api.rs @@ -288,7 +288,6 @@ mod tests { use super::ArtifactApi; #[tokio::test] - #[ignore] async fn fetch_forgejo() { let query = ArtifactQuery::from_subdomain( "code-thetadev-de--hsa--visitenbuch--32-1", @@ -303,7 +302,6 @@ mod tests { } #[tokio::test] - #[ignore] async fn fetch_github() { let query = ArtifactQuery::from_subdomain( "github-com--actions--upload-artifact--8805345396-1440556464", diff --git a/src/cache.rs b/src/cache.rs index a2cb665..513361c 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -16,7 +16,7 @@ use mime::Mime; use path_macro::path; use quick_cache::sync::Cache as QuickCache; use serde::Serialize; -use serde_hex::{SerHex, SerHexOpt}; +use serde_hex::{SerHex, Strict}; use crate::{ artifact_api::ArtifactApi, @@ -54,11 +54,8 @@ pub struct FileEntry { } pub struct GetEntryResult { - /// Cached zip file metadata pub entry: Arc, - /// Path to the cached zip file pub zip_path: PathBuf, - /// True if the entry was just downloaded pub downloaded: bool, } @@ -78,11 +75,10 @@ pub struct GetFileResultFile { pub struct IndexEntry { pub name: String, pub size: u32, - #[serde(with = "SerHex::")] + #[serde(with = "SerHex::")] pub crc32: u32, } -#[derive(Serialize)] pub struct Listing { pub entries: Vec, pub n_files: usize, @@ -90,21 +86,16 @@ pub struct Listing { pub has_parent: bool, } -#[derive(Serialize)] pub struct ListingEntry { pub name: String, pub url: String, pub size: Size, - pub crc32: Crc32, + pub crc32: String, pub is_dir: bool, } -#[derive(Serialize)] pub struct Size(pub u32); -#[derive(Serialize)] -pub struct Crc32(#[serde(with = "SerHexOpt::")] pub Option); - impl Cache { pub fn new(cfg: Config) -> Self { Self { @@ -393,7 +384,7 @@ impl CacheEntry { name: n.to_owned(), url: format!("{n}{path}"), size: 0.into(), - crc32: Crc32(None), + crc32: "-".to_string(), is_dir: true, }); } else { @@ -401,7 +392,7 @@ impl CacheEntry { name: n.to_owned(), url: format!("{n}{path}"), size: entry.uncompressed_size.into(), - crc32: Crc32(Some(entry.crc32)), + crc32: hex::encode(entry.crc32.to_le_bytes()), is_dir: false, }); } @@ -434,199 +425,3 @@ impl From for Size { Self(value) } } - -#[cfg(test)] -mod tests { - use std::{net::Ipv4Addr, str::FromStr}; - - use rstest::{fixture, rstest}; - use temp_testdir::TempDir; - - use super::*; - - struct TdCache { - cache: Cache, - api: ArtifactApi, - td: TempDir, - } - - impl TdCache { - async fn get_entry(&self, subdomain: &str) -> Result { - self.cache - .get_entry( - &self.api, - &ArtifactQuery::from_subdomain(subdomain, &HashMap::new()).unwrap(), - &IpAddr::V4(Ipv4Addr::LOCALHOST), - ) - .await - } - } - - #[fixture] - fn cache() -> TdCache { - let td = TempDir::default(); - util::tests::setup_cache_dir(&td); - let cfg = Config::from_data(crate::ConfigData { - cache_dir: td.to_path_buf(), - ..Default::default() - }) - .unwrap(); - let cache = Cache::new(cfg.clone()); - let api = ArtifactApi::new(cfg); - TdCache { cache, api, td } - } - - const S1: &str = "codeberg-org--thetadev--artifactview-test--1-1"; - const Z1: &str = "codeberg-org--thetadev--artifactview-test--1-1.zip"; - const S2: &str = "codeberg-org--thetadev--artifactview-test--1-2"; - const Z2: &str = "codeberg-org--thetadev--artifactview-test--1-2.zip"; - const S3: &str = "codeberg-org--thetadev--artifactview-test--1-3"; - const Z3: &str = "codeberg-org--thetadev--artifactview-test--1-3.zip"; - - #[rstest] - #[tokio::test] - async fn get_entry(cache: TdCache) { - let entry = cache.get_entry(S1).await.unwrap(); - - assert_eq!(entry.entry.name, "view"); - assert_eq!(entry.zip_path, path!(cache.td / Z1)); - assert!(!entry.downloaded); - - let files = entry.entry.get_files(); - let mut filenames = files.iter().map(|f| f.name.as_str()).collect::>(); - filenames.sort(); - assert_eq!( - filenames, - [ - ".well-known/test.txt", - "README.md", - "example.rs", - "junit/hello.junit.xml", - "junit/retry.junit.xml", - "junit/simple.junit.xml", - "robots.txt", - "sites/index.html", - "sites/style.css" - ] - ); - } - - #[rstest] - #[tokio::test] - async fn garbage_collect(cache: TdCache) { - let ago = SystemTime::now() - Duration::from_secs(13 * 3600); - let file = std::fs::File::open(path!(cache.td / Z1)).unwrap(); - file.set_times(FileTimes::new().set_accessed(ago)).unwrap(); - let file = std::fs::File::open(path!(cache.td / Z2)).unwrap(); - file.set_times(FileTimes::new().set_accessed(ago)).unwrap(); - - // Access artifact 1, artifact 2 should be deleted - cache.get_entry(S1).await.unwrap(); - - cache.cache.garbage_collect().await.unwrap(); - - assert!(path!(cache.td / Z1).is_file()); - assert!(path!(cache.td / format!("{S1}.name")).is_file()); - assert!(path!(cache.td / Z3).is_file()); - assert!(path!(cache.td / format!("{S3}.name")).is_file()); - assert!(!path!(cache.td / Z2).is_file()); - assert!(!path!(cache.td / format!("{S2}.name")).is_file()); - } - - #[rstest] - #[tokio::test] - async fn get_file(cache: TdCache) { - let entry = cache.get_entry(S1).await.unwrap(); - let res = entry.entry.get_file("example.rs", "").unwrap(); - if let GetFileResult::File(file) = res { - assert_eq!(file.filename, Some("example.rs".to_string())); - assert_eq!(file.file.crc32, 0x2013120c); - assert_eq!(file.status, StatusCode::OK); - assert_eq!(file.mime, Some(Mime::from_str("text/x-rust").unwrap())); - } else { - panic!("no file") - } - } - - #[rstest] - #[tokio::test] - async fn get_file_spa(cache: TdCache) { - let entry = cache.get_entry(S3).await.unwrap(); - let res = entry.entry.get_file("foo/bar", "").unwrap(); - if let GetFileResult::File(file) = res { - assert_eq!(file.filename, None); - assert_eq!(file.file.crc32, 0xBE336584); - assert_eq!(file.status, StatusCode::OK); - assert_eq!(file.mime, Some(Mime::from_str("text/html").unwrap())); - } else { - panic!("no file") - } - } - - #[rstest] - #[tokio::test] - async fn get_file_404(cache: TdCache) { - let entry = cache.get_entry(S2).await.unwrap(); - let res = entry.entry.get_file("foo/bar", "").unwrap(); - if let GetFileResult::File(file) = res { - assert_eq!(file.filename, None); - assert_eq!(file.file.crc32, 0x69F73F18); - assert_eq!(file.status, StatusCode::NOT_FOUND); - assert_eq!(file.mime, Some(Mime::from_str("text/html").unwrap())); - } else { - panic!("no file") - } - } - - #[rstest] - #[case("", &[ - ".well-known/", - "junit/", - "sites/", - "README.md", - "example.rs", - "robots.txt" - ])] - #[case("C=N&O=D", &[ - "sites/", - "junit/", - ".well-known/", - "robots.txt", - "example.rs", - "README.md", - ])] - #[case("C=S&O=A", &[ - ".well-known/", - "junit/", - "sites/", - "robots.txt", - "example.rs", - "README.md", - ])] - #[case("C=S&O=D", &[ - ".well-known/", - "junit/", - "sites/", - "README.md", - "example.rs", - "robots.txt" - ])] - #[tokio::test] - async fn get_file_listing(cache: TdCache, #[case] query: &str, #[case] expect: &[&str]) { - let entry = cache.get_entry(S1).await.unwrap(); - let res = entry.entry.get_file("", query).unwrap(); - if let GetFileResult::Listing(listing) = res { - let filenames = listing - .entries - .iter() - .map(|e| e.name.as_str()) - .collect::>(); - assert_eq!(filenames, expect); - assert_eq!(listing.n_dirs, 3); - assert_eq!(listing.n_files, 3); - assert!(!listing.has_parent); - } else { - panic!("no listing") - } - } -} diff --git a/src/error.rs b/src/error.rs index 8d3097f..357084c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -35,7 +35,7 @@ pub enum Error { Inaccessible, #[error("This artifact has already expired")] Expired, - #[error("This action took too long")] + #[error("timeout")] Timeout(#[from] tokio::time::error::Elapsed), #[error("Method not allowed")] MethodNotAllowed, diff --git a/src/lib.rs b/src/lib.rs index 78fd696..f8d8a9a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,4 @@ mod templates; mod util; mod viewer; -pub use app::{App, AppState}; -pub use config::{Config, ConfigData}; -pub use error::Error; +pub struct App; diff --git a/src/query.rs b/src/query.rs index d810b1c..f3993e7 100644 --- a/src/query.rs +++ b/src/query.rs @@ -10,7 +10,6 @@ use crate::{ util, }; -/// Query to select an artifact #[derive(Debug, PartialEq, Eq)] pub struct ArtifactQuery { /// Forge host @@ -27,7 +26,6 @@ pub struct ArtifactQuery { pub artifact: u64, } -/// Query to select a CI run (set of artifacts) #[derive(Debug, PartialEq, Eq)] pub struct RunQuery { /// Forge host diff --git a/src/templates.rs b/src/templates.rs index ec90fe3..56e6618 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -1,10 +1,9 @@ use crate::{ artifact_api::Artifact, - cache::{Crc32, ListingEntry, Size}, + cache::{ListingEntry, Size}, config::Config, query::{Query, QueryRef}, }; -use junit_parser::TestSuites; use yarte::{Render, Template}; #[derive(Template)] @@ -59,12 +58,6 @@ pub struct Preview<'a> { pub body: &'a str, } -#[derive(Template)] -#[template(path = "junit")] -pub struct Junit { - pub suites: TestSuites, -} - pub struct ViewerLink { pub id: &'static str, pub name: &'static str, @@ -105,44 +98,3 @@ impl Render for Size { ) } } - -impl Render for Crc32 { - fn render(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self.0 { - Some(crc) => write!(f, "{crc:08x}"), - None => f.write_str("—"), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[derive(Template)] - #[template(src = "{{ rendered }}")] - struct RenderTemplate { - rendered: T, - } - - #[test] - fn crc32() { - let tmpl = RenderTemplate { - rendered: Crc32(Some(0xc538cf99)), - }; - assert_eq!(tmpl.to_string(), "c538cf99"); - - let tmpl = RenderTemplate { - rendered: Crc32(None), - }; - assert_eq!(tmpl.to_string(), "—"); - } - - #[test] - fn size() { - let tmpl = RenderTemplate { - rendered: Size(1000), - }; - assert_eq!(tmpl.to_string(), "1 kB"); - } -} diff --git a/src/util.rs b/src/util.rs index 5ce1238..bc74080 100644 --- a/src/util.rs +++ b/src/util.rs @@ -241,22 +241,6 @@ pub fn parse_url(input: &str) -> Result<(&str, std::str::Split)> { Ok((host, parts)) } -pub fn time_to_ms(time: f64) -> u64 { - (time * 1000.0) as u64 -} - -/// Get the extension from a filename for selecting a viewer -pub fn filename_ext(filename: &str) -> &str { - let mut rsplit = filename.rsplit('.'); - let ext = rsplit.next().unwrap(); - if filename.starts_with('.') && rsplit.next().map(str::is_empty).unwrap_or(true) { - // Dotfile without extension (e.g. .bashrc) - filename - } else { - ext - } -} - #[derive(Serialize)] pub struct ErrorJson { status: u16, @@ -286,7 +270,7 @@ impl IntoResponse for ErrorJson { #[cfg(test)] pub(crate) mod tests { - use std::path::{Path, PathBuf}; + use std::path::PathBuf; use http::{header, HeaderMap}; use once_cell::sync::Lazy; @@ -296,25 +280,6 @@ pub(crate) mod tests { pub static TESTFILES: Lazy = Lazy::new(|| path!(env!("CARGO_MANIFEST_DIR") / "tests" / "testfiles")); - static SITEDIR: Lazy = Lazy::new(|| { - let sitedir = path!(*TESTFILES / "sites_data"); - if !sitedir.is_dir() { - std::process::Command::new(path!(*TESTFILES / "sites" / "make_zip.sh")) - .output() - .unwrap(); - } - sitedir - }); - - pub fn setup_cache_dir(dir: &Path) { - for entry in std::fs::read_dir(SITEDIR.as_path()).unwrap() { - let entry = entry.unwrap(); - if entry.file_type().unwrap().is_file() { - std::fs::copy(entry.path(), path!(dir / entry.file_name())).unwrap(); - } - } - } - #[rstest] #[case("", false)] #[case("br", false)] @@ -362,14 +327,4 @@ pub(crate) mod tests { } } } - - #[rstest] - #[case("hello.txt", "txt")] - #[case(".bashrc", ".bashrc")] - #[case("Makefile", "Makefile")] - #[case("", "")] - fn filename_ext(#[case] filename: &str, #[case] expect: &str) { - let res = super::filename_ext(filename); - assert_eq!(res, expect); - } } diff --git a/src/viewer/code.rs b/src/viewer/code.rs index e8bdf99..3a56c31 100644 --- a/src/viewer/code.rs +++ b/src/viewer/code.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, sync::Arc}; +use std::sync::Arc; use syntect::{ html::{ClassStyle, ClassedHTMLGenerator}, @@ -12,19 +12,11 @@ use super::Viewer; pub struct CodeViewer { ss: Arc, - smap: HashMap, } impl CodeViewer { pub fn new(ss: Arc) -> Self { - let smap = ss - .syntaxes() - .iter() - .enumerate() - .flat_map(|(i, s)| s.file_extensions.iter().map(move |ext| (ext.to_owned(), i))) - .collect::>(); - - Self { ss, smap } + Self { ss } } } @@ -37,13 +29,15 @@ impl Viewer for CodeViewer { "Code" } - fn is_applicable(&self, _filename: &str, ext: &str) -> bool { - self.smap.contains_key(ext) + fn is_applicable(&self, _filename: &str, _ext: &str) -> bool { + true } fn try_render(&self, _filename: &str, ext: &str, data: &str) -> Result { - let i = self.smap.get(ext).ok_or(Error::ViewerNotApplicable)?; - let syntax = &self.ss.syntaxes()[*i]; + let syntax = self + .ss + .find_syntax_by_extension(ext) + .ok_or(Error::ViewerNotApplicable)?; let mut html_generator = ClassedHTMLGenerator::new_with_class_style(syntax, &self.ss, ClassStyle::Spaced); @@ -60,32 +54,30 @@ impl Viewer for CodeViewer { #[cfg(test)] mod tests { + // use super::*; + + /* use super::*; + use std::{ + fs::File, + io::{BufReader, BufWriter, Write}, + }; + use syntect::{highlighting::ThemeSet, html::css_for_theme_with_class_style}; #[test] - fn is_applicable() { - let ss = Arc::new(SyntaxSet::load_defaults_newlines()); - let cv = CodeViewer::new(ss); - assert!(cv.is_applicable("hello.txt", "txt")); - assert!(cv.is_applicable(".bashrc", ".bashrc")); - assert!(!cv.is_applicable("image.jpg", "jpg")); - } + fn get_stylesheet() { + // let ts = ThemeSet::load_defaults(); - #[test] - fn render() { - let ss = Arc::new(SyntaxSet::load_defaults_newlines()); - let cv = CodeViewer::new(ss); - let res = cv - .try_render( - "hello.rs", - "rs", - r#"fn test() { - let x = "World"; - println!("Hello {x}"); -} -"#, - ) - .unwrap(); - insta::assert_snapshot!(res); + let mut f = BufReader::new(File::open("Monokai.tmTheme").unwrap()); + let dark_theme = ThemeSet::load_from_reader(&mut f).unwrap(); + + // create dark color scheme css + // let dark_theme = &ts.themes["Solarized (dark)"]; + let css_dark_file = File::create("theme-dark.css").unwrap(); + let mut css_dark_writer = BufWriter::new(&css_dark_file); + + let css_dark = css_for_theme_with_class_style(&dark_theme, ClassStyle::Spaced).unwrap(); + writeln!(css_dark_writer, "{}", css_dark).unwrap(); } + */ } diff --git a/src/viewer/junit.rs b/src/viewer/junit.rs index a7f4834..bc2c7de 100644 --- a/src/viewer/junit.rs +++ b/src/viewer/junit.rs @@ -1,4 +1,4 @@ -use crate::{error::Error, templates}; +use crate::error::Error; use super::Viewer; @@ -23,9 +23,8 @@ impl Viewer for JunitViewer { tracing::error!("could not parse junit report {filename}: {e}"); Error::ViewerNotApplicable })?; - - let tmpl = templates::Junit { suites }; - Ok(tmpl.to_string()) + dbg!(&suites); + Ok(String::new()) } } @@ -33,24 +32,15 @@ impl Viewer for JunitViewer { mod tests { use path_macro::path; - use crate::util::tests::TESTFILES; + use crate::{util::tests::TESTFILES, viewer::Viewer}; - use super::*; + use super::JunitViewer; #[test] - fn is_applicable() { - let ju = JunitViewer; - assert!(ju.is_applicable("junit.xml", "xml")); - assert!(ju.is_applicable("hello.junit.xml", "xml")); - assert!(!ju.is_applicable("hello.xml", "xml")); - } - - #[test] - fn render() { - let ju = JunitViewer; + fn t1() { let data = - std::fs::read_to_string(path!(*TESTFILES / "junit" / "hello.junit.xml")).unwrap(); - let res = ju.try_render("hello.junit.xml", "xml", &data).unwrap(); - insta::assert_snapshot!(res); + std::fs::read_to_string(path!(*TESTFILES / "junit" / "simple.junit.xml")).unwrap(); + let html = JunitViewer.try_render("", "", &data).unwrap(); + println!("{html}"); } } diff --git a/src/viewer/markdown.rs b/src/viewer/markdown.rs index c0ed600..f1d158d 100644 --- a/src/viewer/markdown.rs +++ b/src/viewer/markdown.rs @@ -108,33 +108,3 @@ impl SyntaxHighlighterAdapter for SyntectAdapter { output.write_all(b"") } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn is_applicable() { - let ss = Arc::new(SyntaxSet::load_defaults_newlines()); - let mv = MarkdownViewer::new(ss); - assert!(mv.is_applicable("hello.md", "md")); - assert!(!mv.is_applicable("hello.txt", "txt")); - } - - #[test] - fn render() { - let ss = Arc::new(SyntaxSet::load_defaults_newlines()); - let mv = MarkdownViewer::new(ss); - let res = mv - .try_render( - "hello.md", - "md", - r#"# Hello World - -this is a small paragraph for *testing*. -"#, - ) - .unwrap(); - insta::assert_snapshot!(res); - } -} diff --git a/src/viewer/mod.rs b/src/viewer/mod.rs index 632b99c..f8d10f6 100644 --- a/src/viewer/mod.rs +++ b/src/viewer/mod.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use syntect::parsing::SyntaxSet; -use crate::{error::Error, templates::ViewerLink, util}; +use crate::{error::Error, templates::ViewerLink}; mod code; mod junit; @@ -21,9 +21,7 @@ pub struct Viewers { } pub struct RenderRes { - /// Body html pub html: String, - /// List of applicable viewers to be inserted into the top bar pub tmpl_viewers: Vec, } @@ -40,7 +38,7 @@ impl Viewers { } pub fn try_render(&self, filename: &str, viewer: &str, data: &str) -> Result { - let ext = util::filename_ext(filename); + let ext = filename.rsplit('.').next().unwrap(); if !viewer.is_empty() && viewer != "1" { if let Some(viewer) = self.viewers.iter().find(|v| v.id() == viewer) { @@ -90,29 +88,3 @@ impl Viewers { .collect() } } - -#[cfg(test)] -mod tests { - use super::*; - - use rstest::rstest; - - #[rstest] - #[case("test.txt", "", &["code"])] - #[case("hello.md", "", &["md", "code"])] - #[case("junit.xml", r#" "#, &["junit", "code"])] - #[case("img.png", "", &[])] - fn render(#[case] filename: &str, #[case] data: &str, #[case] applicable: &[&str]) { - let viewers = Viewers::new(); - let res = viewers.try_render(filename, "1", data); - - if applicable.is_empty() { - assert!(matches!(res, Err(Error::ViewerNotApplicable))); - } else { - let res = res.unwrap(); - assert!(res.tmpl_viewers[0].selected); - let renderers = res.tmpl_viewers.iter().map(|v| v.id).collect::>(); - assert_eq!(renderers, applicable) - } - } -} diff --git a/src/viewer/snapshots/artifactview__viewer__code__tests__render.snap b/src/viewer/snapshots/artifactview__viewer__code__tests__render.snap deleted file mode 100644 index 3edec2c..0000000 --- a/src/viewer/snapshots/artifactview__viewer__code__tests__render.snap +++ /dev/null @@ -1,10 +0,0 @@ ---- -source: src/viewer/code.rs -assertion_line: 89 -expression: res ---- -
fn test() {
-    let x = "World";
-    println!("Hello {x}");
-}
-
diff --git a/src/viewer/snapshots/artifactview__viewer__junit__tests__render.snap b/src/viewer/snapshots/artifactview__viewer__junit__tests__render.snap deleted file mode 100644 index 1daafd5..0000000 --- a/src/viewer/snapshots/artifactview__viewer__junit__tests__render.snap +++ /dev/null @@ -1,197 +0,0 @@ ---- -source: src/viewer/junit.rs -assertion_line: 54 -expression: res ---- -
-
-

Test suites:

-
    -
  • - -
  • - -
-
-
-

Test cases:

-

- - - - - - -

-
    -
  • - -
    -

    lib1::tests::it_works

    -

    Success3ms

    
    -running 1 test
    -test tests::it_works ... ok
    -
    -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s
    -
    -
    -
    -
  • - -
  • - -
    -

    lib1::tests::pippi_langstrumpf

    -

    Failure3ms

    thread 'tests::pippi_langstrumpf' panicked at src/lib.rs:18:9:
    -assertion `left == right` failed
    -  left: 7
    - right: 9
    -note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
    
    -running 1 test
    -test tests::pippi_langstrumpf ... FAILED
    -
    -failures:
    -
    -failures:
    -    tests::pippi_langstrumpf
    -
    -test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s
    -
    -
    thread 'tests::pippi_langstrumpf' panicked at src/lib.rs:18:9:
    -assertion `left == right` failed
    -  left: 7
    - right: 9
    -note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
    -
    -
    -
  • - -
-
-
-
-
-

Select a test case to show details

-
-
-
- - diff --git a/src/viewer/snapshots/artifactview__viewer__markdown__tests__render.snap b/src/viewer/snapshots/artifactview__viewer__markdown__tests__render.snap deleted file mode 100644 index db772bd..0000000 --- a/src/viewer/snapshots/artifactview__viewer__markdown__tests__render.snap +++ /dev/null @@ -1,8 +0,0 @@ ---- -source: src/viewer/markdown.rs -assertion_line: 138 -expression: res ---- -

Hello World

-

this is a small paragraph for testing.

-
diff --git a/templates/index.hbs b/templates/index.hbs index fcbe54e..baee8b0 100644 --- a/templates/index.hbs +++ b/templates/index.hbs @@ -27,7 +27,7 @@ placeholder="codeberg.org/username/repo/actions/runs/42" style="flex-grow: 1" /> - + diff --git a/templates/junit.hbs b/templates/junit.hbs deleted file mode 100644 index 0689aca..0000000 --- a/templates/junit.hbs +++ /dev/null @@ -1,183 +0,0 @@ -
-
-

Test suites:

-
    -
  • - {{#each suites.suites}} -
  • - {{/each}} -
-
-
-

Test cases:

-

- - - - - - -

-
    - {{~#each suites.suites ~}}{{ let suite_name = &name }} - {{#each cases}} -
  • - -
    -

    {{name}}

    -

    {{ this.status_txt() }}{{ crate::util::time_to_ms(time) }}ms

    - {{~#if let Some(msg) = status.message() ~}} -
    {{msg.message}}{{msg.text}}
    - {{~/if}} - {{~#if let Some(ref stdout) = system_out ~}} -
    {{stdout}}
    - {{~/if}} - {{~#if let Some(ref stderr) = system_err ~}} -
    {{stderr}}
    - {{~/if}} - {{~#each retries ~}} -

    Failed attempt #{{index}}

    -

    {{ crate::util::time_to_ms(time) }}ms

    - {{~#if let Some(msg) = status.message() ~}} -
    {{msg.message}}{{msg.text}}
    - {{~/if}} - {{~#if let Some(ref stdout) = system_out ~}} -
    {{stdout}}
    - {{~/if}} - {{~#if let Some(ref stderr) = system_err ~}} -
    {{stderr}}
    - {{~/if}} - {{~/each}} -
    -
  • - {{/each}} - {{~/each}} -
-
-
-
-
-

Select a test case to show details

-
-
-
- - diff --git a/templates/listing.hbs b/templates/listing.hbs index 94874e5..e97c97d 100644 --- a/templates/listing.hbs +++ b/templates/listing.hbs @@ -4,7 +4,9 @@ {{> partial/fileIcons }}
{{> partial/logoLink }} -

{{#each path_components}}{{this.name}}/{{/each}}

+

+ {{#each path_components}}{{name}}{{/each}} +

-
+
{{#each viewers}}{{name}}{{/each}} Raw
diff --git a/templates/selection.hbs b/templates/selection.hbs index 5ad911e..f283c82 100644 --- a/templates/selection.hbs +++ b/templates/selection.hbs @@ -4,7 +4,10 @@ {{> partial/fileIcons }}
{{> partial/logoLink }} -

{{run_name}}/

+

+ {{run_name}} + / +