From 4deabfa8b275646232173309d3c50cd78d6b3e55 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 6 Sep 2024 19:45:36 -0600 Subject: [PATCH] =?UTF-8?q?Rewrite=20it=20in=20Rust=20=F0=9F=A6=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 6 +- ...319da09b55b6667b0d12efa7422babd5ec584.json | 20 + ...1b0ceaeaa4fb6fd200dacf15030063ec7909d.json | 32 + ...8ab0cc6b5dcc96d1a62db6d763117a3cc5f5e.json | 12 + ...9c6984b1d02dc7b9515584be46e79b3793b1b.json | 26 + ...1293425d9701e3583fb1ce5851092dae89f10.json | 32 + ...42a2694c3a1dbf469c88dfb8df8f283e18a3d.json | 20 + ...52297907c2c3de6ea88951495175e81094343.json | 20 + ...14643065ae66bc008a649cf6669988c738f04.json | 20 + ...c5cc533b79d79f78389b4642c03837b264733.json | 68 + ...b9f6be24b0e71c35ae1798d30c852ca160f32.json | 12 + ...56a67ddcedae6be782be5a7357dbd1628b4e0.json | 12 + ...1d02add2ea3b8abae862a144c4607a62cc196.json | 12 + ...55256cbeb4d2c46e38521fe7c553fc02c861f.json | 32 + ...8ef596317748a42165c899b81ab63b84ff819.json | 80 + ...262c0086a2348ff8726e522aaee6999e61cad.json | 50 + Cargo.lock | 5524 +++++++++++++++++ Cargo.toml | 41 + build.rs | 3 + cmd/decompal/main.go | 56 - config.example.yml | 25 +- config/config.go | 36 - css/main.scss | 261 + genproto.sh | 6 - go.mod | 34 - go.sum | 75 - handlers/pull_request.go | 123 - handlers/shared.go | 387 -- handlers/task.go | 22 - handlers/workflow_run.go | 109 - js/graph.ts | 138 + migrations/1_init.sql | 41 + migrations/2_project_index.sql | 1 + migrations/3_nocase.sql | 7 + migrations/4_project_repo.sql | 1 + migrations/5_project_name.sql | 1 + migrations/6_project_short_name.sql | 1 + migrations/7_project_default_version.sql | 1 + migrations/8_report_units_name.sql | 2 + objdiff/changes.go | 65 - objdiff/legacy.go | 121 - objdiff/report.go | 168 - objdiff/report.pb.go | 1229 ---- package-lock.json | 21 + package.json | 5 + rustfmt.toml | 8 + src/config.rs | 18 + src/cron.rs | 58 + src/db/mod.rs | 487 ++ src/github/mod.rs | 300 + src/handlers/css.rs | 45 + src/handlers/graph.rs | 109 + src/handlers/js.rs | 123 + src/handlers/mod.rs | 78 + src/handlers/project.rs | 94 + src/handlers/report.rs | 361 ++ src/main.rs | 95 + src/models.rs | 81 + src/templates.rs | 42 + templates/oldgraphs.html | 55 + templates/projects.html | 75 + templates/report.html | 127 + templates/treemap.svg | 7 + zipstream/zipstream.go | 275 - 64 files changed, 8666 insertions(+), 2730 deletions(-) create mode 100644 .sqlx/query-21108206934d9a3b1e895e8413c319da09b55b6667b0d12efa7422babd5ec584.json create mode 100644 .sqlx/query-364ea7a0b23d5edfbf4810659d41b0ceaeaa4fb6fd200dacf15030063ec7909d.json create mode 100644 .sqlx/query-3a14bed95e5b640c97da2b6d3458ab0cc6b5dcc96d1a62db6d763117a3cc5f5e.json create mode 100644 .sqlx/query-3eb7ed522f01e36e0969b09ca8b9c6984b1d02dc7b9515584be46e79b3793b1b.json create mode 100644 .sqlx/query-4f3b7670e663abfba2a3c1b41f11293425d9701e3583fb1ce5851092dae89f10.json create mode 100644 .sqlx/query-56c994f1c82f796c9924b571d5442a2694c3a1dbf469c88dfb8df8f283e18a3d.json create mode 100644 .sqlx/query-6550bd96a82a16800fe801be26a52297907c2c3de6ea88951495175e81094343.json create mode 100644 .sqlx/query-7418a824fbb682e0044774c586a14643065ae66bc008a649cf6669988c738f04.json create mode 100644 .sqlx/query-76fc00ee61f9d0761931936dc4cc5cc533b79d79f78389b4642c03837b264733.json create mode 100644 .sqlx/query-79bbdf8acb97c4826c8ef1c0e51b9f6be24b0e71c35ae1798d30c852ca160f32.json create mode 100644 .sqlx/query-a4a5a6ff35d886c5f80a8973e2556a67ddcedae6be782be5a7357dbd1628b4e0.json create mode 100644 .sqlx/query-c0adc5fae9d742173927a8f48d01d02add2ea3b8abae862a144c4607a62cc196.json create mode 100644 .sqlx/query-d2268044470d7a0c539ccbae97e55256cbeb4d2c46e38521fe7c553fc02c861f.json create mode 100644 .sqlx/query-d38ee2d117fd8d6e0628c790c138ef596317748a42165c899b81ab63b84ff819.json create mode 100644 .sqlx/query-d4de02659c86068320c310dc8c6262c0086a2348ff8726e522aaee6999e61cad.json create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 build.rs delete mode 100644 cmd/decompal/main.go delete mode 100644 config/config.go create mode 100644 css/main.scss delete mode 100755 genproto.sh delete mode 100644 go.mod delete mode 100644 go.sum delete mode 100644 handlers/pull_request.go delete mode 100644 handlers/shared.go delete mode 100644 handlers/task.go delete mode 100644 handlers/workflow_run.go create mode 100644 js/graph.ts create mode 100644 migrations/1_init.sql create mode 100644 migrations/2_project_index.sql create mode 100644 migrations/3_nocase.sql create mode 100644 migrations/4_project_repo.sql create mode 100644 migrations/5_project_name.sql create mode 100644 migrations/6_project_short_name.sql create mode 100644 migrations/7_project_default_version.sql create mode 100644 migrations/8_report_units_name.sql delete mode 100644 objdiff/changes.go delete mode 100644 objdiff/legacy.go delete mode 100644 objdiff/report.go delete mode 100644 objdiff/report.pb.go create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 rustfmt.toml create mode 100644 src/config.rs create mode 100644 src/cron.rs create mode 100644 src/db/mod.rs create mode 100644 src/github/mod.rs create mode 100644 src/handlers/css.rs create mode 100644 src/handlers/graph.rs create mode 100644 src/handlers/js.rs create mode 100644 src/handlers/mod.rs create mode 100644 src/handlers/project.rs create mode 100644 src/handlers/report.rs create mode 100644 src/main.rs create mode 100644 src/models.rs create mode 100644 src/templates.rs create mode 100644 templates/oldgraphs.html create mode 100644 templates/projects.html create mode 100644 templates/report.html create mode 100644 templates/treemap.svg delete mode 100644 zipstream/zipstream.go diff --git a/.gitignore b/.gitignore index 2277617..96378df 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ -/tmp +/node_modules +/target /.idea -objdiff-* +db.sqlite* config.yml +.env diff --git a/.sqlx/query-21108206934d9a3b1e895e8413c319da09b55b6667b0d12efa7422babd5ec584.json b/.sqlx/query-21108206934d9a3b1e895e8413c319da09b55b6667b0d12efa7422babd5ec584.json new file mode 100644 index 0000000..0e2aae1 --- /dev/null +++ b/.sqlx/query-21108206934d9a3b1e895e8413c319da09b55b6667b0d12efa7422babd5ec584.json @@ -0,0 +1,20 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT EXISTS (\n SELECT 1\n FROM reports JOIN projects ON reports.project_id = projects.id\n WHERE projects.owner = ? COLLATE NOCASE AND projects.repo = ? COLLATE NOCASE\n AND git_commit = ? COLLATE NOCASE\n ) AS \"exists!\"\n ", + "describe": { + "columns": [ + { + "name": "exists!", + "ordinal": 0, + "type_info": "Integer" + } + ], + "parameters": { + "Right": 3 + }, + "nullable": [ + false + ] + }, + "hash": "21108206934d9a3b1e895e8413c319da09b55b6667b0d12efa7422babd5ec584" +} diff --git a/.sqlx/query-364ea7a0b23d5edfbf4810659d41b0ceaeaa4fb6fd200dacf15030063ec7909d.json b/.sqlx/query-364ea7a0b23d5edfbf4810659d41b0ceaeaa4fb6fd200dacf15030063ec7909d.json new file mode 100644 index 0000000..7483745 --- /dev/null +++ b/.sqlx/query-364ea7a0b23d5edfbf4810659d41b0ceaeaa4fb6fd200dacf15030063ec7909d.json @@ -0,0 +1,32 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT git_commit, timestamp, version\n FROM reports\n WHERE project_id = ? AND git_commit = ? COLLATE NOCASE\n ORDER BY version\n ", + "describe": { + "columns": [ + { + "name": "git_commit", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "timestamp", + "ordinal": 1, + "type_info": "Datetime" + }, + { + "name": "version", + "ordinal": 2, + "type_info": "Text" + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "364ea7a0b23d5edfbf4810659d41b0ceaeaa4fb6fd200dacf15030063ec7909d" +} diff --git a/.sqlx/query-3a14bed95e5b640c97da2b6d3458ab0cc6b5dcc96d1a62db6d763117a3cc5f5e.json b/.sqlx/query-3a14bed95e5b640c97da2b6d3458ab0cc6b5dcc96d1a62db6d763117a3cc5f5e.json new file mode 100644 index 0000000..9140316 --- /dev/null +++ b/.sqlx/query-3a14bed95e5b640c97da2b6d3458ab0cc6b5dcc96d1a62db6d763117a3cc5f5e.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO report_report_units (report_id, report_unit_id, unit_index)\n VALUES (?, ?, ?)\n ON CONFLICT DO NOTHING\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "3a14bed95e5b640c97da2b6d3458ab0cc6b5dcc96d1a62db6d763117a3cc5f5e" +} diff --git a/.sqlx/query-3eb7ed522f01e36e0969b09ca8b9c6984b1d02dc7b9515584be46e79b3793b1b.json b/.sqlx/query-3eb7ed522f01e36e0969b09ca8b9c6984b1d02dc7b9515584be46e79b3793b1b.json new file mode 100644 index 0000000..32f9a0f --- /dev/null +++ b/.sqlx/query-3eb7ed522f01e36e0969b09ca8b9c6984b1d02dc7b9515584be46e79b3793b1b.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT id, data\n FROM report_units\n WHERE name IS NULL\n ", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Blob" + }, + { + "name": "data", + "ordinal": 1, + "type_info": "Blob" + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + true, + false + ] + }, + "hash": "3eb7ed522f01e36e0969b09ca8b9c6984b1d02dc7b9515584be46e79b3793b1b" +} diff --git a/.sqlx/query-4f3b7670e663abfba2a3c1b41f11293425d9701e3583fb1ce5851092dae89f10.json b/.sqlx/query-4f3b7670e663abfba2a3c1b41f11293425d9701e3583fb1ce5851092dae89f10.json new file mode 100644 index 0000000..2c50761 --- /dev/null +++ b/.sqlx/query-4f3b7670e663abfba2a3c1b41f11293425d9701e3583fb1ce5851092dae89f10.json @@ -0,0 +1,32 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT git_commit, timestamp, version\n FROM reports\n WHERE project_id = ? AND timestamp = (\n SELECT MAX(timestamp)\n FROM reports\n WHERE project_id = ?\n )\n ORDER BY version\n ", + "describe": { + "columns": [ + { + "name": "git_commit", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "timestamp", + "ordinal": 1, + "type_info": "Datetime" + }, + { + "name": "version", + "ordinal": 2, + "type_info": "Text" + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "4f3b7670e663abfba2a3c1b41f11293425d9701e3583fb1ce5851092dae89f10" +} diff --git a/.sqlx/query-56c994f1c82f796c9924b571d5442a2694c3a1dbf469c88dfb8df8f283e18a3d.json b/.sqlx/query-56c994f1c82f796c9924b571d5442a2694c3a1dbf469c88dfb8df8f283e18a3d.json new file mode 100644 index 0000000..ef4fdf0 --- /dev/null +++ b/.sqlx/query-56c994f1c82f796c9924b571d5442a2694c3a1dbf469c88dfb8df8f283e18a3d.json @@ -0,0 +1,20 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO reports (project_id, version, git_commit, timestamp, data)\n VALUES (?, ?, ?, ?, ?)\n ON CONFLICT (project_id, version COLLATE NOCASE, git_commit COLLATE NOCASE) DO UPDATE\n SET timestamp = EXCLUDED.timestamp\n RETURNING id\n ", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Integer" + } + ], + "parameters": { + "Right": 5 + }, + "nullable": [ + false + ] + }, + "hash": "56c994f1c82f796c9924b571d5442a2694c3a1dbf469c88dfb8df8f283e18a3d" +} diff --git a/.sqlx/query-6550bd96a82a16800fe801be26a52297907c2c3de6ea88951495175e81094343.json b/.sqlx/query-6550bd96a82a16800fe801be26a52297907c2c3de6ea88951495175e81094343.json new file mode 100644 index 0000000..181def2 --- /dev/null +++ b/.sqlx/query-6550bd96a82a16800fe801be26a52297907c2c3de6ea88951495175e81094343.json @@ -0,0 +1,20 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT git_commit\n FROM reports\n WHERE project_id = ? AND timestamp > ?\n ORDER BY timestamp\n LIMIT 1\n ", + "describe": { + "columns": [ + { + "name": "git_commit", + "ordinal": 0, + "type_info": "Text" + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + false + ] + }, + "hash": "6550bd96a82a16800fe801be26a52297907c2c3de6ea88951495175e81094343" +} diff --git a/.sqlx/query-7418a824fbb682e0044774c586a14643065ae66bc008a649cf6669988c738f04.json b/.sqlx/query-7418a824fbb682e0044774c586a14643065ae66bc008a649cf6669988c738f04.json new file mode 100644 index 0000000..b482bc5 --- /dev/null +++ b/.sqlx/query-7418a824fbb682e0044774c586a14643065ae66bc008a649cf6669988c738f04.json @@ -0,0 +1,20 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT git_commit\n FROM reports\n WHERE project_id = ? AND timestamp < ?\n ORDER BY timestamp DESC\n LIMIT 1\n ", + "describe": { + "columns": [ + { + "name": "git_commit", + "ordinal": 0, + "type_info": "Text" + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + false + ] + }, + "hash": "7418a824fbb682e0044774c586a14643065ae66bc008a649cf6669988c738f04" +} diff --git a/.sqlx/query-76fc00ee61f9d0761931936dc4cc5cc533b79d79f78389b4642c03837b264733.json b/.sqlx/query-76fc00ee61f9d0761931936dc4cc5cc533b79d79f78389b4642c03837b264733.json new file mode 100644 index 0000000..79cfdb2 --- /dev/null +++ b/.sqlx/query-76fc00ee61f9d0761931936dc4cc5cc533b79d79f78389b4642c03837b264733.json @@ -0,0 +1,68 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT\n projects.id AS \"project_id!\",\n owner AS \"owner!\",\n repo AS \"repo!\",\n name,\n short_name,\n default_version,\n git_commit AS \"git_commit!\",\n MAX(timestamp) AS \"timestamp: chrono::NaiveDateTime\",\n JSON_GROUP_ARRAY(version ORDER BY version) AS versions\n FROM projects JOIN reports ON projects.id = reports.project_id\n WHERE timestamp = (\n SELECT MAX(timestamp)\n FROM reports\n WHERE project_id = projects.id\n )\n GROUP BY projects.id\n ORDER BY MAX(timestamp) DESC\n ", + "describe": { + "columns": [ + { + "name": "project_id!", + "ordinal": 0, + "type_info": "Integer" + }, + { + "name": "owner!", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "repo!", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "name", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "short_name", + "ordinal": 4, + "type_info": "Text" + }, + { + "name": "default_version", + "ordinal": 5, + "type_info": "Text" + }, + { + "name": "git_commit!", + "ordinal": 6, + "type_info": "Text" + }, + { + "name": "timestamp: chrono::NaiveDateTime", + "ordinal": 7, + "type_info": "Text" + }, + { + "name": "versions", + "ordinal": 8, + "type_info": "Text" + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + true, + true, + true, + true, + true, + true, + true, + false, + false + ] + }, + "hash": "76fc00ee61f9d0761931936dc4cc5cc533b79d79f78389b4642c03837b264733" +} diff --git a/.sqlx/query-79bbdf8acb97c4826c8ef1c0e51b9f6be24b0e71c35ae1798d30c852ca160f32.json b/.sqlx/query-79bbdf8acb97c4826c8ef1c0e51b9f6be24b0e71c35ae1798d30c852ca160f32.json new file mode 100644 index 0000000..d1bdcfb --- /dev/null +++ b/.sqlx/query-79bbdf8acb97c4826c8ef1c0e51b9f6be24b0e71c35ae1798d30c852ca160f32.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO report_units (id, data, name)\n VALUES (?, ?, ?)\n ON CONFLICT (id) DO NOTHING\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "79bbdf8acb97c4826c8ef1c0e51b9f6be24b0e71c35ae1798d30c852ca160f32" +} diff --git a/.sqlx/query-a4a5a6ff35d886c5f80a8973e2556a67ddcedae6be782be5a7357dbd1628b4e0.json b/.sqlx/query-a4a5a6ff35d886c5f80a8973e2556a67ddcedae6be782be5a7357dbd1628b4e0.json new file mode 100644 index 0000000..2426320 --- /dev/null +++ b/.sqlx/query-a4a5a6ff35d886c5f80a8973e2556a67ddcedae6be782be5a7357dbd1628b4e0.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE report_units\n SET name = ?\n WHERE id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "a4a5a6ff35d886c5f80a8973e2556a67ddcedae6be782be5a7357dbd1628b4e0" +} diff --git a/.sqlx/query-c0adc5fae9d742173927a8f48d01d02add2ea3b8abae862a144c4607a62cc196.json b/.sqlx/query-c0adc5fae9d742173927a8f48d01d02add2ea3b8abae862a144c4607a62cc196.json new file mode 100644 index 0000000..6327e1e --- /dev/null +++ b/.sqlx/query-c0adc5fae9d742173927a8f48d01d02add2ea3b8abae862a144c4607a62cc196.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO projects (id, owner, repo, name, short_name, default_version, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)\n ON CONFLICT (id) DO NOTHING\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 6 + }, + "nullable": [] + }, + "hash": "c0adc5fae9d742173927a8f48d01d02add2ea3b8abae862a144c4607a62cc196" +} diff --git a/.sqlx/query-d2268044470d7a0c539ccbae97e55256cbeb4d2c46e38521fe7c553fc02c861f.json b/.sqlx/query-d2268044470d7a0c539ccbae97e55256cbeb4d2c46e38521fe7c553fc02c861f.json new file mode 100644 index 0000000..f143b77 --- /dev/null +++ b/.sqlx/query-d2268044470d7a0c539ccbae97e55256cbeb4d2c46e38521fe7c553fc02c861f.json @@ -0,0 +1,32 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT ru.id AS \"id!\", ru.data, rru.unit_index\n FROM report_report_units rru JOIN report_units ru ON rru.report_unit_id = ru.id\n WHERE rru.report_id = ?\n ORDER BY rru.unit_index\n ", + "describe": { + "columns": [ + { + "name": "id!", + "ordinal": 0, + "type_info": "Blob" + }, + { + "name": "data", + "ordinal": 1, + "type_info": "Blob" + }, + { + "name": "unit_index", + "ordinal": 2, + "type_info": "Integer" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + true, + false, + false + ] + }, + "hash": "d2268044470d7a0c539ccbae97e55256cbeb4d2c46e38521fe7c553fc02c861f" +} diff --git a/.sqlx/query-d38ee2d117fd8d6e0628c790c138ef596317748a42165c899b81ab63b84ff819.json b/.sqlx/query-d38ee2d117fd8d6e0628c790c138ef596317748a42165c899b81ab63b84ff819.json new file mode 100644 index 0000000..1ecc1db --- /dev/null +++ b/.sqlx/query-d38ee2d117fd8d6e0628c790c138ef596317748a42165c899b81ab63b84ff819.json @@ -0,0 +1,80 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT\n reports.id as \"report_id!\",\n git_commit,\n timestamp,\n version,\n data,\n projects.id as \"project_id!\",\n owner,\n repo,\n name,\n short_name,\n default_version\n FROM reports JOIN projects ON reports.project_id = projects.id\n WHERE projects.owner = ? COLLATE NOCASE AND projects.repo = ? COLLATE NOCASE\n AND version = ? COLLATE NOCASE AND git_commit = ? COLLATE NOCASE\n ", + "describe": { + "columns": [ + { + "name": "report_id!", + "ordinal": 0, + "type_info": "Integer" + }, + { + "name": "git_commit", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "timestamp", + "ordinal": 2, + "type_info": "Datetime" + }, + { + "name": "version", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "data", + "ordinal": 4, + "type_info": "Blob" + }, + { + "name": "project_id!", + "ordinal": 5, + "type_info": "Integer" + }, + { + "name": "owner", + "ordinal": 6, + "type_info": "Text" + }, + { + "name": "repo", + "ordinal": 7, + "type_info": "Text" + }, + { + "name": "name", + "ordinal": 8, + "type_info": "Text" + }, + { + "name": "short_name", + "ordinal": 9, + "type_info": "Text" + }, + { + "name": "default_version", + "ordinal": 10, + "type_info": "Text" + } + ], + "parameters": { + "Right": 4 + }, + "nullable": [ + true, + false, + false, + false, + false, + true, + false, + false, + true, + true, + true + ] + }, + "hash": "d38ee2d117fd8d6e0628c790c138ef596317748a42165c899b81ab63b84ff819" +} diff --git a/.sqlx/query-d4de02659c86068320c310dc8c6262c0086a2348ff8726e522aaee6999e61cad.json b/.sqlx/query-d4de02659c86068320c310dc8c6262c0086a2348ff8726e522aaee6999e61cad.json new file mode 100644 index 0000000..577ffa6 --- /dev/null +++ b/.sqlx/query-d4de02659c86068320c310dc8c6262c0086a2348ff8726e522aaee6999e61cad.json @@ -0,0 +1,50 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT id AS \"id!\", owner, repo, name, short_name, default_version\n FROM projects\n WHERE owner = ? COLLATE NOCASE AND repo = ? COLLATE NOCASE\n ", + "describe": { + "columns": [ + { + "name": "id!", + "ordinal": 0, + "type_info": "Integer" + }, + { + "name": "owner", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "repo", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "name", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "short_name", + "ordinal": 4, + "type_info": "Text" + }, + { + "name": "default_version", + "ordinal": 5, + "type_info": "Text" + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + true, + false, + false, + true, + true, + true + ] + }, + "hash": "d4de02659c86068320c310dc8c6262c0086a2348ff8726e522aaee6999e61cad" +} diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..b6ac895 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,5524 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.76", +] + +[[package]] +name = "arrayref" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "assert-unchecked" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7330592adf847ee2e3513587b4db2db410a0d751378654e7e993d9adcbe5c795" + +[[package]] +name = "async-compression" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fec134f64e2bc57411226dfc4e52dec859ddfc7e711fc5e07b612584f000e4aa" +dependencies = [ + "brotli", + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "zstd", + "zstd-safe", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-trait" +version = "0.1.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.76", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "av1-grain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "axum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 0.1.2", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide 0.7.4", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64-simd" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "781dd20c3aff0bd194fe7d2a977dd92f21c173891f3a03b677359e5fa457e5d5" +dependencies = [ + "simd-abstraction", +] + +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref 0.5.1", + "vsimd", +] + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] + +[[package]] +name = "bitstream-io" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b81e1519b0d82120d2fd469d5bfb2919a9361c48b02d82d04befc1cdd2002452" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake3" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "built" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "236e6289eda5a812bc6b53c3b024039382a2895fbbeef2d748b2931546d392c4" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +dependencies = [ + "allocator-api2", +] + +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" + +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bytemuck" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" + +[[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.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets 0.52.6", +] + +[[package]] +name = "clap" +version = "4.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "codemap" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e769b5c8c8283982a987c6e948e540254f1058d5a74b8794914d4ef5fc2a24" + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + +[[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 = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-str" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21077772762a1002bb421c3af42ac1725fa56066bfc53d9a55bb79905df2aaf3" +dependencies = [ + "const-str-proc-macro", +] + +[[package]] +name = "const-str-proc-macro" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1e0fdd2e5d3041e530e1b21158aeeef8b5d0e306bc5c1e3d6cf0930d10e25a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core_maths" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b02505ccb8c50b0aa21ace0fc08c3e53adebd4e58caa18a36152803c7709a3" +dependencies = [ + "libm", +] + +[[package]] +name = "cpufeatures" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cron" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8c3e73077b4b4a6ab1ea5047c37c57aee77657bc8ecd6f29b0af082d0b0c07" +dependencies = [ + "chrono", + "nom", + "once_cell", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9be934d936a0fbed5bcdc01042b770de1398bf79d0e192f49fa7faea0e99281e" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf 0.11.2", + "smallvec", +] + +[[package]] +name = "cssparser-color" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556c099a61d85989d7af52b692e35a8d68a57e7df8c6d07563dc0778b3960c9f" +dependencies = [ + "cssparser", +] + +[[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.76", +] + +[[package]] +name = "daachorse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b7ef7a4be509357f4804d0a22e830daddb48f19fd604e4ad32ddce04a94c36" + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + +[[package]] +name = "data-url" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a30bfce702bcfa94e906ef82421f2c0e61c076ad76030c16ee5d2e9a32fe193" +dependencies = [ + "matches", +] + +[[package]] +name = "data-url" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" + +[[package]] +name = "decompal-rs" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "blake3", + "bytes", + "chrono", + "grass", + "image", + "lightningcss", + "mime", + "minijinja", + "minijinja-autoreload", + "moka", + "objdiff-core", + "octocrab", + "oxc", + "palette", + "prost", + "regex", + "resvg", + "serde", + "serde_json", + "serde_yaml", + "sqlx", + "streemap", + "timeago", + "tokio", + "tokio-cron-scheduler", + "tower", + "tower-http", + "tracing", + "tracing-subscriber", + "treemap", + "zip", + "zstd", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.76", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.76", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "dtoa" +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 = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +dependencies = [ + "serde", +] + +[[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 0.52.0", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "exr" +version = "1.72.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" +dependencies = [ + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide 0.7.4", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fast-srgb8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" + +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + +[[package]] +name = "fdeflate" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flagset" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3ea1ec5f8307826a5b71094dd91fc04d4ae75d5709b20ad351c7fb4815c86ec" + +[[package]] +name = "flate2" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c0596c1eac1f9e04ed902702e9878208b336edc9d6fddc8a48387349bab3666" +dependencies = [ + "crc32fast", + "miniz_oxide 0.8.0", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "fontconfig-parser" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fcfcd44ca6e90c921fee9fa665d530b21ef1327a4c1a6c5250ea44b776ada7" +dependencies = [ + "roxmltree", +] + +[[package]] +name = "fontdb" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37be9fc20d966be438cd57a45767f73349477fb0f85ce86e000557f787298afb" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser", +] + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.76", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gif" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] +name = "grass" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7a68216437ef68f0738e48d6c7bb9e6e6a92237e001b03d838314b068f33c94" +dependencies = [ + "clap", + "getrandom", + "grass_compiler", +] + +[[package]] +name = "grass_compiler" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d9e3df7f0222ce5184154973d247c591d9aadc28ce7a73c6cd31100c9facff6" +dependencies = [ + "codemap", + "indexmap 2.4.0", + "lasso", + "once_cell", + "phf 0.11.2", + "rand", +] + +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.11", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "hdrhistogram" +version = "7.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" +dependencies = [ + "byteorder", + "num-traits", +] + +[[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 = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a397c49fec283e3d6211adbe480be95aae5f304cfb923e9970e08956d5168a" + +[[package]] +name = "httparse" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "log", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "image" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "num-traits", + "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imagesize" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" + +[[package]] +name = "imgref" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", +] + +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.76", +] + +[[package]] +name = "iri-string" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f5f6c2df22c009ac44f6f1499308e7a3ac7ba42cd2378475cc691510e1eef1b" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[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 = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + +[[package]] +name = "js-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jsonwebtoken" +version = "9.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" +dependencies = [ + "base64 0.21.7", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + +[[package]] +name = "kurbo" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5aa9f0f96a938266bdb12928a67169e8d22c6a786fda8ed984b85e6ba93c3c" +dependencies = [ + "arrayvec", + "smallvec", +] + +[[package]] +name = "lasso" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e14eda50a3494b3bf7b9ce51c52434a761e383d7238ce1dd5dcec2fbc13e9fb" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", + "redox_syscall 0.5.3", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "lightningcss" +version = "1.0.0-alpha.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec380ca49dc7f6a1cafbdd2de5e587958eac0f67ab26b1e56727fcc60a0c3d4d" +dependencies = [ + "ahash 0.8.11", + "bitflags 2.6.0", + "const-str", + "cssparser", + "cssparser-color", + "dashmap 5.5.3", + "data-encoding", + "getrandom", + "itertools 0.10.5", + "lazy_static", + "lightningcss-derive", + "parcel_selectors", + "parcel_sourcemap", + "paste", + "pathdiff", + "rayon", + "serde", + "smallvec", +] + +[[package]] +name = "lightningcss-derive" +version = "1.0.0-alpha.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12744d1279367caed41739ef094c325d53fb0ffcd4f9b84a368796f870252" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[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 = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memmap2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +dependencies = [ + "libc", +] + +[[package]] +name = "memo-map" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miette" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4edc8853320c2a0dab800fbda86253c8938f6ea88510dc92c5f1ed20e794afc1" +dependencies = [ + "cfg-if", + "miette-derive", + "owo-colors", + "textwrap", + "thiserror", + "unicode-width", +] + +[[package]] +name = "miette-derive" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.76", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minijinja" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7d3e3a3eece1fa4618237ad41e1de855ced47eab705cec1c9a920e1d1c5aad" +dependencies = [ + "memo-map", + "self_cell", + "serde", +] + +[[package]] +name = "minijinja-autoreload" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50514775ef2041ef77281e7f900964819d9c32752a9a48193d17ecd5f011acd7" +dependencies = [ + "minijinja", + "notify", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", + "simd-adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "moka" +version = "0.12.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cf62eb4dd975d2dde76432fb1075c49e3ee2331cf36f1f8fd4b66550d32b6f" +dependencies = [ + "async-lock", + "async-trait", + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "event-listener", + "futures-util", + "once_cell", + "parking_lot", + "quanta", + "rustc_version", + "smallvec", + "tagptr", + "thiserror", + "triomphe", + "uuid", +] + +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "notify" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "729f63e1ca555a43fe3efa4f3efdf4801c479da85b432242a7b726f353c88486" +dependencies = [ + "bitflags 1.3.2", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "mio 0.8.11", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.76", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "objdiff-core" +version = "2.0.0-beta.6" +source = "git+https://github.com/encounter/objdiff?tag=v2.0.0-beta.6#e1ae369d172240091735c7063f85d54c5052ac62" +dependencies = [ + "anyhow", + "byteorder", + "filetime", + "flagset", + "log", + "memmap2", + "num-traits", + "object", + "pbjson", + "pbjson-build", + "prost", + "prost-build", + "serde", + "serde_json", + "similar", + "strum", + "tsify-next", + "wasm-bindgen", +] + +[[package]] +name = "object" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +dependencies = [ + "memchr", +] + +[[package]] +name = "octocrab" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9305e4c99543ecd0f42bd659c9e9d6ca7115fe5e37d5c85a7277b1db0d4c4101" +dependencies = [ + "arc-swap", + "async-trait", + "base64 0.22.1", + "bytes", + "cfg-if", + "chrono", + "either", + "futures", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-timeout", + "hyper-util", + "jsonwebtoken", + "once_cell", + "percent-encoding", + "pin-project", + "secrecy", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "snafu", + "tokio", + "tower", + "tower-http", + "tracing", + "url", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "outref" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f222829ae9293e33a9f5e9f440c6760a3d450a64affe1846486b140db81c1f4" + +[[package]] +name = "outref" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owo-colors" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" + +[[package]] +name = "oxc" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be9e1d423159a2fea59afa0568a35cadbc34ff9f808bef52ce4780e4d4c1a1cf" +dependencies = [ + "oxc_allocator", + "oxc_ast", + "oxc_codegen", + "oxc_diagnostics", + "oxc_index", + "oxc_mangler", + "oxc_minifier", + "oxc_parser", + "oxc_semantic", + "oxc_span", + "oxc_syntax", + "oxc_transformer", +] + +[[package]] +name = "oxc-browserslist" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b5ad390a7cf69d5f574ea41eada88a7c0947272647703fe1ec6ce23bfe1714" +dependencies = [ + "nom", + "rustc-hash", + "serde", + "serde_json", + "thiserror", + "time", +] + +[[package]] +name = "oxc_allocator" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f922944b51ca85c0acf47c37726a1e9475e5dd9f36c2ea89d1057f5c68f91ff" +dependencies = [ + "allocator-api2", + "bumpalo", +] + +[[package]] +name = "oxc_ast" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4385ef64890edde1135e5431fbe397cdc8f38bf7341d7e23429b5de09dd03897" +dependencies = [ + "bitflags 2.6.0", + "num-bigint", + "oxc_allocator", + "oxc_ast_macros", + "oxc_regular_expression", + "oxc_span", + "oxc_syntax", +] + +[[package]] +name = "oxc_ast_macros" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "807868208f9a594a88f6714dae60bc8bed4ccb87a5d3fd33ed946f6fc8a216de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.76", +] + +[[package]] +name = "oxc_cfg" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3cc540f00d582f3744b53b1316bf40a27a96817aab8dd3a01c6af36591e1ed7" +dependencies = [ + "bitflags 2.6.0", + "itertools 0.13.0", + "oxc_syntax", + "petgraph", + "rustc-hash", +] + +[[package]] +name = "oxc_codegen" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6baeec6b0f636263db3aae5ddbefb0d851a0de8ac4e866513adba605570c8009" +dependencies = [ + "bitflags 2.6.0", + "daachorse", + "nonmax", + "once_cell", + "oxc_allocator", + "oxc_ast", + "oxc_index", + "oxc_mangler", + "oxc_sourcemap", + "oxc_span", + "oxc_syntax", + "rustc-hash", +] + +[[package]] +name = "oxc_diagnostics" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb283f8d9f7926c5ec4db85a65908ad72a3783110cc14771dbdec5ac81ad5d79" +dependencies = [ + "miette", + "owo-colors", + "textwrap", + "unicode-width", +] + +[[package]] +name = "oxc_index" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15c56c7fe9c3d99df968c5d0b3129eb373228c09915e22fc91d24e80c262e0d" + +[[package]] +name = "oxc_mangler" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edf0aa6e9bff1f112cf640e12c3c9986c6119df5dfd6d81d7573951902db2891" +dependencies = [ + "itertools 0.13.0", + "oxc_ast", + "oxc_index", + "oxc_semantic", + "oxc_span", +] + +[[package]] +name = "oxc_minifier" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e13c98ed13f58b86cc16703c7606653021ac832948cfe5d28ccec17c1ea5c1" +dependencies = [ + "num-bigint", + "num-traits", + "oxc_allocator", + "oxc_ast", + "oxc_codegen", + "oxc_diagnostics", + "oxc_mangler", + "oxc_parser", + "oxc_semantic", + "oxc_span", + "oxc_syntax", + "oxc_traverse", +] + +[[package]] +name = "oxc_parser" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1ac96c09e7d0a33f25bfac70632eb3d8196e52b8e276ba5661c71da36f3d1ee" +dependencies = [ + "assert-unchecked", + "bitflags 2.6.0", + "memchr", + "num-bigint", + "num-traits", + "oxc_allocator", + "oxc_ast", + "oxc_diagnostics", + "oxc_regular_expression", + "oxc_span", + "oxc_syntax", + "rustc-hash", + "seq-macro", +] + +[[package]] +name = "oxc_regular_expression" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d98c72fa996ba40322be6bd6c00e427036d9ffacbafad9347f08401e3553266" +dependencies = [ + "oxc_allocator", + "oxc_ast_macros", + "oxc_diagnostics", + "oxc_span", + "phf 0.11.2", + "rustc-hash", + "unicode-id-start", +] + +[[package]] +name = "oxc_semantic" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd200ec63c6bcb0251af23608e741449add80be84c925f2ba2bbc66ac614e7a6" +dependencies = [ + "assert-unchecked", + "indexmap 2.4.0", + "itertools 0.13.0", + "oxc_allocator", + "oxc_ast", + "oxc_cfg", + "oxc_diagnostics", + "oxc_index", + "oxc_span", + "oxc_syntax", + "phf 0.11.2", + "rustc-hash", +] + +[[package]] +name = "oxc_sourcemap" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb8a12d2fa644f0bd6ae928e7f6fa81b483e312aadc2dbfd12bed2f34db25c6" +dependencies = [ + "base64-simd 0.8.0", + "cfg-if", + "rustc-hash", + "serde", + "serde_json", +] + +[[package]] +name = "oxc_span" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "984cf0c05a0da6c557d7c7a600120841910d382008b92ac0ff44af400ba79e29" +dependencies = [ + "compact_str", + "miette", + "oxc_allocator", + "oxc_ast_macros", +] + +[[package]] +name = "oxc_syntax" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0343ef487214dbf9f296e155caeaab0d20a5da577ebd54a80f4fa4ce6a462f5a" +dependencies = [ + "assert-unchecked", + "bitflags 2.6.0", + "dashmap 6.1.0", + "nonmax", + "oxc_allocator", + "oxc_ast_macros", + "oxc_index", + "oxc_span", + "phf 0.11.2", + "rustc-hash", + "ryu-js", + "unicode-id-start", +] + +[[package]] +name = "oxc_transformer" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e0bed15e5c31df9b299da2d24fda74f6954ffcb7d77940872e279e7fb0c6258" +dependencies = [ + "dashmap 6.1.0", + "indexmap 2.4.0", + "oxc-browserslist", + "oxc_allocator", + "oxc_ast", + "oxc_diagnostics", + "oxc_regular_expression", + "oxc_semantic", + "oxc_span", + "oxc_syntax", + "oxc_traverse", + "ropey", + "rustc-hash", + "serde", + "serde_json", +] + +[[package]] +name = "oxc_traverse" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e98e46241628be228e93619d5fb6ab676ba8adaf9e2f372ea3b06111034001" +dependencies = [ + "compact_str", + "memoffset", + "oxc_allocator", + "oxc_ast", + "oxc_semantic", + "oxc_span", + "oxc_syntax", +] + +[[package]] +name = "palette" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" +dependencies = [ + "approx", + "fast-srgb8", + "palette_derive", + "phf 0.11.2", +] + +[[package]] +name = "palette_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" +dependencies = [ + "by_address", + "proc-macro2", + "quote", + "syn 2.0.76", +] + +[[package]] +name = "parcel_selectors" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512215cb1d3814e276ace4ec2dbc2cac16726ea3fcac20c22ae1197e16fdd72d" +dependencies = [ + "bitflags 2.6.0", + "cssparser", + "fxhash", + "log", + "phf 0.10.1", + "phf_codegen", + "precomputed-hash", + "smallvec", +] + +[[package]] +name = "parcel_sourcemap" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "485b74d7218068b2b7c0e3ff12fbc61ae11d57cb5d8224f525bd304c6be05bbb" +dependencies = [ + "base64-simd 0.7.0", + "data-url 0.1.1", + "rkyv", + "serde", + "serde_json", + "vlq", +] + +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + +[[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 0.5.3", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + +[[package]] +name = "pbjson" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e6349fa080353f4a597daffd05cb81572a9c031a6d4fff7e504947496fcc68" +dependencies = [ + "base64 0.21.7", + "serde", +] + +[[package]] +name = "pbjson-build" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eea3058763d6e656105d1403cb04e0a41b7bbac6362d413e7c33be0c32279c9" +dependencies = [ + "heck", + "itertools 0.13.0", + "prost", + "prost-types", +] + +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.1", + "serde", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap 2.4.0", +] + +[[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.76", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.76", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "png" +version = "0.17.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.7.4", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" +dependencies = [ + "proc-macro2", + "syn 2.0.76", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" +dependencies = [ + "quote", + "syn 2.0.76", +] + +[[package]] +name = "prost" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13db3d3fde688c61e2446b4d843bc27a7e8af269a69440c0308021dc92333cc" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb182580f71dd070f88d01ce3de9f4da5021db7115d2e1c3605a754153b77c1" +dependencies = [ + "bytes", + "heck", + "itertools 0.13.0", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 2.0.76", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18bec9b0adc4eba778b33684b7ba3e7137789434769ee3ce3930463ef904cfca" +dependencies = [ + "anyhow", + "itertools 0.13.0", + "proc-macro2", + "quote", + "syn 2.0.76", +] + +[[package]] +name = "prost-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cee5168b05f49d4b0ca581206eb14a7b22fafd963efe729ac48eb03266e25cc2" +dependencies = [ + "prost", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quanta" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.12.1", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "system-deps", + "thiserror", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f0bfd976333248de2078d350bfdf182ff96e168a24d23d2436cef320dd4bdd" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rgb", +] + +[[package]] +name = "raw-cpuid" +version = "11.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb9ee317cfe3fbd54b36a511efc1edd42e216903c9cd575e686dd68a2ba90d8d" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "resvg" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7314563c59c7ce31c18e23ad3dd092c37b928a0fa4e1c0a1a6504351ab411d1" +dependencies = [ + "gif", + "image-webp", + "log", + "pico-args", + "rgb", + "svgtypes", + "tiny-skia", + "usvg", + "zune-jpeg", +] + +[[package]] +name = "rgb" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rkyv" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ropey" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93411e420bcd1a75ddd1dc3caf18c23155eda2c090631a85af21ba19e97093b5" +dependencies = [ + "smallvec", + "str_indices", +] + +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + +[[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.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" + +[[package]] +name = "rustls-webpki" +version = "0.102.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "rustybuzz" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85d1ccd519e61834798eb52c4e886e8c2d7d698dd3d6ce0b1b47eb8557f1181" +dependencies = [ + "bitflags 2.6.0", + "bytemuck", + "core_maths", + "log", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "ryu-js" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad97d4ce1560a5e27cec89519dc8300d1aa6035b099821261c651486a19e44d5" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "self_cell" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "seq-macro" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" + +[[package]] +name = "serde" +version = "1.0.209" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_derive" +version = "1.0.209" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.76", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.76", +] + +[[package]] +name = "serde_json" +version = "1.0.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.4.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[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-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "simd-abstraction" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cadb29c57caadc51ff8346233b5cec1d240b68ce55cf1afc764818791876987" +dependencies = [ + "outref 0.1.0", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + +[[package]] +name = "similar" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" + +[[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + +[[package]] +name = "simplecss" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "snafu" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b835cb902660db3415a672d862905e791e54d306c6e8189168c7f3d9ae1c79d" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d1e02fca405f6280643174a50c942219f0bbf4dbf7d480f1dd864d6f211ae5" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.76", +] + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f" +dependencies = [ + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcfa89bea9500db4a0d038513d7a060566bfc51d46d1c014847049a45cce85e8" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d06e2f2bd861719b1f3f0c7dbe1d80c30bf59e76cf019f07d9014ed7eefb8e08" +dependencies = [ + "atoi", + "byteorder", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.14.5", + "hashlink", + "hex", + "indexmap 2.4.0", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror", + "tokio", + "tokio-stream", + "tracing", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f998a9defdbd48ed005a89362bd40dd2117502f15294f61c8d47034107dbbdc" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.76", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d100558134176a2629d46cec0c8891ba0be8910f7896abfdb75ef4ab6f4e7ce" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 2.0.76", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cac0ab331b14cb3921c62156d913e4c15b74fb6ec0f3146bd4ef6e4fb3c12" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.6.0", + "byteorder", + "bytes", + "chrono", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9734dbce698c67ecf67c442f768a5e90a49b2a4d61a9f1d59f73874bd4cf0710" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.6.0", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75b419c3c1b1697833dd927bdc4c6545a620bc1bbafabd44e1efbe9afcd337e" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "tracing", + "url", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "str_indices" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9557cb6521e8d009c51a8666f09356f4b817ba9ba0981a305bd86aee47bd35c" + +[[package]] +name = "streemap" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17f303a9ec55e27e92d11c78e30d029f0dc97e80f8323c97e63aab901116329" +dependencies = [ + "num-traits", +] + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[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 2.0.76", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "svgtypes" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "794de53cc48eaabeed0ab6a3404a65f40b3e38c067e4435883a65d2aa4ca000e" +dependencies = [ + "kurbo", + "siphasher 1.0.1", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tempfile" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.76", +] + +[[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 = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "timeago" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1710e589de0a76aaf295cd47a6699f6405737dbfd3cf2b75c92d000b548d0e6" +dependencies = [ + "chrono", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "png", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio 1.0.2", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-cron-scheduler" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b9480125554f0ace1c3c3797a24b5cc56c6a7cd82c739db35fb54c4dc046f3" +dependencies = [ + "chrono", + "cron", + "num-derive", + "num-traits", + "tokio", + "tracing", + "uuid", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.76", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +dependencies = [ + "indexmap 2.4.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "hdrhistogram", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +dependencies = [ + "async-compression", + "base64 0.21.7", + "bitflags 2.6.0", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "http-range-header", + "httpdate", + "iri-string", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower", + "tower-layer", + "tower-service", + "tracing", + "uuid", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +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", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.76", +] + +[[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-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "treemap" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1571f89da27a5e1aa83304ee1ab9519ea8c6432b4c8903aaaa6c9a9eecb6f36" + +[[package]] +name = "triomphe" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3" + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tsify-next" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4a645dca4ee0800f5ab60ce166deba2db6a0315de795a2691e138a3d55d756" +dependencies = [ + "serde", + "serde-wasm-bindgen", + "tsify-next-macros", + "wasm-bindgen", +] + +[[package]] +name = "tsify-next-macros" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d5c06f8a51d759bb58129e30b2631739e7e1e4579fad1f30ac09a6c88e488a6" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.76", +] + +[[package]] +name = "ttf-parser" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be21190ff5d38e8b4a2d3b6a3ae57f612cc39c96e83cedeaf7abc338a8bac4a" +dependencies = [ + "core_maths", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64af057ad7466495ca113126be61838d8af947f41d93a949980b2389a118082f" + +[[package]] +name = "unicode-ccc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "260bc6647b3893a9a90668360803a15f96b85a5257b1c3a0c3daf6ae2496de42" + +[[package]] +name = "unicode-id-start" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc3882f69607a2ac8cc4de3ee7993d8f68bb06f2974271195065b3bd07f2edea" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" + +[[package]] +name = "unicode-script" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd" + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "usvg" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6803057b5cbb426e9fb8ce2216f3a9b4ca1dd2c705ba3cbebc13006e437735fd" +dependencies = [ + "base64 0.22.1", + "data-url 0.3.1", + "flate2", + "fontdb", + "imagesize", + "kurbo", + "log", + "pico-args", + "roxmltree", + "rustybuzz", + "simplecss", + "siphasher 1.0.1", + "strict-num", + "svgtypes", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +dependencies = [ + "getrandom", +] + +[[package]] +name = "v_frame" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vlq" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65dd7eed29412da847b0f78bcec0ac98588165988a8cfe41d4ea1d429f8ccfff" + +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.76", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.76", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" + +[[package]] +name = "web-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + +[[package]] +name = "whoami" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +dependencies = [ + "redox_syscall 0.4.1", + "wasite", +] + +[[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-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[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-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.76", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zip" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc5e4288ea4057ae23afc69a4472434a87a2495cafce6632fd1c4ec9f5cf3494" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "flate2", + "indexmap 2.4.0", + "memchr", + "thiserror", +] + +[[package]] +name = "zstd" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.13+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..67c2159 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "decompal-rs" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0" +axum = "0.7" +blake3 = "1.5" +bytes = "1.7" +chrono = "0.4" +grass = "0.13" +image = "0.25" +lightningcss = "1.0.0-alpha" +mime = "0.3" +minijinja = { version = "2.2", features = ["loader"] } +minijinja-autoreload = "2.2" +moka = { version = "0.12", features = ["future"] } +objdiff-core = { git = "https://github.com/encounter/objdiff", tag = "v2.0.0-beta.6", features = ["bindings"] } +#objdiff-core = { path = "../objdiff/objdiff-core", features = ["bindings"] } +octocrab = "0.39" +oxc = { version = "0.27", features = ["codegen", "minifier", "transformer", "semantic"] } +palette = "0.7" +prost = "0.13" +regex = "1.10" +resvg = "0.43" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +serde_yaml = "0.9" +sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite", "chrono"] } +streemap = "0.1" +timeago = { version = "0.4.2", default-features = false, features = ["chrono"] } +tokio = { version = "1.40", features = ["full"] } +tokio-cron-scheduler = "0.11" +tower = { version = "0.4", features = ["full"] } +tower-http = { version = "0.5", features = ["full"] } +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +treemap = "0.3" +zip = { version = "2.2", default-features = false, features = ["deflate-flate2"] } +zstd = "0.13" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..3a8149e --- /dev/null +++ b/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo:rerun-if-changed=migrations"); +} diff --git a/cmd/decompal/main.go b/cmd/decompal/main.go deleted file mode 100644 index e7204c8..0000000 --- a/cmd/decompal/main.go +++ /dev/null @@ -1,56 +0,0 @@ -package main - -import ( - "context" - "github.com/encounter/decompal/config" - "github.com/encounter/decompal/handlers" - "github.com/gregjones/httpcache" - "github.com/palantir/go-baseapp/baseapp" - "github.com/palantir/go-githubapp/githubapp" - "github.com/pkg/errors" - "github.com/rs/zerolog" - "goji.io/pat" - "time" -) - -func main() { - // Load configuration from a file - cfg, err := config.ReadConfig("config.yml") - if err != nil { - panic(errors.Wrap(err, "failed to read config")) - } - logger := baseapp.NewLogger(cfg.Logging) - zerolog.DefaultContextLogger = &logger - - // Create the server - serverParams := baseapp.DefaultParams(logger, "decompal.") - server, err := baseapp.NewServer(cfg.Server, serverParams...) - if err != nil { - logger.Fatal().Err(err).Msg("failed to create server") - } - - // Create GitHub app client - cc, err := githubapp.NewDefaultCachingClientCreator( - cfg.GitHub, - githubapp.WithClientUserAgent("decompal/1.0.0"), - githubapp.WithClientTimeout(5*time.Second), - githubapp.WithClientCaching(false, func() httpcache.Cache { return httpcache.NewMemoryCache() }), - ) - if err != nil { - logger.Fatal().Err(err).Msg("failed to create GitHub app client") - } - - // Register GitHub webhook handlers - taskCtx, taskCancel := context.WithCancel(context.Background()) - defer taskCancel() - server.Mux().Handle(pat.Post(githubapp.DefaultWebhookRoute), githubapp.NewDefaultEventDispatcher( - cfg.GitHub, - handlers.NewPullRequestHandler(cc, &cfg.App, taskCtx), - handlers.NewWorkflowRunHandler(cc, &cfg.App, taskCtx), - )) - - // Start the server (blocking) - if err = server.Start(); err != nil { - logger.Fatal().Err(err).Msg("server failed") - } -} diff --git a/config.example.yml b/config.example.yml index a22c07c..26e6fce 100644 --- a/config.example.yml +++ b/config.example.yml @@ -1,25 +1,6 @@ server: - address: 127.0.0.1 - port: 8080 - shutdown_wait_time: 15s - -logging: - pretty: true - level: debug + port: 3000 app: - objdiff_path: ./objdiff-cli-linux-x86_64 - -github: - v3_api_url: https://api.github.com/ - v4_api_url: https://api.github.com/graphql - app: - integration_id: 1234 - webhook_secret: webhook_secret - private_key: | - -----BEGIN RSA PRIVATE KEY----- - ... - -----END RSA PRIVATE KEY----- - oauth: - client_id: client_id - client_secret: client_secret + db_url: sqlite:./db.sqlite + github_token: ghp_abcd1234abcd1234abcd1234abcd1234abcd diff --git a/config/config.go b/config/config.go deleted file mode 100644 index 5e58228..0000000 --- a/config/config.go +++ /dev/null @@ -1,36 +0,0 @@ -package config - -import ( - "github.com/palantir/go-baseapp/baseapp" - "github.com/palantir/go-githubapp/githubapp" - "github.com/pkg/errors" - "gopkg.in/yaml.v3" - "os" -) - -type Config struct { - Server baseapp.HTTPConfig `yaml:"server"` - Logging baseapp.LoggingConfig `yaml:"logging"` - GitHub githubapp.Config `yaml:"github"` - App AppConfig `yaml:"app"` -} - -type AppConfig struct { - //TmpDir string `yaml:"tmp_dir"` - ObjdiffPath string `yaml:"objdiff_path"` -} - -func ReadConfig(path string) (Config, error) { - var c Config - - bytes, err := os.ReadFile(path) - if err != nil { - return c, errors.Wrapf(err, "failed reading server config file: %s", path) - } - - if err := yaml.Unmarshal(bytes, &c); err != nil { - return c, errors.Wrap(err, "failed parsing configuration file") - } - - return c, nil -} diff --git a/css/main.scss b/css/main.scss new file mode 100644 index 0000000..e725fa9 --- /dev/null +++ b/css/main.scss @@ -0,0 +1,261 @@ +@use "sass:map"; + +$breakpoints: ( + // Small (landscape phones) + // Font size: 17px + sm: + ( + breakpoint: 576px, + viewport: 510px, + root-font-size: 106.25%, + ), + // Medium (tablets) + // Font size: 18px + md: + ( + breakpoint: 768px, + viewport: 700px, + root-font-size: 112.5%, + ), + // Large + // Font size: 19px + lg: + ( + breakpoint: 1024px, + viewport: 950px, + root-font-size: 118.75%, + ), + // Max out at lg breakpoint + xl: + ( + breakpoint: 1024px, + viewport: 950px, + root-font-size: 118.75%, + ), + xxl: + ( + breakpoint: 1024px, + viewport: 950px, + root-font-size: 118.75%, + ), +); +@use "@picocss/pico/scss/pico" with ( + $theme-color: "azure", + $breakpoints: $breakpoints, + $enable-semantic-container: true, + $enable-classes: true, + $modules: ( + // Theme + "themes/default": true, + + // Layout + "layout/document": true, + "layout/landmarks": true, + "layout/container": true, + "layout/section": true, + "layout/grid": true, + "layout/overflow-auto": true, + + // Content + "content/link": true, + "content/typography": true, + "content/embedded": true, + "content/button": true, + "content/table": true, + "content/code": true, + "content/figure": true, + "content/miscs": true, + + // Forms + "forms/basics": true, + "forms/checkbox-radio-switch": true, + "forms/input-color": true, + "forms/input-date": true, + "forms/input-file": true, + "forms/input-range": true, + "forms/input-search": true, + + // Components + "components/accordion": true, + "components/card": true, + "components/dropdown": true, + "components/group": true, + "components/loading": true, + "components/modal": true, + "components/nav": true, + "components/progress": false, + "components/tooltip": true, + + // Utilities + "utilities/accessibility": true, + "utilities/reduce-motion": true + ) +); +@use "@picocss/pico/scss/colors" as *; + +[data-theme=light], +:root:not([data-theme=dark]) { + --pico-code-kbd-color: #000; + --pico-code-kbd-background-color: #fff; +} + +@mixin pico-theme-dark { + --pico-background-color: #181c25; + --pico-code-kbd-color: #fff; + --pico-code-kbd-background-color: #333; +} + +[data-theme=dark] { + @include pico-theme-dark; +} + +@media only screen and (prefers-color-scheme: dark) { + :root:not([data-theme]) { + @include pico-theme-dark; + } +} + +// Override the tooltip component +[data-tooltip]:not(a, button, input) { + border-bottom: none; + cursor: pointer; +} + +nav { + > ul:first-child { + > li:first-child * { + color: var(--pico-contrast); + } + + > li:not(:last-child):after { + display: inline-block; + width: 0; + vertical-align: baseline; + content: "/"; + padding-left: 10px; + } + + @media (max-width: map.get(map.get($breakpoints, "sm"), "breakpoint")) { + > li:not(:first-child) { + display: none; + } + + > li:not(:last-child):after { + display: none; + } + } + } +} + +@keyframes progress-stripes { + 0% { + background-position: 0 0 + } + to { + background-position: calc(2.5rem) 0 + } +} + +$progress-height: 2rem; + +.progress-root { + display: flex; + height: $progress-height; + overflow: hidden; + border-radius: var(--pico-border-radius); + background-color: $slate-800; + + .progress-section.striped { + //animation: progress-stripes 1s linear infinite; + background-image: linear-gradient( + 45deg, + rgba(255, 255, 255, .15) 25%, + transparent 25%, + transparent 50%, + rgba(255, 255, 255, .15) 50%, + rgba(255, 255, 255, .15) 75%, + transparent 75%, + transparent); + background-size: calc(1.25rem) calc(1.25rem); + background-repeat: repeat; + } + + &.code { + .progress-section:nth-child(1) { + background-color: $jade-400; + } + + .progress-section:nth-child(2) { + background-color: $jade-500; + } + + .progress-section:nth-child(3) { + background-color: $slate-800; + } + } + + &.data { + .progress-section:nth-child(1) { + background-color: $azure-400; + } + + .progress-section:nth-child(2) { + background-color: $azure-500; + } + } + + &.overlay { + position: relative; + top: $progress-height * -1; + margin-bottom: calc($progress-height * -1 + var(--pico-spacing)); + overflow: visible; + background: none; + + .progress-section { + background: none; + } + } +} + +@media (prefers-reduced-motion: reduce) { + .progress-root .progress-section.striped { + animation: none; + } +} + +$unit-size: 0.75rem; + +.units-root { + display: flex; + flex-flow: row wrap; + margin-bottom: var(--pico-spacing); + + .unit { + width: $unit-size; + height: $unit-size; + background-color: #333; + + &:hover { + border: 1px solid #fff; + } + } +} + +.muted { + color: var(--pico-muted-color); + + a { + color: var(--pico-muted-color); + } +} + +#graph { + width: 100%; + aspect-ratio: 2 / 1; + margin-bottom: var(--pico-spacing); + touch-action: none; + // Accessed via JS + --font-weight: 400; + --font-size: 1rem; + --font-family: var(--pico-font-family); +} diff --git a/genproto.sh b/genproto.sh deleted file mode 100755 index 2333a82..0000000 --- a/genproto.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh -e -SRC_DIR=../objdiff/objdiff-core/protos -protoc -I=$SRC_DIR \ - --go_out=paths=source_relative:objdiff \ - --go_opt=Mreport.proto=github.com/encounter/decompal/objdiff \ - $SRC_DIR/report.proto diff --git a/go.mod b/go.mod deleted file mode 100644 index a9a3b66..0000000 --- a/go.mod +++ /dev/null @@ -1,34 +0,0 @@ -module github.com/encounter/decompal - -go 1.22 - -require ( - github.com/google/go-github/v63 v63.0.0 - github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 - github.com/palantir/go-baseapp v0.5.2 - github.com/palantir/go-githubapp v0.27.0 - github.com/pkg/errors v0.9.1 - github.com/rs/zerolog v1.33.0 - goji.io v2.0.2+incompatible - google.golang.org/protobuf v1.34.2 - gopkg.in/yaml.v3 v3.0.1 -) - -require ( - github.com/bluekeyes/hatpear v0.1.2 // indirect - github.com/bradleyfalzon/ghinstallation/v2 v2.11.0 // indirect - github.com/golang-jwt/jwt/v4 v4.5.0 // indirect - github.com/google/go-github/v62 v62.0.0 // indirect - github.com/google/go-querystring v1.1.0 // indirect - github.com/hashicorp/golang-lru v1.0.2 // indirect - github.com/kr/text v0.2.0 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/patrickmn/go-cache v2.1.0+incompatible // indirect - github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect - github.com/rs/xid v1.5.0 // indirect - github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7 // indirect - github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect - golang.org/x/oauth2 v0.22.0 // indirect - golang.org/x/sys v0.24.0 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 359b88f..0000000 --- a/go.sum +++ /dev/null @@ -1,75 +0,0 @@ -github.com/bluekeyes/hatpear v0.1.2 h1:D5dz9W/W5YxKlmqn7hRJi218+yVkXylhs/R6Ns/jj4A= -github.com/bluekeyes/hatpear v0.1.2/go.mod h1:2bh+rl4wLhqzzL0hT7Q4SVGXIivrE8oKgH2WYM3ubt0= -github.com/bradleyfalzon/ghinstallation/v2 v2.11.0 h1:R9d0v+iobRHSaE4wKUnXFiZp53AL4ED5MzgEMwGTZag= -github.com/bradleyfalzon/ghinstallation/v2 v2.11.0/go.mod h1:0LWKQwOHewXO/1acI6TtyE0Xc4ObDb2rFN7eHBAG71M= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-github/v62 v62.0.0 h1:/6mGCaRywZz9MuHyw9gD1CwsbmBX8GWsbFkwMmHdhl4= -github.com/google/go-github/v62 v62.0.0/go.mod h1:EMxeUqGJq2xRu9DYBMwel/mr7kZrzUOfQmmpYrZn2a4= -github.com/google/go-github/v63 v63.0.0 h1:13xwK/wk9alSokujB9lJkuzdmQuVn2QCPeck76wR3nE= -github.com/google/go-github/v63 v63.0.0/go.mod h1:IqbcrgUmIcEaioWrGYei/09o+ge5vhffGOcxrO0AfmA= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= -github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= -github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/palantir/go-baseapp v0.5.2 h1:b1ukx7AXo2/E4NkUvTFlW+185uwCcifzd2XzLrG4oS8= -github.com/palantir/go-baseapp v0.5.2/go.mod h1:uijQMPfmgV69oiMu2jkskum/4HiYuEP/gzrnphD+/Co= -github.com/palantir/go-githubapp v0.27.0 h1:YTSnLElOVOSSFOqCiAyc1wpZMQjGmMvjas0W/F9W4Jk= -github.com/palantir/go-githubapp v0.27.0/go.mod h1:yi9WLdjHkIfeZMetaZyCFJLcxocmrYdd+e5BJTfkimI= -github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= -github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= -github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= -github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7 h1:cYCy18SHPKRkvclm+pWm1Lk4YrREb4IOIb/YdFO0p2M= -github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7/go.mod h1:zqMwyHmnN/eDOZOdiTohqIUKUrTFX62PNlu7IJdu0q8= -github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZVHkmAsGEkcIu0oCe3AM420QDgGwZx0= -github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -goji.io v2.0.2+incompatible h1:uIssv/elbKRLznFUy3Xj4+2Mz/qKhek/9aZQDUMae7c= -goji.io v2.0.2+incompatible/go.mod h1:sbqFwrtqZACxLBTQcdgVjFh54yGVCvwq8+w49MVMMIk= -golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= -golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/handlers/pull_request.go b/handlers/pull_request.go deleted file mode 100644 index cb6b1bd..0000000 --- a/handlers/pull_request.go +++ /dev/null @@ -1,123 +0,0 @@ -package handlers - -import ( - "context" - "encoding/json" - "github.com/encounter/decompal/config" - "github.com/encounter/decompal/objdiff" - "github.com/google/go-github/v63/github" - "github.com/palantir/go-githubapp/githubapp" - "github.com/pkg/errors" -) - -type pullRequestHandler struct { - githubapp.ClientCreator - config *config.AppConfig - taskCtx context.Context -} - -func NewPullRequestHandler( - cc githubapp.ClientCreator, - config *config.AppConfig, - taskCtx context.Context, -) githubapp.EventHandler { - return &pullRequestHandler{ - ClientCreator: cc, - config: config, - taskCtx: taskCtx, - } -} - -func (h *pullRequestHandler) Handles() []string { - return []string{"pull_request"} -} - -func (h *pullRequestHandler) Handle(_ context.Context, eventType, deliveryID string, payload []byte) error { - event := &github.PullRequestEvent{} - if err := json.Unmarshal(payload, event); err != nil { - return errors.Wrap(err, "failed to parse pull request event payload") - } - if event.GetAction() != "opened" { - return nil - } - - installationID := githubapp.GetInstallationIDFromEvent(event) - client, err := h.NewInstallationClient(installationID) - if err != nil { - return err - } - - go backgroundTask(h.taskCtx, eventType, deliveryID, func(ctx context.Context) error { - repo := event.GetRepo() - // Re-prepare logger instead of using the one from the request context - ctx, logger := githubapp.PrepareRepoContext(ctx, installationID, repo) - - // Find any completed workflow runs for the current PR - repoOwner := repo.GetOwner().GetLogin() - repoName := repo.GetName() - pr := event.GetPullRequest() - sha := pr.GetHead().GetSHA() - runs, _, err := client.Actions.ListRepositoryWorkflowRuns( - ctx, - repoOwner, - repoName, - &github.ListWorkflowRunsOptions{ - Status: "completed", - HeadSHA: sha, - ExcludePullRequests: true, - }, - ) - if err != nil { - return errors.Wrap(err, "failed to list workflow runs") - } - if len(runs.WorkflowRuns) == 0 { - logger.Debug().Msg("No workflow runs found") - return nil - } - - // Find report files in any completed workflow runs - var files []objdiff.ReportFile - var run *github.WorkflowRun - for _, run = range runs.WorkflowRuns { - files, err = objdiff.FetchReportFiles( - ctx, - logger, - client, - repoOwner, - repoName, - sha, - run.GetID(), - ) - if err != nil { - return err - } - if len(files) > 0 { - break - } - } - if run == nil || len(files) == 0 { - logger.Info().Msg("No report files found") - return nil - } - - // Generate changes and create a PR comment - err = processPR( - ctx, - h.config, - installationID, - pr, - sha, - client, - repo, - run.GetWorkflowID(), - files, - ) - if err != nil { - return err - } - - return nil - }) - - return nil -} diff --git a/handlers/shared.go b/handlers/shared.go deleted file mode 100644 index 5204e62..0000000 --- a/handlers/shared.go +++ /dev/null @@ -1,387 +0,0 @@ -package handlers - -import ( - "context" - "fmt" - "github.com/encounter/decompal/config" - "github.com/encounter/decompal/objdiff" - "github.com/google/go-github/v63/github" - "github.com/palantir/go-githubapp/githubapp" - "github.com/pkg/errors" - "strings" -) - -func processPR( - ctx context.Context, - config *config.AppConfig, - installationID int64, - pr *github.PullRequest, - sha string, - client *github.Client, - repo *github.Repository, - workflowID int64, - files []objdiff.ReportFile, -) error { - prNum := pr.GetNumber() - ctx, logger := githubapp.PreparePRContext(ctx, installationID, repo, prNum) - - // Sanity check - head := pr.GetHead() - if head.GetSHA() != sha { - logger.Debug(). - Str("head_sha", head.GetSHA()). - Str("commit_sha", sha). - Msg("Head SHA does not match workflow run SHA") - return nil - } - - // Find workflows runs for the PR base commit - base := pr.GetBase() - repoOwner := repo.GetOwner().GetLogin() - repoName := repo.GetName() - runs, _, err := client.Actions.ListWorkflowRunsByID( - ctx, - repoOwner, - repoName, - workflowID, - &github.ListWorkflowRunsOptions{ - Status: "completed", - HeadSHA: base.GetSHA(), - ExcludePullRequests: true, - }, - ) - if err != nil { - return errors.Wrap(err, "failed to list workflow runs by file name") - } - if len(runs.WorkflowRuns) == 0 { - logger.Debug(). - Str("commit_sha", base.GetSHA()). - Msg("No base workflow runs found") - return nil - } - - // Fetch report files for the PR base commit - baseRun := runs.WorkflowRuns[0] - baseFiles, err := objdiff.FetchReportFiles( - ctx, - logger, - client, - repoOwner, - repoName, - base.GetSHA(), - baseRun.GetID(), - ) - if err != nil { - return err - } - if len(baseFiles) == 0 { - logger.Info(). - Str("commit_sha", base.GetSHA()). - Int64("workflow_run_id", baseRun.GetID()). - Msg("No base report files found") - return nil - } - - // Generate changes for each report file - type versionChange struct { - Version string - Body string - } - versionChanges := make([]versionChange, 0) - for _, baseFile := range baseFiles { - for _, file := range files { - if baseFile.Version == file.Version { - logger := logger.With(). - Str("version", file.Version). - Str("from_sha", baseFile.Sha). - Str("to_sha", file.Sha). - Logger() - logger.Info().Msg("Generating changes") - changes, err := objdiff.GenerateChanges(config, logger, &baseFile, &file) - if err != nil { - logger.Error().Err(err).Msg("Failed to generate changes") - return err - } - body := createChanges(changes) - if body != "" { - versionChanges = append(versionChanges, versionChange{ - Version: file.Version, - Body: body, - }) - } - } - } - } - if len(versionChanges) == 0 { - logger.Info().Msg("No changes found") - return nil - } - - // Generate PR comment body - body := "## Changes\n\n" - for _, vc := range versionChanges { - body += fmt.Sprintf( - "
Version %s\n\n%s\n\n
\n\n", - vc.Version, - vc.Body, - ) - } - - // Update or create PR comment - err = upsertComment(ctx, client, repoOwner, repoName, prNum, body) - if err != nil { - return err - } - return nil -} - -func upsertComment( - ctx context.Context, - client *github.Client, - repoOwner, repoName string, - prNum int, - body string, -) error { - sort := "created" - direction := "asc" - existing, _, err := client.Issues.ListComments(ctx, repoOwner, repoName, prNum, &github.IssueListCommentsOptions{ - Sort: &sort, - Direction: &direction, - }) - if err != nil { - return errors.Wrap(err, "failed to list existing comments") - } - commentID := int64(0) - for _, comment := range existing { - // TODO: update go-github to expose performed_via_github_app - if comment.GetUser().GetLogin() == "decompal[bot]" { - commentID = comment.GetID() - if comment.GetBody() == body { - // No changes - return nil - } - break - } - } - if commentID != 0 { - _, _, err = client.Issues.EditComment(ctx, repoOwner, repoName, commentID, &github.IssueComment{ - Body: github.String(body), - }) - if err != nil { - return errors.Wrap(err, "failed to edit comment") - } - } else { - _, _, err = client.Issues.CreateComment(ctx, repoOwner, repoName, prNum, &github.IssueComment{ - Body: github.String(body), - }) - if err != nil { - return errors.Wrap(err, "failed to create comment") - } - } - return nil -} - -func createChanges(changes *objdiff.Changes) string { - out := "### Overall\n\n" - overallTable := measuresTable(changes.From, changes.To) - if overallTable == "" { - if len(changes.Units) == 0 { - return "" - } - out += "No changes\n\n" - } else { - out += overallTable + "\n\n" - } - for _, unit := range changes.Units { - out += fmt.Sprintf("---\n### `%s`\n\n", unit.Name) - unitTable := measuresTable(unit.From, unit.To) - if unitTable != "" { - out += unitTable + "\n\n" - } - functionsTable := changeItemTable("Functions", unit.Functions) - if functionsTable != "" { - out += functionsTable + "\n\n" - } - } - return out -} - -func changeItemTable(name string, items []*objdiff.ChangeItem) string { - header := fmt.Sprintf("|%s|Previous|Current|Change|\n|-|-|-|-|", name) - rows := make([]string, 0) - for _, item := range items { - row := changeItemInfoRow(item) - if row != "" { - rows = append(rows, row) - } - } - if len(rows) == 0 { - return "" - } - return header + "\n" + strings.Join(rows, "\n") -} - -const ( - incArrow = "${\\color{green}▲}$" - decArrow = "${\\color{red}▼}$" -) - -func floatArrow(diff float32) string { - if diff > 0 { - return " " + incArrow - } - if diff < 0 { - return " " + decArrow - } - return "" -} - -func intArrow(diff int64) string { - if diff > 0 { - return " " + incArrow - } - if diff < 0 { - return " " + decArrow - } - return "" -} - -func changeItemInfoRow(item *objdiff.ChangeItem) string { - var fromPercent, toPercent float32 - if item.From != nil { - fromPercent = item.From.FuzzyMatchPercent - } - if item.To != nil { - toPercent = item.To.FuzzyMatchPercent - } - if fromPercent == toPercent { - return "" - } - diff := toPercent - fromPercent - return fmt.Sprintf( - "|`%s`|%.2f%%|%.2f%%|%.2f%%%s|", - item.Name, - fromPercent, - toPercent, - diff, - floatArrow(diff), - ) -} - -func measuresTable(prev, curr *objdiff.Measures) string { - if prev == nil && curr == nil { - return "" - } else if prev == nil { - // TODO: added - prev = &objdiff.Measures{} - } else if curr == nil { - // TODO: removed - curr = &objdiff.Measures{} - } - header := "|Metric|Previous|Current|Change|\n|-|-|-|-|" - rows := make([]string, 0) - if prev.FuzzyMatchPercent != curr.FuzzyMatchPercent { - rows = append(rows, floatRow("Fuzzy match", prev.FuzzyMatchPercent, curr.FuzzyMatchPercent)) - } - if prev.TotalCode != curr.TotalCode { - rows = append(rows, sizeRow("Total code", prev.TotalCode, curr.TotalCode)) - } - if prev.MatchedCode != curr.MatchedCode || - prev.MatchedCodePercent != curr.MatchedCodePercent { - rows = append(rows, intPercentRow( - "Matched code", - prev.MatchedCode, - prev.MatchedCodePercent, - curr.MatchedCode, - curr.MatchedCodePercent, - )) - } - if prev.TotalData != curr.TotalData { - rows = append(rows, sizeRow("Total data", prev.TotalData, curr.TotalData)) - } - if prev.MatchedData != curr.MatchedData || - prev.MatchedDataPercent != curr.MatchedDataPercent { - rows = append(rows, intPercentRow( - "Matched data", - prev.MatchedData, - prev.MatchedDataPercent, - curr.MatchedData, - curr.MatchedDataPercent, - )) - } - if prev.TotalFunctions != curr.TotalFunctions { - rows = append(rows, intRow("Total functions", prev.TotalFunctions, curr.TotalFunctions)) - } - if prev.MatchedFunctions != curr.MatchedFunctions || - prev.MatchedFunctionsPercent != curr.MatchedFunctionsPercent { - rows = append(rows, intPercentRow( - "Matched functions", - uint64(prev.MatchedFunctions), - prev.MatchedFunctionsPercent, - uint64(curr.MatchedFunctions), - curr.MatchedFunctionsPercent, - )) - } - if len(rows) == 0 { - return "" - } - return header + "\n" + strings.Join(rows, "\n") -} - -func floatRow(name string, prev, curr float32) string { - diff := curr - prev - return fmt.Sprintf( - "|%s|%.2f%%|%.2f%%|%.2f%%%s|", - name, - prev, - curr, - diff, - floatArrow(diff), - ) -} - -func intRow(name string, prev, curr uint32) string { - diff := int64(curr) - int64(prev) - return fmt.Sprintf( - "|%s|%d|%d|%d%s|", - name, - prev, - curr, - diff, - intArrow(diff), - ) -} - -func sizeRow(name string, prev, curr uint64) string { - // TODO: format size - diff := int64(curr) - int64(prev) - return fmt.Sprintf( - "|%s|%d|%d|%d%s|", - name, - prev, - curr, - diff, - intArrow(diff), - ) -} - -func intPercentRow( - name string, - prevInt uint64, - prevPercent float32, - currInt uint64, - currPercent float32, -) string { - diff := int64(currInt) - int64(prevInt) - return fmt.Sprintf( - "|%s|%d (%.2f%%)|%d (%.2f%%)|%d (%.2f%%)%s|", - name, - prevInt, - prevPercent, - currInt, - currPercent, - diff, - currPercent-prevPercent, - intArrow(diff), - ) -} diff --git a/handlers/task.go b/handlers/task.go deleted file mode 100644 index d8cdc2f..0000000 --- a/handlers/task.go +++ /dev/null @@ -1,22 +0,0 @@ -package handlers - -import ( - "context" - "github.com/palantir/go-githubapp/githubapp" - "github.com/rs/zerolog" - "time" -) - -func backgroundTask(taskCtx context.Context, eventType, deliveryID string, run func(context.Context) error) { - logger := zerolog.Ctx(taskCtx).With(). - Str(githubapp.LogKeyDeliveryID, deliveryID). - Str(githubapp.LogKeyEventType, eventType). - Logger() - ctx, cancel := context.WithDeadline(taskCtx, time.Now().Add(time.Minute)) - defer cancel() - ctx = logger.WithContext(ctx) - err := run(ctx) - if err != nil { - logger.Error().Err(err).Msg("Background task failed") - } -} diff --git a/handlers/workflow_run.go b/handlers/workflow_run.go deleted file mode 100644 index 69b9f04..0000000 --- a/handlers/workflow_run.go +++ /dev/null @@ -1,109 +0,0 @@ -package handlers - -import ( - "context" - "encoding/json" - "github.com/encounter/decompal/config" - "github.com/encounter/decompal/objdiff" - "github.com/google/go-github/v63/github" - "github.com/palantir/go-githubapp/githubapp" - "github.com/pkg/errors" -) - -type workflowRunHandler struct { - githubapp.ClientCreator - config *config.AppConfig - taskCtx context.Context -} - -func NewWorkflowRunHandler( - cc githubapp.ClientCreator, - config *config.AppConfig, - taskCtx context.Context, -) githubapp.EventHandler { - return &workflowRunHandler{ - ClientCreator: cc, - config: config, - taskCtx: taskCtx, - } -} - -func (h *workflowRunHandler) Handles() []string { - return []string{"workflow_run"} -} - -func (h *workflowRunHandler) Handle(ctx context.Context, eventType, deliveryID string, payload []byte) error { - event := &github.WorkflowRunEvent{} - if err := json.Unmarshal(payload, event); err != nil { - return errors.Wrap(err, "failed to parse workflow run event payload") - } - - installationID := githubapp.GetInstallationIDFromEvent(event) - ctx, logger := githubapp.PrepareRepoContext(ctx, installationID, event.GetRepo()) - status := event.GetWorkflowRun().GetStatus() - if status != "completed" { - logger.Debug(). - Str("status", status). - Msg("Workflow run event is not completed") - return nil - } - - client, err := h.NewInstallationClient(installationID) - if err != nil { - return err - } - - go backgroundTask(h.taskCtx, eventType, deliveryID, func(ctx context.Context) error { - repo := event.GetRepo() - // Re-prepare logger instead of using the one from the request context - ctx, logger := githubapp.PrepareRepoContext(ctx, installationID, repo) - - // Fetch report files for the current workflow run - repoOwner := repo.GetOwner().GetLogin() - repoName := repo.GetName() - run := event.GetWorkflowRun() - runId := run.GetID() - sha := run.GetHeadSHA() - files, err := objdiff.FetchReportFiles( - ctx, - logger, - client, - repoOwner, - repoName, - sha, - runId, - ) - if err != nil { - return err - } - if len(files) == 0 { - logger.Info().Msg("No report files found") - return nil - } - - // Process all pull requests associated with the workflow run - prs := event.GetWorkflowRun().PullRequests - if prs != nil { - for _, pr := range prs { - err = processPR( - ctx, - h.config, - installationID, - pr, - sha, - client, - repo, - run.GetWorkflowID(), - files, - ) - if err != nil { - return err - } - } - } - - return nil - }) - - return nil -} diff --git a/js/graph.ts b/js/graph.ts new file mode 100644 index 0000000..4170734 --- /dev/null +++ b/js/graph.ts @@ -0,0 +1,138 @@ +type Unit = { + name: string; + fuzzy_match_percent: number; + color: string; + x: number; + y: number; + w: number; + h: number; +}; + +const unitBounds = (unit: Unit, width: number, height: number) => { + return { + x: (unit.x / 100) * width, + y: (unit.y / 100) * height, + w: (unit.w / 100) * width, + h: (unit.h / 100) * height, + }; +} + +const TOOLTIP_PADDING = 10; +const TOOLTIP_MARGIN = 10; + +const drawTooltip = (ctx: CanvasRenderingContext2D, unit: Unit, width: number, height: number) => { + const {x, y, w, h} = unitBounds(unit, width, height); + const text = `${unit.name} • ${unit.fuzzy_match_percent.toFixed(2)}%`; + const m = ctx.measureText(text); + const hw = m.actualBoundingBoxRight + m.actualBoundingBoxLeft + TOOLTIP_PADDING * 2; + const bh = m.actualBoundingBoxAscent + m.actualBoundingBoxDescent + TOOLTIP_PADDING * 2; + let bx = x + (w - hw) / 2 + TOOLTIP_PADDING; + let by = y - TOOLTIP_MARGIN; + if (bx < 0) { + bx = 0; + } + if (bx + hw > width) { + bx = width - hw; + } + if (by - bh < 0) { + by = y + h + bh + TOOLTIP_MARGIN; + } + ctx.fillStyle = "rgba(255, 255, 255, 0.8)"; + ctx.fillRect(bx, by - m.actualBoundingBoxAscent - TOOLTIP_PADDING * 2, hw, bh); + ctx.fillStyle = "#000"; + ctx.fillText(text, bx + TOOLTIP_PADDING, by - TOOLTIP_PADDING); +}; + +let hovered = null; +let dirty = false; + +const draw = (canvas: HTMLCanvasElement, units: Unit[]) => { + if (!canvas.getContext) { + return; + } + const ratio = window.devicePixelRatio; + const {width, height} = canvas.getBoundingClientRect(); + const renderWidth = width * ratio; + const renderHeight = height * ratio; + if (!dirty && canvas.width === renderWidth && canvas.height === renderHeight) { + // Nothing changed + return; + } + dirty = false; + if (canvas.width !== renderWidth || canvas.height !== renderHeight) { + canvas.width = renderWidth; + canvas.height = renderHeight; + } + const style = getComputedStyle(canvas); + const fontWeight = style.getPropertyValue('--font-weight') || 'normal'; + const fontSize = style.getPropertyValue('--font-size') || '16px'; + const fontFamily = style.getPropertyValue('--font-family') || 'sans-serif'; + const ctx = canvas.getContext("2d"); + ctx.setTransform(ratio, 0, 0, ratio, 0, 0); + ctx.clearRect(0, 0, width, height); + ctx.font = `${fontWeight} ${fontSize} ${fontFamily}`; + ctx.lineWidth = 1; + ctx.strokeStyle = "#000"; + for (const unit of units) { + const {x, y, w, h} = unitBounds(unit, width, height); + ctx.fillStyle = unit.color; + ctx.fillRect(x, y, w, h); + ctx.strokeRect(x, y, w, h); + } + if (hovered) { + const {x, y, w, h} = unitBounds(hovered, width, height); + ctx.lineWidth = 2; + ctx.strokeStyle = "#fff"; + ctx.strokeRect(x, y, w, h); + drawTooltip(ctx, hovered, width, height); + } +}; + +const drawGraph = (id: string, units: Unit[]) => { + const canvas = document.getElementById(id) as HTMLCanvasElement; + if (!canvas) { + return; + } + const queueDraw = () => requestAnimationFrame(() => draw(canvas, units)); + const resizeObserver = new ResizeObserver(queueDraw); + resizeObserver.observe(canvas); + const handleHover = ({clientX, clientY}: { clientX: number, clientY: number }) => { + const {width, height, left, top} = canvas.getBoundingClientRect(); + const mx = clientX - left; + const my = clientY - top; + const prev = hovered; + hovered = null; + for (const unit of units) { + const {x, y, w, h} = unitBounds(unit, width, height); + if (mx >= x && mx <= x + w && my >= y && my <= y + h) { + hovered = unit; + break; + } + } + if (prev === hovered) { + return; + } + dirty = true; + queueDraw(); + } + const handleLeave = () => { + if (!hovered) { + return; + } + hovered = null; + dirty = true; + queueDraw(); + }; + canvas.addEventListener("mousemove", handleHover); + canvas.addEventListener("mouseleave", handleLeave); + canvas.addEventListener("touchmove", (e) => handleHover(e.touches[0])); + canvas.addEventListener("touchend", handleLeave); + draw(canvas, units); +}; + +// noinspection JSUnusedGlobalSymbols +interface Window { + drawGraph: (id: string, units: Unit[]) => void; +} + +window.drawGraph = drawGraph; diff --git a/migrations/1_init.sql b/migrations/1_init.sql new file mode 100644 index 0000000..8138dbd --- /dev/null +++ b/migrations/1_init.sql @@ -0,0 +1,41 @@ +CREATE TABLE projects +( + id INTEGER PRIMARY KEY, -- GitHub repository ID + owner TEXT NOT NULL, -- GitHub repository owner + name TEXT NOT NULL, -- GitHub repository name + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL +); + +CREATE TABLE reports +( + id INTEGER PRIMARY KEY, + project_id INTEGER NOT NULL, -- GitHub repository ID + version TEXT NOT NULL, -- Game ID + git_commit TEXT NOT NULL, -- Git commit SHA + timestamp DATETIME NOT NULL, -- Git commit timestamp + data BLOB NOT NULL, -- Serialized report data + FOREIGN KEY (project_id) REFERENCES projects (id) +); + +CREATE UNIQUE INDEX reports_project_id_version_git_commit_index ON reports (project_id, version, git_commit); +CREATE INDEX reports_timestamp_index ON reports (timestamp); + +CREATE TABLE report_report_units +( + report_id INTEGER NOT NULL, + report_unit_id BLOB NOT NULL, + unit_index INTEGER NOT NULL, -- Index of the report unit in the report + PRIMARY KEY (report_id, report_unit_id), + FOREIGN KEY (report_id) REFERENCES reports (id), + FOREIGN KEY (report_unit_id) REFERENCES report_units (id) +); + +CREATE INDEX report_report_units_report_id_index ON report_report_units (report_id); +CREATE INDEX report_report_units_report_unit_id_index ON report_report_units (report_unit_id); + +CREATE TABLE report_units +( + id BLOB PRIMARY KEY, -- BLAKE3 hash of the report unit data (256 bits) + data BLOB NOT NULL -- Serialized report unit data +); diff --git a/migrations/2_project_index.sql b/migrations/2_project_index.sql new file mode 100644 index 0000000..3f1c4d3 --- /dev/null +++ b/migrations/2_project_index.sql @@ -0,0 +1 @@ +CREATE INDEX projects_owner_name_index ON projects (owner COLLATE NOCASE, name COLLATE NOCASE); diff --git a/migrations/3_nocase.sql b/migrations/3_nocase.sql new file mode 100644 index 0000000..49ae019 --- /dev/null +++ b/migrations/3_nocase.sql @@ -0,0 +1,7 @@ +DROP INDEX reports_project_id_version_git_commit_index; +CREATE UNIQUE INDEX reports_project_id_version_git_commit_index ON reports (project_id, version COLLATE NOCASE, git_commit COLLATE NOCASE); + +DROP INDEX reports_timestamp_index; +CREATE INDEX reports_project_id_timestamp_index ON reports (project_id, timestamp); + +CREATE INDEX project_owner_name_index ON projects (owner COLLATE NOCASE, name COLLATE NOCASE); diff --git a/migrations/4_project_repo.sql b/migrations/4_project_repo.sql new file mode 100644 index 0000000..cada0fa --- /dev/null +++ b/migrations/4_project_repo.sql @@ -0,0 +1 @@ +ALTER TABLE projects RENAME COLUMN name TO repo; diff --git a/migrations/5_project_name.sql b/migrations/5_project_name.sql new file mode 100644 index 0000000..4dcdf3f --- /dev/null +++ b/migrations/5_project_name.sql @@ -0,0 +1 @@ +ALTER TABLE projects ADD COLUMN name TEXT; diff --git a/migrations/6_project_short_name.sql b/migrations/6_project_short_name.sql new file mode 100644 index 0000000..b505648 --- /dev/null +++ b/migrations/6_project_short_name.sql @@ -0,0 +1 @@ +ALTER TABLE projects ADD COLUMN short_name TEXT; diff --git a/migrations/7_project_default_version.sql b/migrations/7_project_default_version.sql new file mode 100644 index 0000000..df48db7 --- /dev/null +++ b/migrations/7_project_default_version.sql @@ -0,0 +1 @@ +ALTER TABLE projects ADD COLUMN default_version TEXT; diff --git a/migrations/8_report_units_name.sql b/migrations/8_report_units_name.sql new file mode 100644 index 0000000..3920207 --- /dev/null +++ b/migrations/8_report_units_name.sql @@ -0,0 +1,2 @@ +ALTER TABLE report_units ADD COLUMN name TEXT; +CREATE INDEX report_units_name_idx ON report_units (name); diff --git a/objdiff/changes.go b/objdiff/changes.go deleted file mode 100644 index 4fa662e..0000000 --- a/objdiff/changes.go +++ /dev/null @@ -1,65 +0,0 @@ -package objdiff - -import ( - "github.com/encounter/decompal/config" - "github.com/pkg/errors" - "github.com/rs/zerolog" - "google.golang.org/protobuf/proto" - "os/exec" -) - -func GenerateChanges( - config *config.AppConfig, - logger zerolog.Logger, - prev *ReportFile, - curr *ReportFile, -) (*Changes, error) { - if config.ObjdiffPath == "" { - return nil, errors.New("objdiff_path not set") - } - - data, err := proto.Marshal(&ChangesInput{ - From: prev.Report, - To: curr.Report, - }) - if err != nil { - return nil, errors.Wrap(err, "failed to encode changes input") - } - - // Run objdiff with proto input and output - // The `--` is to delimit the end of flags and the start of positional arguments - // Otherwise the argument parser gets confused - cmd := exec.Command( - config.ObjdiffPath, - "report", - "changes", - "-f", - "proto", - "--", - "-", - "-", - ) - stdin, err := cmd.StdinPipe() - if err != nil { - return nil, errors.Wrap(err, "failed to open stdin pipe") - } - go func() { - defer stdin.Close() - if _, err := stdin.Write(data); err != nil { - logger.Err(err).Msg("failed to write data to objdiff") - } - }() - // It shouldn't have any stderr output when successful, but we want to capture errors - // May need to change this if objdiff changes - output, err := cmd.CombinedOutput() - if err != nil { - return nil, errors.Wrapf(err, "failed to generate changes: %s", string(output)) - } - - changes := &Changes{} - err = proto.Unmarshal(output, changes) - if err != nil { - return nil, errors.Wrap(err, "failed to decode changes file") - } - return changes, nil -} diff --git a/objdiff/legacy.go b/objdiff/legacy.go deleted file mode 100644 index fd39015..0000000 --- a/objdiff/legacy.go +++ /dev/null @@ -1,121 +0,0 @@ -package objdiff - -import ( - "strconv" - "strings" -) - -// Older JSON report types -type legacyReport struct { - FuzzyMatchPercent float32 `json:"fuzzy_match_percent"` - TotalCode uint64 `json:"total_code"` - MatchedCode uint64 `json:"matched_code"` - MatchedCodePercent float32 `json:"matched_code_percent"` - TotalData uint64 `json:"total_data"` - MatchedData uint64 `json:"matched_data"` - MatchedDataPercent float32 `json:"matched_data_percent"` - TotalFunctions uint32 `json:"total_functions"` - MatchedFunctions uint32 `json:"matched_functions"` - MatchedFunctionsPercent float32 `json:"matched_functions_percent"` - Units []legacyReportUnit `json:"units"` -} - -type legacyReportUnit struct { - Name string `json:"name"` - FuzzyMatchPercent float32 `json:"fuzzy_match_percent"` - TotalCode uint64 `json:"total_code"` - MatchedCode uint64 `json:"matched_code"` - TotalData uint64 `json:"total_data"` - MatchedData uint64 `json:"matched_data"` - TotalFunctions uint32 `json:"total_functions"` - MatchedFunctions uint32 `json:"matched_functions"` - Complete *bool `json:"complete,omitempty"` - ModuleName *string `json:"module_name,omitempty"` - ModuleID *uint32 `json:"module_id,omitempty"` - Sections []legacyReportItem `json:"sections"` - Functions []legacyReportItem `json:"functions"` -} - -type legacyReportItem struct { - Name string `json:"name"` - DemangledName *string `json:"demangled_name,omitempty"` - Address *string `json:"address,omitempty"` // hex string - Size uint64 `json:"size"` - FuzzyMatchPercent float32 `json:"fuzzy_match_percent"` -} - -func (r *legacyReport) convert() *Report { - report := &Report{ - Measures: &Measures{ - FuzzyMatchPercent: r.FuzzyMatchPercent, - TotalCode: r.TotalCode, - MatchedCode: r.MatchedCode, - MatchedCodePercent: r.MatchedCodePercent, - TotalData: r.TotalData, - MatchedData: r.MatchedData, - MatchedDataPercent: r.MatchedDataPercent, - TotalFunctions: r.TotalFunctions, - MatchedFunctions: r.MatchedFunctions, - MatchedFunctionsPercent: r.MatchedFunctionsPercent, - }, - Units: make([]*ReportUnit, 0, len(r.Units)), - } - for _, unit := range r.Units { - report.Units = append(report.Units, unit.convert()) - } - return report -} - -func (u *legacyReportUnit) convert() *ReportUnit { - measures := &Measures{ - FuzzyMatchPercent: u.FuzzyMatchPercent, - TotalCode: u.TotalCode, - MatchedCode: u.MatchedCode, - TotalData: u.TotalData, - MatchedData: u.MatchedData, - TotalFunctions: u.TotalFunctions, - MatchedFunctions: u.MatchedFunctions, - } - measures.CalcMatchedPercent() - unit := &ReportUnit{ - Name: u.Name, - Measures: measures, - Sections: make([]*ReportItem, 0, len(u.Sections)), - Functions: make([]*ReportItem, 0, len(u.Functions)), - Metadata: &ReportUnitMetadata{ - Complete: u.Complete, - ModuleName: u.ModuleName, - ModuleId: u.ModuleID, - }, - } - for _, section := range u.Sections { - unit.Sections = append(unit.Sections, section.convert()) - } - for _, function := range u.Functions { - unit.Functions = append(unit.Functions, function.convert()) - } - return unit -} - -func (i *legacyReportItem) convert() *ReportItem { - var address *uint64 - if i.Address != nil { - addressStr := *i.Address - if strings.HasPrefix(addressStr, "0x") { - v, _ := strconv.ParseUint(addressStr[2:], 16, 64) - address = &v - } else { - v, _ := strconv.ParseUint(addressStr, 10, 64) - address = &v - } - } - return &ReportItem{ - Name: i.Name, - Size: i.Size, - FuzzyMatchPercent: i.FuzzyMatchPercent, - Metadata: &ReportItemMetadata{ - DemangledName: i.DemangledName, - VirtualAddress: address, - }, - } -} diff --git a/objdiff/report.go b/objdiff/report.go deleted file mode 100644 index 98ca855..0000000 --- a/objdiff/report.go +++ /dev/null @@ -1,168 +0,0 @@ -package objdiff - -import ( - "context" - "encoding/json" - "github.com/encounter/decompal/zipstream" - "github.com/google/go-github/v63/github" - "github.com/pkg/errors" - "github.com/rs/zerolog" - "google.golang.org/protobuf/encoding/protojson" - "google.golang.org/protobuf/proto" - "io" - "net/http" - "regexp" - "sort" - "strings" -) - -type ReportFile struct { - Version string - Sha string - Report *Report -} - -var artifactNameRegex = regexp.MustCompile(`^(?P[A-z0-9_\-]+)[_-]report(?:[_-].*)?$`) - -func FetchReportFiles( - ctx context.Context, - logger zerolog.Logger, - client *github.Client, - repoOwner, repoName, sha string, - runId int64, -) ([]ReportFile, error) { - logger = logger.With(). - Str("commit_sha", sha). - Int64("workflow_run_id", runId). - Logger() - - artifacts, _, err := client.Actions.ListWorkflowRunArtifacts(ctx, repoOwner, repoName, runId, nil) - if err != nil { - logger.Error(). - Err(err). - Msg("Failed to list workflow run artifacts") - return nil, errors.Wrap(err, "failed to list workflow run artifacts") - } - - files := make([]ReportFile, 0) - for _, artifact := range artifacts.Artifacts { - logger := logger.With(). - Str("artifact_name", artifact.GetName()). - Int64("artifact_id", artifact.GetID()). - Logger() - - matches := artifactNameRegex.FindStringSubmatch(artifact.GetName()) - if matches == nil { - //logger.Debug().Msg("Skipping artifact") - continue - } - version := matches[artifactNameRegex.SubexpIndex("version")] - - artifactUrl, _, err := client.Actions.DownloadArtifact(ctx, repoOwner, repoName, artifact.GetID(), 3) - if err != nil { - return nil, errors.Wrap(err, "failed to get artifact download url") - } - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, artifactUrl.String(), nil) - if err != nil { - return nil, errors.Wrap(err, "failed to create download request") - } - - req.Header.Set("User-Agent", client.UserAgent) - resp, err := http.DefaultClient.Do(req) - if err != nil { - return nil, errors.Wrap(err, "failed to download artifact") - } - - report, err := findReportFile(logger, resp.Body) - _ = resp.Body.Close() - if err != nil { - return nil, err - } - if report != nil { - files = append(files, ReportFile{ - Version: version, - Sha: sha, - Report: report, - }) - } - } - - // Sort files by version - sort.Slice(files, func(i, j int) bool { - return files[i].Version < files[j].Version - }) - return files, nil -} - -// findReportFile reads the zip stream and writes the report file to the output path -// Returns true if the report file was found and written -func findReportFile(logger zerolog.Logger, r io.Reader) (*Report, error) { - zr := zipstream.NewReader(r) - for { - entry, err := zr.Next() - if err != nil { - if err == io.EOF { - break - } - return nil, errors.Wrap(err, "failed to get next entry") - } - - data, err := io.ReadAll(entry) - if err != nil { - return nil, errors.Wrap(err, "failed to read report file") - } - if strings.HasSuffix(entry.Name, "report.json") { - report := &Report{} - err := parseJson(data, report) - if err != nil { - return nil, errors.Wrap(err, "failed to read report file") - } - logger.Info(). - Str("filename", entry.Name). - Msg("Read report file") - return report, nil - } else if strings.HasSuffix(entry.Name, "report.binpb") || - strings.HasSuffix(entry.Name, "report.pb") { - report := &Report{} - err = proto.Unmarshal(data, report) - if err != nil { - return nil, err - } - logger.Info(). - Str("filename", entry.Name). - Msg("Read report file") - return report, nil - } - } - return nil, nil -} - -func parseJson(data []byte, v *Report) error { - err := protojson.Unmarshal(data, v) - if err != nil { - // Try to parse as legacy report - legacy := &legacyReport{} - if other := json.Unmarshal(data, legacy); other != nil { - // Return the original error - return err - } - *v = *legacy.convert() - } - return nil -} - -func (m *Measures) CalcMatchedPercent() { - m.MatchedCodePercent = 100 - if m.TotalCode != 0 { - m.MatchedCodePercent = float32(m.MatchedCode) / float32(m.TotalCode) * 100 - } - m.MatchedDataPercent = 100 - if m.TotalData != 0 { - m.MatchedDataPercent = float32(m.MatchedData) / float32(m.TotalData) * 100 - } - m.MatchedFunctionsPercent = 100 - if m.TotalFunctions != 0 { - m.MatchedFunctionsPercent = float32(m.MatchedFunctions) / float32(m.TotalFunctions) * 100 - } -} diff --git a/objdiff/report.pb.go b/objdiff/report.pb.go deleted file mode 100644 index 32b5e2d..0000000 --- a/objdiff/report.pb.go +++ /dev/null @@ -1,1229 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.34.2 -// protoc v5.27.3 -// source: report.proto - -package objdiff - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -// Progress info for a report or unit -type Measures struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Overall match percent, including partially matched functions and data - FuzzyMatchPercent float32 `protobuf:"fixed32,1,opt,name=fuzzy_match_percent,json=fuzzyMatchPercent,proto3" json:"fuzzy_match_percent,omitempty"` - // Total size of code in bytes - TotalCode uint64 `protobuf:"varint,2,opt,name=total_code,json=totalCode,proto3" json:"total_code,omitempty"` - // Fully matched code size in bytes - MatchedCode uint64 `protobuf:"varint,3,opt,name=matched_code,json=matchedCode,proto3" json:"matched_code,omitempty"` - // Fully matched code percent - MatchedCodePercent float32 `protobuf:"fixed32,4,opt,name=matched_code_percent,json=matchedCodePercent,proto3" json:"matched_code_percent,omitempty"` - // Total size of data in bytes - TotalData uint64 `protobuf:"varint,5,opt,name=total_data,json=totalData,proto3" json:"total_data,omitempty"` - // Fully matched data size in bytes - MatchedData uint64 `protobuf:"varint,6,opt,name=matched_data,json=matchedData,proto3" json:"matched_data,omitempty"` - // Fully matched data percent - MatchedDataPercent float32 `protobuf:"fixed32,7,opt,name=matched_data_percent,json=matchedDataPercent,proto3" json:"matched_data_percent,omitempty"` - // Total number of functions - TotalFunctions uint32 `protobuf:"varint,8,opt,name=total_functions,json=totalFunctions,proto3" json:"total_functions,omitempty"` - // Fully matched functions - MatchedFunctions uint32 `protobuf:"varint,9,opt,name=matched_functions,json=matchedFunctions,proto3" json:"matched_functions,omitempty"` - // Fully matched functions percent - MatchedFunctionsPercent float32 `protobuf:"fixed32,10,opt,name=matched_functions_percent,json=matchedFunctionsPercent,proto3" json:"matched_functions_percent,omitempty"` -} - -func (x *Measures) Reset() { - *x = Measures{} - if protoimpl.UnsafeEnabled { - mi := &file_report_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Measures) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Measures) ProtoMessage() {} - -func (x *Measures) ProtoReflect() protoreflect.Message { - mi := &file_report_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Measures.ProtoReflect.Descriptor instead. -func (*Measures) Descriptor() ([]byte, []int) { - return file_report_proto_rawDescGZIP(), []int{0} -} - -func (x *Measures) GetFuzzyMatchPercent() float32 { - if x != nil { - return x.FuzzyMatchPercent - } - return 0 -} - -func (x *Measures) GetTotalCode() uint64 { - if x != nil { - return x.TotalCode - } - return 0 -} - -func (x *Measures) GetMatchedCode() uint64 { - if x != nil { - return x.MatchedCode - } - return 0 -} - -func (x *Measures) GetMatchedCodePercent() float32 { - if x != nil { - return x.MatchedCodePercent - } - return 0 -} - -func (x *Measures) GetTotalData() uint64 { - if x != nil { - return x.TotalData - } - return 0 -} - -func (x *Measures) GetMatchedData() uint64 { - if x != nil { - return x.MatchedData - } - return 0 -} - -func (x *Measures) GetMatchedDataPercent() float32 { - if x != nil { - return x.MatchedDataPercent - } - return 0 -} - -func (x *Measures) GetTotalFunctions() uint32 { - if x != nil { - return x.TotalFunctions - } - return 0 -} - -func (x *Measures) GetMatchedFunctions() uint32 { - if x != nil { - return x.MatchedFunctions - } - return 0 -} - -func (x *Measures) GetMatchedFunctionsPercent() float32 { - if x != nil { - return x.MatchedFunctionsPercent - } - return 0 -} - -// Project progress report -type Report struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Overall progress info - Measures *Measures `protobuf:"bytes,1,opt,name=measures,proto3" json:"measures,omitempty"` - // Units within this report - Units []*ReportUnit `protobuf:"bytes,2,rep,name=units,proto3" json:"units,omitempty"` -} - -func (x *Report) Reset() { - *x = Report{} - if protoimpl.UnsafeEnabled { - mi := &file_report_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Report) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Report) ProtoMessage() {} - -func (x *Report) ProtoReflect() protoreflect.Message { - mi := &file_report_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Report.ProtoReflect.Descriptor instead. -func (*Report) Descriptor() ([]byte, []int) { - return file_report_proto_rawDescGZIP(), []int{1} -} - -func (x *Report) GetMeasures() *Measures { - if x != nil { - return x.Measures - } - return nil -} - -func (x *Report) GetUnits() []*ReportUnit { - if x != nil { - return x.Units - } - return nil -} - -// A unit of the report (usually a translation unit) -type ReportUnit struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // The name of the unit - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - // Progress info for this unit - Measures *Measures `protobuf:"bytes,2,opt,name=measures,proto3" json:"measures,omitempty"` - // Sections within this unit - Sections []*ReportItem `protobuf:"bytes,3,rep,name=sections,proto3" json:"sections,omitempty"` - // Functions within this unit - Functions []*ReportItem `protobuf:"bytes,4,rep,name=functions,proto3" json:"functions,omitempty"` - // Extra metadata for this unit - Metadata *ReportUnitMetadata `protobuf:"bytes,5,opt,name=metadata,proto3,oneof" json:"metadata,omitempty"` -} - -func (x *ReportUnit) Reset() { - *x = ReportUnit{} - if protoimpl.UnsafeEnabled { - mi := &file_report_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ReportUnit) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ReportUnit) ProtoMessage() {} - -func (x *ReportUnit) ProtoReflect() protoreflect.Message { - mi := &file_report_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ReportUnit.ProtoReflect.Descriptor instead. -func (*ReportUnit) Descriptor() ([]byte, []int) { - return file_report_proto_rawDescGZIP(), []int{2} -} - -func (x *ReportUnit) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *ReportUnit) GetMeasures() *Measures { - if x != nil { - return x.Measures - } - return nil -} - -func (x *ReportUnit) GetSections() []*ReportItem { - if x != nil { - return x.Sections - } - return nil -} - -func (x *ReportUnit) GetFunctions() []*ReportItem { - if x != nil { - return x.Functions - } - return nil -} - -func (x *ReportUnit) GetMetadata() *ReportUnitMetadata { - if x != nil { - return x.Metadata - } - return nil -} - -// Extra metadata for a unit -type ReportUnitMetadata struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Whether this unit is marked as complete (or "linked") - Complete *bool `protobuf:"varint,1,opt,name=complete,proto3,oneof" json:"complete,omitempty"` - // The name of the module this unit belongs to - ModuleName *string `protobuf:"bytes,2,opt,name=module_name,json=moduleName,proto3,oneof" json:"module_name,omitempty"` - // The ID of the module this unit belongs to - ModuleId *uint32 `protobuf:"varint,3,opt,name=module_id,json=moduleId,proto3,oneof" json:"module_id,omitempty"` - // The path to the source file of this unit - SourcePath *string `protobuf:"bytes,4,opt,name=source_path,json=sourcePath,proto3,oneof" json:"source_path,omitempty"` -} - -func (x *ReportUnitMetadata) Reset() { - *x = ReportUnitMetadata{} - if protoimpl.UnsafeEnabled { - mi := &file_report_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ReportUnitMetadata) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ReportUnitMetadata) ProtoMessage() {} - -func (x *ReportUnitMetadata) ProtoReflect() protoreflect.Message { - mi := &file_report_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ReportUnitMetadata.ProtoReflect.Descriptor instead. -func (*ReportUnitMetadata) Descriptor() ([]byte, []int) { - return file_report_proto_rawDescGZIP(), []int{3} -} - -func (x *ReportUnitMetadata) GetComplete() bool { - if x != nil && x.Complete != nil { - return *x.Complete - } - return false -} - -func (x *ReportUnitMetadata) GetModuleName() string { - if x != nil && x.ModuleName != nil { - return *x.ModuleName - } - return "" -} - -func (x *ReportUnitMetadata) GetModuleId() uint32 { - if x != nil && x.ModuleId != nil { - return *x.ModuleId - } - return 0 -} - -func (x *ReportUnitMetadata) GetSourcePath() string { - if x != nil && x.SourcePath != nil { - return *x.SourcePath - } - return "" -} - -// A section or function within a unit -type ReportItem struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // The name of the item - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - // The size of the item in bytes - Size uint64 `protobuf:"varint,2,opt,name=size,proto3" json:"size,omitempty"` - // The overall match percent for this item - FuzzyMatchPercent float32 `protobuf:"fixed32,3,opt,name=fuzzy_match_percent,json=fuzzyMatchPercent,proto3" json:"fuzzy_match_percent,omitempty"` - // Extra metadata for this item - Metadata *ReportItemMetadata `protobuf:"bytes,4,opt,name=metadata,proto3,oneof" json:"metadata,omitempty"` -} - -func (x *ReportItem) Reset() { - *x = ReportItem{} - if protoimpl.UnsafeEnabled { - mi := &file_report_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ReportItem) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ReportItem) ProtoMessage() {} - -func (x *ReportItem) ProtoReflect() protoreflect.Message { - mi := &file_report_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ReportItem.ProtoReflect.Descriptor instead. -func (*ReportItem) Descriptor() ([]byte, []int) { - return file_report_proto_rawDescGZIP(), []int{4} -} - -func (x *ReportItem) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *ReportItem) GetSize() uint64 { - if x != nil { - return x.Size - } - return 0 -} - -func (x *ReportItem) GetFuzzyMatchPercent() float32 { - if x != nil { - return x.FuzzyMatchPercent - } - return 0 -} - -func (x *ReportItem) GetMetadata() *ReportItemMetadata { - if x != nil { - return x.Metadata - } - return nil -} - -// Extra metadata for an item -type ReportItemMetadata struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // The demangled name of the function - DemangledName *string `protobuf:"bytes,1,opt,name=demangled_name,json=demangledName,proto3,oneof" json:"demangled_name,omitempty"` - // The virtual address of the function or section - VirtualAddress *uint64 `protobuf:"varint,2,opt,name=virtual_address,json=virtualAddress,proto3,oneof" json:"virtual_address,omitempty"` -} - -func (x *ReportItemMetadata) Reset() { - *x = ReportItemMetadata{} - if protoimpl.UnsafeEnabled { - mi := &file_report_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ReportItemMetadata) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ReportItemMetadata) ProtoMessage() {} - -func (x *ReportItemMetadata) ProtoReflect() protoreflect.Message { - mi := &file_report_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ReportItemMetadata.ProtoReflect.Descriptor instead. -func (*ReportItemMetadata) Descriptor() ([]byte, []int) { - return file_report_proto_rawDescGZIP(), []int{5} -} - -func (x *ReportItemMetadata) GetDemangledName() string { - if x != nil && x.DemangledName != nil { - return *x.DemangledName - } - return "" -} - -func (x *ReportItemMetadata) GetVirtualAddress() uint64 { - if x != nil && x.VirtualAddress != nil { - return *x.VirtualAddress - } - return 0 -} - -// A pair of reports to compare and generate changes -type ChangesInput struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // The previous report - From *Report `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` - // The current report - To *Report `protobuf:"bytes,2,opt,name=to,proto3" json:"to,omitempty"` -} - -func (x *ChangesInput) Reset() { - *x = ChangesInput{} - if protoimpl.UnsafeEnabled { - mi := &file_report_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ChangesInput) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ChangesInput) ProtoMessage() {} - -func (x *ChangesInput) ProtoReflect() protoreflect.Message { - mi := &file_report_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ChangesInput.ProtoReflect.Descriptor instead. -func (*ChangesInput) Descriptor() ([]byte, []int) { - return file_report_proto_rawDescGZIP(), []int{6} -} - -func (x *ChangesInput) GetFrom() *Report { - if x != nil { - return x.From - } - return nil -} - -func (x *ChangesInput) GetTo() *Report { - if x != nil { - return x.To - } - return nil -} - -// Changes between two reports -type Changes struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // The progress info for the previous report - From *Measures `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` - // The progress info for the current report - To *Measures `protobuf:"bytes,2,opt,name=to,proto3" json:"to,omitempty"` - // Units that changed - Units []*ChangeUnit `protobuf:"bytes,3,rep,name=units,proto3" json:"units,omitempty"` -} - -func (x *Changes) Reset() { - *x = Changes{} - if protoimpl.UnsafeEnabled { - mi := &file_report_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Changes) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Changes) ProtoMessage() {} - -func (x *Changes) ProtoReflect() protoreflect.Message { - mi := &file_report_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Changes.ProtoReflect.Descriptor instead. -func (*Changes) Descriptor() ([]byte, []int) { - return file_report_proto_rawDescGZIP(), []int{7} -} - -func (x *Changes) GetFrom() *Measures { - if x != nil { - return x.From - } - return nil -} - -func (x *Changes) GetTo() *Measures { - if x != nil { - return x.To - } - return nil -} - -func (x *Changes) GetUnits() []*ChangeUnit { - if x != nil { - return x.Units - } - return nil -} - -// A changed unit -type ChangeUnit struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // The name of the unit - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - // The previous progress info (omitted if new) - From *Measures `protobuf:"bytes,2,opt,name=from,proto3,oneof" json:"from,omitempty"` - // The current progress info (omitted if removed) - To *Measures `protobuf:"bytes,3,opt,name=to,proto3,oneof" json:"to,omitempty"` - // Sections that changed - Sections []*ChangeItem `protobuf:"bytes,4,rep,name=sections,proto3" json:"sections,omitempty"` - // Functions that changed - Functions []*ChangeItem `protobuf:"bytes,5,rep,name=functions,proto3" json:"functions,omitempty"` - // Extra metadata for this unit - Metadata *ReportUnitMetadata `protobuf:"bytes,6,opt,name=metadata,proto3,oneof" json:"metadata,omitempty"` -} - -func (x *ChangeUnit) Reset() { - *x = ChangeUnit{} - if protoimpl.UnsafeEnabled { - mi := &file_report_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ChangeUnit) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ChangeUnit) ProtoMessage() {} - -func (x *ChangeUnit) ProtoReflect() protoreflect.Message { - mi := &file_report_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ChangeUnit.ProtoReflect.Descriptor instead. -func (*ChangeUnit) Descriptor() ([]byte, []int) { - return file_report_proto_rawDescGZIP(), []int{8} -} - -func (x *ChangeUnit) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *ChangeUnit) GetFrom() *Measures { - if x != nil { - return x.From - } - return nil -} - -func (x *ChangeUnit) GetTo() *Measures { - if x != nil { - return x.To - } - return nil -} - -func (x *ChangeUnit) GetSections() []*ChangeItem { - if x != nil { - return x.Sections - } - return nil -} - -func (x *ChangeUnit) GetFunctions() []*ChangeItem { - if x != nil { - return x.Functions - } - return nil -} - -func (x *ChangeUnit) GetMetadata() *ReportUnitMetadata { - if x != nil { - return x.Metadata - } - return nil -} - -// A changed section or function -type ChangeItem struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // The name of the item - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - // The previous progress info (omitted if new) - From *ChangeItemInfo `protobuf:"bytes,2,opt,name=from,proto3,oneof" json:"from,omitempty"` - // The current progress info (omitted if removed) - To *ChangeItemInfo `protobuf:"bytes,3,opt,name=to,proto3,oneof" json:"to,omitempty"` - // Extra metadata for this item - Metadata *ReportItemMetadata `protobuf:"bytes,4,opt,name=metadata,proto3,oneof" json:"metadata,omitempty"` -} - -func (x *ChangeItem) Reset() { - *x = ChangeItem{} - if protoimpl.UnsafeEnabled { - mi := &file_report_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ChangeItem) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ChangeItem) ProtoMessage() {} - -func (x *ChangeItem) ProtoReflect() protoreflect.Message { - mi := &file_report_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ChangeItem.ProtoReflect.Descriptor instead. -func (*ChangeItem) Descriptor() ([]byte, []int) { - return file_report_proto_rawDescGZIP(), []int{9} -} - -func (x *ChangeItem) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *ChangeItem) GetFrom() *ChangeItemInfo { - if x != nil { - return x.From - } - return nil -} - -func (x *ChangeItem) GetTo() *ChangeItemInfo { - if x != nil { - return x.To - } - return nil -} - -func (x *ChangeItem) GetMetadata() *ReportItemMetadata { - if x != nil { - return x.Metadata - } - return nil -} - -// Progress info for a section or function -type ChangeItemInfo struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // The overall match percent for this item - FuzzyMatchPercent float32 `protobuf:"fixed32,1,opt,name=fuzzy_match_percent,json=fuzzyMatchPercent,proto3" json:"fuzzy_match_percent,omitempty"` - // The size of the item in bytes - Size uint64 `protobuf:"varint,2,opt,name=size,proto3" json:"size,omitempty"` -} - -func (x *ChangeItemInfo) Reset() { - *x = ChangeItemInfo{} - if protoimpl.UnsafeEnabled { - mi := &file_report_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ChangeItemInfo) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ChangeItemInfo) ProtoMessage() {} - -func (x *ChangeItemInfo) ProtoReflect() protoreflect.Message { - mi := &file_report_proto_msgTypes[10] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ChangeItemInfo.ProtoReflect.Descriptor instead. -func (*ChangeItemInfo) Descriptor() ([]byte, []int) { - return file_report_proto_rawDescGZIP(), []int{10} -} - -func (x *ChangeItemInfo) GetFuzzyMatchPercent() float32 { - if x != nil { - return x.FuzzyMatchPercent - } - return 0 -} - -func (x *ChangeItemInfo) GetSize() uint64 { - if x != nil { - return x.Size - } - return 0 -} - -var File_report_proto protoreflect.FileDescriptor - -var file_report_proto_rawDesc = []byte{ - 0x0a, 0x0c, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e, - 0x6f, 0x62, 0x6a, 0x64, 0x69, 0x66, 0x66, 0x2e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x22, 0xb4, - 0x03, 0x0a, 0x08, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x66, - 0x75, 0x7a, 0x7a, 0x79, 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, - 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x02, 0x52, 0x11, 0x66, 0x75, 0x7a, 0x7a, 0x79, 0x4d, - 0x61, 0x74, 0x63, 0x68, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x74, - 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, - 0x74, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x0b, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x30, 0x0a, - 0x14, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x70, 0x65, - 0x72, 0x63, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x02, 0x52, 0x12, 0x6d, 0x61, 0x74, - 0x63, 0x68, 0x65, 0x64, 0x43, 0x6f, 0x64, 0x65, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x12, - 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x12, 0x21, - 0x0a, 0x0c, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x44, 0x61, 0x74, - 0x61, 0x12, 0x30, 0x0a, 0x14, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x64, 0x61, 0x74, - 0x61, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x02, 0x52, - 0x12, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x50, 0x65, 0x72, 0x63, - 0x65, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x66, 0x75, 0x6e, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x74, 0x6f, - 0x74, 0x61, 0x6c, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2b, 0x0a, 0x11, - 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, - 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3a, 0x0a, 0x19, 0x6d, 0x61, 0x74, - 0x63, 0x68, 0x65, 0x64, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x70, - 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x02, 0x52, 0x17, 0x6d, 0x61, - 0x74, 0x63, 0x68, 0x65, 0x64, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x50, 0x65, - 0x72, 0x63, 0x65, 0x6e, 0x74, 0x22, 0x70, 0x0a, 0x06, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, - 0x34, 0x0a, 0x08, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x18, 0x2e, 0x6f, 0x62, 0x6a, 0x64, 0x69, 0x66, 0x66, 0x2e, 0x72, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x2e, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x73, 0x52, 0x08, 0x6d, 0x65, 0x61, - 0x73, 0x75, 0x72, 0x65, 0x73, 0x12, 0x30, 0x0a, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x02, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6f, 0x62, 0x6a, 0x64, 0x69, 0x66, 0x66, 0x2e, 0x72, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x55, 0x6e, 0x69, 0x74, - 0x52, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x22, 0x9a, 0x02, 0x0a, 0x0a, 0x52, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x55, 0x6e, 0x69, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x34, 0x0a, 0x08, 0x6d, 0x65, - 0x61, 0x73, 0x75, 0x72, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6f, - 0x62, 0x6a, 0x64, 0x69, 0x66, 0x66, 0x2e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x4d, 0x65, - 0x61, 0x73, 0x75, 0x72, 0x65, 0x73, 0x52, 0x08, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x73, - 0x12, 0x36, 0x0a, 0x08, 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6f, 0x62, 0x6a, 0x64, 0x69, 0x66, 0x66, 0x2e, 0x72, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x08, - 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x38, 0x0a, 0x09, 0x66, 0x75, 0x6e, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6f, 0x62, - 0x6a, 0x64, 0x69, 0x66, 0x66, 0x2e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x52, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x12, 0x43, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6f, 0x62, 0x6a, 0x64, 0x69, 0x66, 0x66, 0x2e, 0x72, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x55, 0x6e, 0x69, 0x74, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x48, 0x00, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x22, 0xde, 0x01, 0x0a, 0x12, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x55, - 0x6e, 0x69, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1f, 0x0a, 0x08, 0x63, - 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, - 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x88, 0x01, 0x01, 0x12, 0x24, 0x0a, 0x0b, - 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x48, 0x01, 0x52, 0x0a, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x88, - 0x01, 0x01, 0x12, 0x20, 0x0a, 0x09, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x02, 0x52, 0x08, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x49, - 0x64, 0x88, 0x01, 0x01, 0x12, 0x24, 0x0a, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, - 0x61, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x0a, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x50, 0x61, 0x74, 0x68, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x63, - 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x6d, 0x6f, 0x64, 0x75, - 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6d, 0x6f, 0x64, 0x75, - 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x5f, 0x70, 0x61, 0x74, 0x68, 0x22, 0xb6, 0x01, 0x0a, 0x0a, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x49, 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x2e, 0x0a, 0x13, - 0x66, 0x75, 0x7a, 0x7a, 0x79, 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x70, 0x65, 0x72, 0x63, - 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x11, 0x66, 0x75, 0x7a, 0x7a, 0x79, - 0x4d, 0x61, 0x74, 0x63, 0x68, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x12, 0x43, 0x0a, 0x08, - 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, - 0x2e, 0x6f, 0x62, 0x6a, 0x64, 0x69, 0x66, 0x66, 0x2e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, - 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x48, 0x00, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x88, 0x01, - 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x95, - 0x01, 0x0a, 0x12, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x4d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x2a, 0x0a, 0x0e, 0x64, 0x65, 0x6d, 0x61, 0x6e, 0x67, 0x6c, - 0x65, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, - 0x0d, 0x64, 0x65, 0x6d, 0x61, 0x6e, 0x67, 0x6c, 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, - 0x01, 0x12, 0x2c, 0x0a, 0x0f, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x48, 0x01, 0x52, 0x0e, 0x76, 0x69, - 0x72, 0x74, 0x75, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x88, 0x01, 0x01, 0x42, - 0x11, 0x0a, 0x0f, 0x5f, 0x64, 0x65, 0x6d, 0x61, 0x6e, 0x67, 0x6c, 0x65, 0x64, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x5f, 0x61, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x62, 0x0a, 0x0c, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x73, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x2a, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6f, 0x62, 0x6a, 0x64, 0x69, 0x66, 0x66, 0x2e, 0x72, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x04, 0x66, 0x72, - 0x6f, 0x6d, 0x12, 0x26, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, - 0x2e, 0x6f, 0x62, 0x6a, 0x64, 0x69, 0x66, 0x66, 0x2e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, - 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x02, 0x74, 0x6f, 0x22, 0x93, 0x01, 0x0a, 0x07, 0x43, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6f, 0x62, 0x6a, 0x64, 0x69, 0x66, 0x66, 0x2e, 0x72, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x73, 0x52, 0x04, - 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x28, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x18, 0x2e, 0x6f, 0x62, 0x6a, 0x64, 0x69, 0x66, 0x66, 0x2e, 0x72, 0x65, 0x70, 0x6f, 0x72, - 0x74, 0x2e, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x73, 0x52, 0x02, 0x74, 0x6f, 0x12, 0x30, - 0x0a, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x6f, 0x62, 0x6a, 0x64, 0x69, 0x66, 0x66, 0x2e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x43, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x55, 0x6e, 0x69, 0x74, 0x52, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, - 0x22, 0xd6, 0x02, 0x0a, 0x0a, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x55, 0x6e, 0x69, 0x74, 0x12, - 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x12, 0x31, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x18, 0x2e, 0x6f, 0x62, 0x6a, 0x64, 0x69, 0x66, 0x66, 0x2e, 0x72, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x2e, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x73, 0x48, 0x00, 0x52, 0x04, 0x66, - 0x72, 0x6f, 0x6d, 0x88, 0x01, 0x01, 0x12, 0x2d, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6f, 0x62, 0x6a, 0x64, 0x69, 0x66, 0x66, 0x2e, 0x72, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x2e, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x73, 0x48, 0x01, 0x52, 0x02, - 0x74, 0x6f, 0x88, 0x01, 0x01, 0x12, 0x36, 0x0a, 0x08, 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6f, 0x62, 0x6a, 0x64, 0x69, 0x66, - 0x66, 0x2e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x49, - 0x74, 0x65, 0x6d, 0x52, 0x08, 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x38, 0x0a, - 0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x6f, 0x62, 0x6a, 0x64, 0x69, 0x66, 0x66, 0x2e, 0x72, 0x65, 0x70, 0x6f, 0x72, - 0x74, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x09, 0x66, 0x75, - 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x43, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6f, 0x62, 0x6a, 0x64, - 0x69, 0x66, 0x66, 0x2e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, - 0x74, 0x55, 0x6e, 0x69, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x48, 0x02, 0x52, - 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, - 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x42, 0x05, 0x0a, 0x03, 0x5f, 0x74, 0x6f, 0x42, 0x0b, 0x0a, 0x09, - 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0xf0, 0x01, 0x0a, 0x0a, 0x43, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x04, - 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6f, 0x62, 0x6a, - 0x64, 0x69, 0x66, 0x66, 0x2e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x43, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, 0x52, 0x04, 0x66, 0x72, - 0x6f, 0x6d, 0x88, 0x01, 0x01, 0x12, 0x33, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1e, 0x2e, 0x6f, 0x62, 0x6a, 0x64, 0x69, 0x66, 0x66, 0x2e, 0x72, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x66, - 0x6f, 0x48, 0x01, 0x52, 0x02, 0x74, 0x6f, 0x88, 0x01, 0x01, 0x12, 0x43, 0x0a, 0x08, 0x6d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6f, - 0x62, 0x6a, 0x64, 0x69, 0x66, 0x66, 0x2e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x52, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x48, 0x02, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x88, 0x01, 0x01, 0x42, - 0x07, 0x0a, 0x05, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x42, 0x05, 0x0a, 0x03, 0x5f, 0x74, 0x6f, 0x42, - 0x0b, 0x0a, 0x09, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x54, 0x0a, 0x0e, - 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2e, - 0x0a, 0x13, 0x66, 0x75, 0x7a, 0x7a, 0x79, 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x70, 0x65, - 0x72, 0x63, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x02, 0x52, 0x11, 0x66, 0x75, 0x7a, - 0x7a, 0x79, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x12, 0x12, - 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, - 0x7a, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_report_proto_rawDescOnce sync.Once - file_report_proto_rawDescData = file_report_proto_rawDesc -) - -func file_report_proto_rawDescGZIP() []byte { - file_report_proto_rawDescOnce.Do(func() { - file_report_proto_rawDescData = protoimpl.X.CompressGZIP(file_report_proto_rawDescData) - }) - return file_report_proto_rawDescData -} - -var file_report_proto_msgTypes = make([]protoimpl.MessageInfo, 11) -var file_report_proto_goTypes = []any{ - (*Measures)(nil), // 0: objdiff.report.Measures - (*Report)(nil), // 1: objdiff.report.Report - (*ReportUnit)(nil), // 2: objdiff.report.ReportUnit - (*ReportUnitMetadata)(nil), // 3: objdiff.report.ReportUnitMetadata - (*ReportItem)(nil), // 4: objdiff.report.ReportItem - (*ReportItemMetadata)(nil), // 5: objdiff.report.ReportItemMetadata - (*ChangesInput)(nil), // 6: objdiff.report.ChangesInput - (*Changes)(nil), // 7: objdiff.report.Changes - (*ChangeUnit)(nil), // 8: objdiff.report.ChangeUnit - (*ChangeItem)(nil), // 9: objdiff.report.ChangeItem - (*ChangeItemInfo)(nil), // 10: objdiff.report.ChangeItemInfo -} -var file_report_proto_depIdxs = []int32{ - 0, // 0: objdiff.report.Report.measures:type_name -> objdiff.report.Measures - 2, // 1: objdiff.report.Report.units:type_name -> objdiff.report.ReportUnit - 0, // 2: objdiff.report.ReportUnit.measures:type_name -> objdiff.report.Measures - 4, // 3: objdiff.report.ReportUnit.sections:type_name -> objdiff.report.ReportItem - 4, // 4: objdiff.report.ReportUnit.functions:type_name -> objdiff.report.ReportItem - 3, // 5: objdiff.report.ReportUnit.metadata:type_name -> objdiff.report.ReportUnitMetadata - 5, // 6: objdiff.report.ReportItem.metadata:type_name -> objdiff.report.ReportItemMetadata - 1, // 7: objdiff.report.ChangesInput.from:type_name -> objdiff.report.Report - 1, // 8: objdiff.report.ChangesInput.to:type_name -> objdiff.report.Report - 0, // 9: objdiff.report.Changes.from:type_name -> objdiff.report.Measures - 0, // 10: objdiff.report.Changes.to:type_name -> objdiff.report.Measures - 8, // 11: objdiff.report.Changes.units:type_name -> objdiff.report.ChangeUnit - 0, // 12: objdiff.report.ChangeUnit.from:type_name -> objdiff.report.Measures - 0, // 13: objdiff.report.ChangeUnit.to:type_name -> objdiff.report.Measures - 9, // 14: objdiff.report.ChangeUnit.sections:type_name -> objdiff.report.ChangeItem - 9, // 15: objdiff.report.ChangeUnit.functions:type_name -> objdiff.report.ChangeItem - 3, // 16: objdiff.report.ChangeUnit.metadata:type_name -> objdiff.report.ReportUnitMetadata - 10, // 17: objdiff.report.ChangeItem.from:type_name -> objdiff.report.ChangeItemInfo - 10, // 18: objdiff.report.ChangeItem.to:type_name -> objdiff.report.ChangeItemInfo - 5, // 19: objdiff.report.ChangeItem.metadata:type_name -> objdiff.report.ReportItemMetadata - 20, // [20:20] is the sub-list for method output_type - 20, // [20:20] is the sub-list for method input_type - 20, // [20:20] is the sub-list for extension type_name - 20, // [20:20] is the sub-list for extension extendee - 0, // [0:20] is the sub-list for field type_name -} - -func init() { file_report_proto_init() } -func file_report_proto_init() { - if File_report_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_report_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*Measures); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_report_proto_msgTypes[1].Exporter = func(v any, i int) any { - switch v := v.(*Report); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_report_proto_msgTypes[2].Exporter = func(v any, i int) any { - switch v := v.(*ReportUnit); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_report_proto_msgTypes[3].Exporter = func(v any, i int) any { - switch v := v.(*ReportUnitMetadata); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_report_proto_msgTypes[4].Exporter = func(v any, i int) any { - switch v := v.(*ReportItem); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_report_proto_msgTypes[5].Exporter = func(v any, i int) any { - switch v := v.(*ReportItemMetadata); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_report_proto_msgTypes[6].Exporter = func(v any, i int) any { - switch v := v.(*ChangesInput); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_report_proto_msgTypes[7].Exporter = func(v any, i int) any { - switch v := v.(*Changes); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_report_proto_msgTypes[8].Exporter = func(v any, i int) any { - switch v := v.(*ChangeUnit); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_report_proto_msgTypes[9].Exporter = func(v any, i int) any { - switch v := v.(*ChangeItem); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_report_proto_msgTypes[10].Exporter = func(v any, i int) any { - switch v := v.(*ChangeItemInfo); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - file_report_proto_msgTypes[2].OneofWrappers = []any{} - file_report_proto_msgTypes[3].OneofWrappers = []any{} - file_report_proto_msgTypes[4].OneofWrappers = []any{} - file_report_proto_msgTypes[5].OneofWrappers = []any{} - file_report_proto_msgTypes[8].OneofWrappers = []any{} - file_report_proto_msgTypes[9].OneofWrappers = []any{} - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_report_proto_rawDesc, - NumEnums: 0, - NumMessages: 11, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_report_proto_goTypes, - DependencyIndexes: file_report_proto_depIdxs, - MessageInfos: file_report_proto_msgTypes, - }.Build() - File_report_proto = out.File - file_report_proto_rawDesc = nil - file_report_proto_goTypes = nil - file_report_proto_depIdxs = nil -} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..1dde298 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,21 @@ +{ + "name": "decompal-rs", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@picocss/pico": "^2.0.6" + } + }, + "node_modules/@picocss/pico": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@picocss/pico/-/pico-2.0.6.tgz", + "integrity": "sha512-/d8qsykowelD6g8k8JYgmCagOIulCPHMEc2NC4u7OjmpQLmtSetLhEbt0j1n3fPNJVcrT84dRp0RfJBn3wJROA==", + "license": "MIT", + "engines": { + "node": ">=18.19.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ad08f0e --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@picocss/pico": "^2.0.6" + } +} diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..0a9eda5 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,8 @@ +fn_single_line = true +group_imports = "StdExternalCrate" +imports_granularity = "Crate" +overflow_delimited_expr = true +reorder_impl_items = true +use_field_init_shorthand = true +use_small_heuristics = "Max" +where_single_line = true diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..0999fb5 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,18 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Config { + pub server: ServerConfig, + pub app: AppConfig, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ServerConfig { + pub port: u16, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct AppConfig { + pub db_url: String, + pub github_token: String, +} diff --git a/src/cron.rs b/src/cron.rs new file mode 100644 index 0000000..c7bbe55 --- /dev/null +++ b/src/cron.rs @@ -0,0 +1,58 @@ +use anyhow::Result; +use tokio_cron_scheduler::{Job, JobScheduler}; + +use crate::{github, AppState}; + +pub type Scheduler = JobScheduler; + +pub async fn create(state: AppState) -> Result { + let sched = JobScheduler::new().await?; + sched + .add(Job::new_async("0 0/5 * * * *", move |_uuid, _l| { + let mut state = state.clone(); + Box::pin(async move { + refresh_projects(&mut state).await.expect("Failed to refresh projects"); + }) + })?) + .await?; + sched.start().await?; + Ok(sched) +} + +pub async fn refresh_projects(state: &mut AppState) -> Result<()> { + for project_info in state.db.get_projects().await? { + github::run(state, &project_info.project.owner, &project_info.project.repo, 0).await?; + } + Ok(()) +} + +// github::run(&mut state, "zeldaret", "tww", 9983071101) +// .await +// .expect("Failed to run GitHub client"); +// github::run(&mut state, "DarkRTA", "rb3", 10482280646) +// .await +// .expect("Failed to run GitHub client"); +// github::run(&mut state, "bfbbdecomp", "bfbb", 9726343619) +// .await +// .expect("Failed to run GitHub client"); +// github::run(&mut state, "projectPiki", "pikmin2", 10429582042) +// .await +// .expect("Failed to run GitHub client"); +// github::run(&mut state, "doldecomp", "melee", 10661324447) +// .await +// .expect("Failed to run GitHub client"); +// github::run(&mut state, "xbret", "xenoblade", 10687397293) +// .await +// .expect("Failed to run GitHub client"); +// github::run(&mut state, "SeekyCt", "spm-decomp", 10605206350) +// .await +// .expect("Failed to run GitHub client"); +// github::run(&mut state, "PrimeDecomp", "prime", 10713664775) +// .await +// .expect("Failed to run GitHub client"); +// github::run(&mut state, "PrimeDecomp", "echoes", 10717066271) +// .await +// .expect("Failed to run GitHub client"); +// github::run(&mut state, "Rainchus", "marioparty4", 10715620655) +// .await +// .expect("Failed to run GitHub client"); diff --git a/src/db/mod.rs b/src/db/mod.rs new file mode 100644 index 0000000..f5b8841 --- /dev/null +++ b/src/db/mod.rs @@ -0,0 +1,487 @@ +use std::{borrow::Cow, cell::RefCell, sync::Arc}; + +use anyhow::{anyhow, bail, Context, Result}; +use moka::future::Cache; +use objdiff_core::bindings::report::{Report, ReportUnit}; +use prost::Message; +use sqlx::{migrate::MigrateDatabase, Pool, Sqlite, SqlitePool}; + +use crate::{ + config::AppConfig, + models::{Commit, Project, ProjectInfo, ReportFile}, +}; + +#[derive(Clone)] +pub struct Database { + pool: Pool, + report_cache: Cache>, +} + +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +struct ReportKey { + owner: String, + repo: String, + commit: String, + version: String, +} + +// BLAKE3 hash of the unit data +type UnitKey = [u8; 32]; + +impl Database { + pub async fn open(config: &AppConfig) -> Result { + if !Sqlite::database_exists(&config.db_url).await.unwrap_or(false) { + tracing::info!(db_url = %config.db_url, "Creating database"); + Sqlite::create_database(&config.db_url).await.context("Failed to create database")?; + tracing::info!("Database created"); + } + let pool = + SqlitePool::connect(&config.db_url).await.context("Failed to connect to database")?; + sqlx::migrate!("./migrations") + .run(&pool) + .await + .context("Failed to run database migrations")?; + let report_cache = Cache::builder().max_capacity(100).build(); + let db = Self { pool, report_cache }; + db.fixup_report_units().await?; + Ok(db) + } + + pub async fn close(&self) { self.pool.close().await } + + pub async fn insert_report(&mut self, file: &ReportFile) -> Result<()> { + let mut tx = self.pool.begin().await?; + let project_id = file.project.id as i64; + sqlx::query!( + r#" + INSERT INTO projects (id, owner, repo, name, short_name, default_version, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) + ON CONFLICT (id) DO NOTHING + "#, + project_id, + file.project.owner, + file.project.repo, + file.project.name, + file.project.short_name, + file.project.default_version, + ) + .execute(&mut *tx) + .await?; + let data = compress( + &Report { + measures: file.report.measures, + units: vec![], + version: file.report.version, + categories: file.report.categories.clone(), + } + .encode_to_vec(), + ); + let report_id = sqlx::query!( + r#" + INSERT INTO reports (project_id, version, git_commit, timestamp, data) + VALUES (?, ?, ?, ?, ?) + ON CONFLICT (project_id, version COLLATE NOCASE, git_commit COLLATE NOCASE) DO UPDATE + SET timestamp = EXCLUDED.timestamp + RETURNING id + "#, + project_id, + file.version, + file.commit.sha, + file.commit.timestamp, + data, + ) + .fetch_one(&mut *tx) + .await? + .id; + let mut keys = Vec::with_capacity(file.report.units.len()); + for unit in &file.report.units { + let mut data = unit.encode_to_vec(); + let key: UnitKey = blake3::hash(&data).into(); + keys.push(key); + let key = key.to_vec(); + data = compress(&data); + sqlx::query!( + r#" + INSERT INTO report_units (id, data, name) + VALUES (?, ?, ?) + ON CONFLICT (id) DO NOTHING + "#, + key, + data, + unit.name, + ) + .execute(&mut *tx) + .await?; + } + for (idx, key) in keys.iter().enumerate() { + let key = key.to_vec(); + let idx = idx as i32; + sqlx::query!( + r#" + INSERT INTO report_report_units (report_id, report_unit_id, unit_index) + VALUES (?, ?, ?) + ON CONFLICT DO NOTHING + "#, + report_id, + key, + idx, + ) + .execute(&mut *tx) + .await?; + } + tx.commit().await?; + self.report_cache + .insert( + ReportKey { + owner: file.project.owner.to_ascii_lowercase(), + repo: file.project.repo.to_ascii_lowercase(), + commit: file.commit.sha.to_ascii_lowercase(), + version: file.version.to_ascii_lowercase(), + }, + file.report.clone(), + ) + .await; + Ok(()) + } + + pub async fn get_report( + &self, + owner: &str, + repo: &str, + commit: &str, + version: &str, + ) -> Result> { + let mut conn = self.pool.acquire().await?; + let (report_id, project, commit, version, mut report) = match sqlx::query!( + r#" + SELECT + reports.id as "report_id!", + git_commit, + timestamp, + version, + data, + projects.id as "project_id!", + owner, + repo, + name, + short_name, + default_version + FROM reports JOIN projects ON reports.project_id = projects.id + WHERE projects.owner = ? COLLATE NOCASE AND projects.repo = ? COLLATE NOCASE + AND version = ? COLLATE NOCASE AND git_commit = ? COLLATE NOCASE + "#, + owner, + repo, + version, + commit + ) + .fetch_optional(&mut *conn) + .await? + { + Some(row) => { + let data = decompress(&row.data).context("Failed to decompress report data")?; + let report = Report::decode(data.as_ref()).context("Failed to decode report")?; + ( + row.report_id, + Project { + id: row.project_id as u64, + owner: row.owner, + repo: row.repo, + name: row.name, + short_name: row.short_name, + default_version: row.default_version, + }, + Commit { sha: row.git_commit, timestamp: row.timestamp.and_utc() }, + row.version, + report, + ) + } + None => return Ok(None), + }; + let key = ReportKey { + owner: owner.to_ascii_lowercase(), + repo: repo.to_ascii_lowercase(), + commit: commit.sha.to_ascii_lowercase(), + version: version.to_ascii_lowercase(), + }; + if let Some(report) = self.report_cache.get(&key).await { + return Ok(Some(ReportFile { project, commit, version, report })); + } + for row in sqlx::query!( + r#" + SELECT ru.id AS "id!", ru.data, rru.unit_index + FROM report_report_units rru JOIN report_units ru ON rru.report_unit_id = ru.id + WHERE rru.report_id = ? + ORDER BY rru.unit_index + "#, + report_id + ) + .fetch_all(&mut *conn) + .await? + { + let idx = row.unit_index as usize; + if idx != report.units.len() { + bail!("Report unit index mismatch"); + } + let key: UnitKey = row.id.as_slice().try_into()?; + let data = decompress(&row.data).context("Failed to decompress report unit data")?; + let hash: UnitKey = blake3::hash(data.as_ref()).into(); + if hash != key { + bail!("Report unit data hash mismatch"); + } + let unit = ReportUnit::decode(data.as_ref()).context("Failed to decode report unit")?; + report.units.push(unit); + } + report.migrate()?; + let report = Arc::new(report); + self.report_cache.insert(key, report.clone()).await; + Ok(Some(ReportFile { project, commit, version, report })) + } + + pub async fn report_exists(&self, owner: &str, repo: &str, commit: &str) -> Result { + let mut conn = self.pool.acquire().await?; + let exists = sqlx::query!( + r#" + SELECT EXISTS ( + SELECT 1 + FROM reports JOIN projects ON reports.project_id = projects.id + WHERE projects.owner = ? COLLATE NOCASE AND projects.repo = ? COLLATE NOCASE + AND git_commit = ? COLLATE NOCASE + ) AS "exists!" + "#, + owner, + repo, + commit + ) + .fetch_one(&mut *conn) + .await? + .exists + != 0; + Ok(exists) + } + + pub async fn get_project_info( + &self, + owner: &str, + repo: &str, + commit: Option<&str>, + ) -> Result> { + let mut conn = self.pool.acquire().await?; + let project = match sqlx::query!( + r#" + SELECT id AS "id!", owner, repo, name, short_name, default_version + FROM projects + WHERE owner = ? COLLATE NOCASE AND repo = ? COLLATE NOCASE + "#, + owner, + repo + ) + .fetch_optional(&mut *conn) + .await? + { + Some(row) => Project { + id: row.id as u64, + owner: row.owner, + repo: row.repo, + name: row.name, + short_name: row.short_name, + default_version: row.default_version, + }, + None => return Ok(None), + }; + let project_id = project.id as i64; + struct ReportInfo { + git_commit: String, + timestamp: chrono::NaiveDateTime, + version: String, + } + let reports = if let Some(commit) = commit { + // Fetch all reports for the specified commit + sqlx::query!( + r#" + SELECT git_commit, timestamp, version + FROM reports + WHERE project_id = ? AND git_commit = ? COLLATE NOCASE + ORDER BY version + "#, + project_id, + commit, + ) + .fetch_all(&mut *conn) + .await? + .into_iter() + .map(|row| ReportInfo { + git_commit: row.git_commit, + timestamp: row.timestamp, + version: row.version, + }) + .collect::>() + } else { + // Fetch the latest report for each version + sqlx::query!( + r#" + SELECT git_commit, timestamp, version + FROM reports + WHERE project_id = ? AND timestamp = ( + SELECT MAX(timestamp) + FROM reports + WHERE project_id = ? + ) + ORDER BY version + "#, + project_id, + project_id, + ) + .fetch_all(&mut *conn) + .await? + .into_iter() + .map(|row| ReportInfo { + git_commit: row.git_commit, + timestamp: row.timestamp, + version: row.version, + }) + .collect::>() + }; + let Some(first_report) = reports.first() else { + return Ok(None); + }; + // Fetch previous and next commits + let timestamp = first_report.timestamp.and_utc(); + let prev_commit = sqlx::query!( + r#" + SELECT git_commit + FROM reports + WHERE project_id = ? AND timestamp < ? + ORDER BY timestamp DESC + LIMIT 1 + "#, + project_id, + timestamp, + ) + .fetch_optional(&mut *conn) + .await? + .map(|row| row.git_commit); + let next_commit = sqlx::query!( + r#" + SELECT git_commit + FROM reports + WHERE project_id = ? AND timestamp > ? + ORDER BY timestamp + LIMIT 1 + "#, + project_id, + timestamp, + ) + .fetch_optional(&mut *conn) + .await? + .map(|row| row.git_commit); + Ok(Some(ProjectInfo { + project, + commit: Commit { + sha: first_report.git_commit.clone(), + timestamp: first_report.timestamp.and_utc(), + }, + report_versions: reports.iter().map(|r| r.version.clone()).collect(), + prev_commit, + next_commit, + })) + } + + pub async fn get_projects(&self) -> Result> { + let mut conn = self.pool.acquire().await?; + let projects = sqlx::query!( + r#" + SELECT + projects.id AS "project_id!", + owner AS "owner!", + repo AS "repo!", + name, + short_name, + default_version, + git_commit AS "git_commit!", + MAX(timestamp) AS "timestamp: chrono::NaiveDateTime", + JSON_GROUP_ARRAY(version ORDER BY version) AS versions + FROM projects JOIN reports ON projects.id = reports.project_id + WHERE timestamp = ( + SELECT MAX(timestamp) + FROM reports + WHERE project_id = projects.id + ) + GROUP BY projects.id + ORDER BY MAX(timestamp) DESC + "#, + ) + .fetch_all(&mut *conn) + .await? + .into_iter() + .map(|row| ProjectInfo { + project: Project { + id: row.project_id as u64, + owner: row.owner, + repo: row.repo, + name: row.name, + short_name: row.short_name, + default_version: row.default_version, + }, + commit: Commit { sha: row.git_commit, timestamp: row.timestamp.and_utc() }, + report_versions: serde_json::from_str(&row.versions).unwrap_or_default(), + prev_commit: None, + next_commit: None, + }) + .collect(); + Ok(projects) + } + + async fn fixup_report_units(&self) -> Result<()> { + let mut conn = self.pool.acquire().await?; + for row in sqlx::query!( + r#" + SELECT id, data + FROM report_units + WHERE name IS NULL + "#, + ) + .fetch_all(&mut *conn) + .await? + { + let data = decompress(&row.data).context("Failed to decompress report unit data")?; + let unit = ReportUnit::decode(data.as_ref()).context("Failed to decode report unit")?; + sqlx::query!( + r#" + UPDATE report_units + SET name = ? + WHERE id = ? + "#, + unit.name, + row.id, + ) + .execute(&mut *conn) + .await?; + } + Ok(()) + } +} + +thread_local! { + pub static COMPRESSOR: RefCell> = { + let mut compressor = zstd::bulk::Compressor::new(1).unwrap(); + // Always include the content size in the compressed data + compressor.set_parameter(zstd::zstd_safe::CParameter::ContentSizeFlag(true)).unwrap(); + RefCell::new(compressor) + }; + pub static DECOMPRESSOR: RefCell> = { + RefCell::new(zstd::bulk::Decompressor::new().unwrap()) + }; +} + +fn compress(data: &[u8]) -> Vec { COMPRESSOR.with_borrow_mut(|z| z.compress(data).unwrap()) } + +fn decompress(data: &[u8]) -> Result> { + match zstd::zstd_safe::get_frame_content_size(data) { + Ok(Some(size)) => { + Ok(Cow::Owned(DECOMPRESSOR.with_borrow_mut(|z| z.decompress(data, size as usize))?)) + } + Ok(None) => Err(anyhow!("Decompressed data size is unknown")), + Err(_) => Ok(Cow::Borrowed(data)), // Assume uncompressed + } +} diff --git a/src/github/mod.rs b/src/github/mod.rs new file mode 100644 index 0000000..4ec9ba8 --- /dev/null +++ b/src/github/mod.rs @@ -0,0 +1,300 @@ +use std::{ + ffi::OsStr, + io::{Cursor, Read}, + sync::{Arc, OnceLock}, +}; + +use anyhow::{anyhow, Context, Result}; +use objdiff_core::bindings::report::Report; +use octocrab::{ + models::{ArtifactId, RunId}, + params::actions::ArchiveFormat, + Octocrab, +}; +use regex::Regex; +use tokio::{sync::Semaphore, task::JoinSet}; + +use crate::{ + config::AppConfig, + models::{Commit, Project, ReportFile}, + AppState, +}; + +pub type Client = Octocrab; + +pub async fn create(config: &AppConfig) -> Result { + let client = Octocrab::builder() + .personal_token(config.github_token.clone()) + .build() + .context("Failed to create GitHub client")?; + octocrab::initialise(client.clone()); + let profile = client.current().user().await.context("Failed to fetch current user")?; + tracing::info!("Logged in as {}", profile.login); + Ok(client) +} + +pub async fn run(state: &mut AppState, owner: &str, repo: &str, stop_run_id: u64) -> Result<()> { + tracing::info!("Refreshing project {}/{}", owner, repo); + let existing = state.db.get_project_info(owner, repo, None).await?; + let repo = state.github.repos(owner, repo).get().await.context("Failed to fetch repo")?; + let branch = repo.default_branch.as_deref().unwrap_or("main"); + let Some(owner) = repo.owner else { + return Err(anyhow!("Repo has no owner")); + }; + let project = existing.as_ref().map(|e| e.project.clone()).unwrap_or_else(|| Project { + id: repo.id.0, + owner: owner.login.clone(), + repo: repo.name.clone(), + name: None, + short_name: None, + default_version: None, + }); + + let mut runs = vec![]; + let mut page = 1u32; + 'outer: loop { + let result = state + .github + .workflows(&project.owner, &project.repo) + .list_runs("build.yml") + .branch(branch) + .event("push") + .status("completed") + .exclude_pull_requests(true) + .page(page) + // .per_page(10) + .send() + .await?; + if result.items.is_empty() { + break; + } + for run in result.items { + if let Some(existing) = existing.as_ref() { + if run.head_sha == existing.commit.sha { + break 'outer; + } + } + let run_id = run.id; + runs.push(run); + if run_id == RunId(stop_run_id) { + break 'outer; + } + } + page += 1; + } + tracing::info!("Fetched {} runs", runs.len()); + + struct TaskResult { + run_id: RunId, + commit: Commit, + result: Result, + } + let sem = Arc::new(Semaphore::new(10)); + let mut set = JoinSet::new(); + for run in runs { + let sem = sem.clone(); + let project = project.clone(); + let github = state.github.clone(); + let db = state.db.clone(); + let run_id = run.id; + let commit = Commit::from(&run.head_commit); + set.spawn(async move { + let _permit = sem.acquire().await.unwrap(); + match db.report_exists(&project.owner, &project.repo, &commit.sha).await { + Ok(true) => { + return TaskResult { + run_id, + commit, + result: Ok(ProcessWorkflowRunResult { artifacts: vec![] }), + }; + } + Ok(false) => {} + Err(e) => return TaskResult { run_id, commit, result: Err(e) }, + } + let result = process_workflow_run(github, project, run.id).await; + TaskResult { run_id, commit, result } + }); + } + while let Some(join_result) = set.join_next().await { + match join_result { + Ok(TaskResult { + run_id, + commit, + result: Ok(ProcessWorkflowRunResult { artifacts }), + }) => { + tracing::debug!( + "Processed workflow run {} ({}) (artifacts {})", + run_id, + commit.sha, + artifacts.len() + ); + for artifact in artifacts { + let file = ReportFile { + project: project.clone(), + commit: commit.clone(), + version: artifact.version, + report: artifact.report, + }; + let start = std::time::Instant::now(); + state.db.insert_report(&file).await?; + let duration = start.elapsed(); + tracing::info!( + "Inserted report {} ({}) in {}ms", + file.version, + file.commit.sha, + duration.as_millis() + ); + } + } + Ok(TaskResult { run_id, commit, result: Err(e) }) => { + tracing::error!( + "Failed to process workflow run {} ({}): {:?}", + run_id, + commit.sha, + e + ); + } + Err(e) => { + tracing::error!("Failed to process workflow run: {:?}", e); + } + } + } + Ok(()) +} + +struct ProcessWorkflowRunResult { + artifacts: Vec, +} + +struct ProcessArtifactResult { + version: String, + report: Arc, +} + +async fn process_workflow_run( + github: Client, + project: Project, + run_id: RunId, +) -> Result { + let artifacts = github + .all_pages( + github + .actions() + .list_workflow_run_artifacts(&project.owner, &project.repo, run_id) + .send() + .await + .context("Failed to fetch artifacts")? + .value + .unwrap(), + ) + .await?; + tracing::debug!("Run {} (artifacts {})", run_id, artifacts.len()); + let mut result = ProcessWorkflowRunResult { artifacts: vec![] }; + if artifacts.is_empty() { + return Ok(result); + } + static REGEX: OnceLock = OnceLock::new(); + let regex = REGEX + .get_or_init(|| Regex::new(r"^(?P[A-z0-9_\-]+)[_-]report(?:[_-].*)?$").unwrap()); + let sem = Arc::new(Semaphore::new(3)); + let mut set = JoinSet::new(); + struct TaskResult { + artifact_name: String, + result: DownloadArtifactResult, + } + for artifact in &artifacts { + let artifact_name = artifact.name.clone(); + let version = + if let Some(version) = regex.captures(&artifact_name).and_then(|c| c.name("version")) { + version.as_str().to_string() + } else if artifact_name == "progress" || artifact_name == "progress.json" { + // bfbb compatibility + static MAPS_REGEX: OnceLock = OnceLock::new(); + let maps_regex = MAPS_REGEX + .get_or_init(|| Regex::new(r"^(?P[A-z0-9_\-]+)_maps$").unwrap()); + if let Some(version) = artifacts.iter().find_map(|a| { + maps_regex + .captures(&a.name) + .and_then(|c| c.name("version")) + .map(|m| m.as_str().to_string()) + }) { + version + } else { + continue; + } + } else { + continue; + }; + let sem = sem.clone(); + let github = github.clone(); + let project = project.clone(); + let artifact_id = artifact.id; + set.spawn(async move { + let _permit = sem.acquire().await.unwrap(); + TaskResult { + artifact_name, + result: download_artifact(github, project, artifact_id, version).await, + } + }); + } + while let Some(join_result) = set.join_next().await { + match join_result { + Ok(TaskResult { artifact_name: name, result: Ok(reports) }) => { + if reports.is_empty() { + tracing::warn!("No report found in artifact {}", name); + } else { + for (version, report) in reports { + tracing::info!("Processed artifact {} ({})", name, version); + result.artifacts.push(ProcessArtifactResult { version, report }); + } + } + } + Ok(TaskResult { artifact_name: name, result: Err(e) }) => { + tracing::error!("Failed to process artifact {}: {:?}", name, e); + } + Err(e) => { + tracing::error!("Failed to process artifact: {:?}", e); + } + } + } + Ok(result) +} + +type DownloadArtifactResult = Result)>>; + +async fn download_artifact( + github: Client, + project: Project, + artifact_id: ArtifactId, + version: String, +) -> DownloadArtifactResult { + let bytes = github + .actions() + .download_artifact(&project.owner, &project.repo, artifact_id, ArchiveFormat::Zip) + .await?; + let mut archive = zip::ZipArchive::new(Cursor::new(bytes))?; + for i in 0..archive.len() { + let mut file = archive.by_index(i)?; + let Some(path) = file.enclosed_name() else { + continue; + }; + if path.file_stem() == Some(OsStr::new("report")) + || path.file_stem() == Some(OsStr::new("progress")) + { + let mut contents = Vec::with_capacity(file.size() as usize); + file.read_to_end(&mut contents)?; + let mut report = Report::parse(&contents)?; + report.migrate()?; + // Split combined reports into individual reports TODO + // if version.eq_ignore_ascii_case("combined") { + // return Ok(report + // .split() + // .into_iter() + // .map(|(version, report)| (version, Arc::new(report))) + // .collect()); + // } + return Ok(vec![(version, Arc::new(report))]); + } + } + Ok(vec![]) +} diff --git a/src/handlers/css.rs b/src/handlers/css.rs new file mode 100644 index 0000000..0798e28 --- /dev/null +++ b/src/handlers/css.rs @@ -0,0 +1,45 @@ +use std::{ffi::OsStr, path::PathBuf}; + +use anyhow::anyhow; +use axum::{ + extract::Path, + http::{header, StatusCode}, + response::{IntoResponse, Response}, +}; + +use super::AppError; + +pub async fn get_css(Path(filename): Path) -> Result { + let mut path = PathBuf::from(format!("css/{filename}")); + if path.extension() != Some(OsStr::new("css")) { + return Err(AppError::Status(StatusCode::NOT_FOUND)); + } + path = path.with_extension(""); + let printer_options = lightningcss::stylesheet::PrinterOptions { + minify: path.extension() == Some(OsStr::new("min")), + ..Default::default() + }; + path = path.with_extension("scss"); + let options = grass::Options::default().load_path("node_modules"); + let mut output = grass::from_path(&path, &options)?; + // Skip lightningcss entirely if we're not minifying + if printer_options.minify { + let options = lightningcss::stylesheet::ParserOptions::default(); + let stylesheet = lightningcss::stylesheet::StyleSheet::parse(&output, options) + .map_err(|e| anyhow!(e.to_string()))?; + let result = stylesheet.to_css(printer_options)?; + drop(stylesheet); + output = result.code; + } + Ok(( + [ + (header::CONTENT_TYPE, mime::TEXT_CSS_UTF_8.as_ref()), + #[cfg(not(debug_assertions))] + (header::CACHE_CONTROL, "public, max-age=3600"), + #[cfg(debug_assertions)] + (header::CACHE_CONTROL, "no-cache"), + ], + output, + ) + .into_response()) +} diff --git a/src/handlers/graph.rs b/src/handlers/graph.rs new file mode 100644 index 0000000..e2016ce --- /dev/null +++ b/src/handlers/graph.rs @@ -0,0 +1,109 @@ +use std::io::Cursor; + +use anyhow::{anyhow, Context, Result}; +use image::{buffer::ConvertBuffer, ImageFormat, RgbImage, RgbaImage}; +use objdiff_core::bindings::report::{Report, ReportUnit}; +use palette::{Mix, Srgb}; +use resvg::{ + tiny_skia::{PixmapMut, Transform}, + usvg::{Options, Tree}, +}; +use serde::Serialize; +use streemap::Rect; + +use crate::{handlers::report::ReportTemplateUnit, templates::render, AppState}; + +#[derive(Clone)] +pub struct ReportUnitItem<'report> { + pub unit: &'report ReportUnit, + pub size: f32, + pub bounds: Rect, +} + +impl<'report> ReportUnitItem<'report> { + pub fn with_size(unit: &'report ReportUnit, size: f32) -> ReportUnitItem<'report> { + ReportUnitItem { unit, size, bounds: Rect::from_size(0.0, 0.0) } + } + + pub fn unit(&self) -> &'report ReportUnit { self.unit } +} + +pub fn layout_units( + report: &Report, + w: u32, + h: u32, + mut predicate: impl FnMut(&ReportUnit) -> bool, +) -> Vec { + let aspect = w as f32 / h as f32; + let rect = if aspect > 1.0 { + Rect::from_size(1.0, 1.0 / aspect) + } else { + Rect::from_size(aspect, 1.0) + }; + let mut items = report + .units + .iter() + .filter_map(|unit| { + if !predicate(unit) { + return None; + } + let total_code = unit.measures.as_ref().unwrap().total_code; + if total_code == 0 { + return None; + } + Some(ReportUnitItem::with_size( + unit, + total_code as f32 / report.measures.as_ref().unwrap().total_code as f32, + )) + }) + .collect::>(); + streemap::ordered_pivot_by_middle(rect, &mut items, |i| i.size, |i, s| i.bounds = s); + items +} + +#[derive(Serialize)] +struct GraphTemplateContext<'a> { + units: &'a [ReportTemplateUnit<'a>], + w: u32, + h: u32, +} + +pub fn render_svg( + units: &[ReportTemplateUnit], + w: u32, + h: u32, + state: &AppState, +) -> Result { + render(&state.templates, "treemap.svg", GraphTemplateContext { units, w, h }) +} + +pub fn render_image( + units: &[ReportTemplateUnit], + w: u32, + h: u32, + state: &AppState, + format: ImageFormat, +) -> Result> { + let svg = render_svg(units, w, h, state)?; + let opt = Options::default(); + let tree = Tree::from_str(&svg, &opt).context("Failed to parse SVG")?; + let mut image = RgbaImage::new(w, h); + let mut pixmap = PixmapMut::from_bytes(image.as_mut(), w, h) + .ok_or_else(|| anyhow!("Failed to create pixmap"))?; + resvg::render(&tree, Transform::identity(), &mut pixmap); + let mut bytes = Vec::new(); + let image: RgbImage = image.convert(); + image.write_to(&mut Cursor::new(&mut bytes), format)?; + Ok(bytes) +} + +fn rgb(r: u8, g: u8, b: u8) -> Srgb { + Srgb::new(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0) +} + +pub fn unit_color(fuzzy_match_percent: f32) -> String { + let red = rgb(42, 49, 64); + let green = rgb(0, 200, 0); + let (r, g, b) = red.mix(green, fuzzy_match_percent / 100.0).into_components(); + format!("#{:02x}{:02x}{:02x}", (r * 255.0) as u8, (g * 255.0) as u8, (b * 255.0) as u8) +} diff --git a/src/handlers/js.rs b/src/handlers/js.rs new file mode 100644 index 0000000..51bbcf7 --- /dev/null +++ b/src/handlers/js.rs @@ -0,0 +1,123 @@ +use std::{ffi::OsStr, path::PathBuf}; + +use anyhow::{anyhow, Result}; +use axum::{ + extract::Path, + http::{header, StatusCode}, + response::{IntoResponse, Response}, +}; +use oxc::{ + allocator::Allocator, + codegen::{CodeGenerator, CodegenReturn}, + minifier::{CompressOptions, Minifier, MinifierOptions}, + parser::Parser, + semantic::SemanticBuilder, + span::SourceType, + transformer::{EnvOptions, Targets, TransformOptions, Transformer}, +}; + +use crate::handlers::AppError; + +pub async fn get_js(Path(filename): Path) -> Result { + let mut path = PathBuf::from(format!("js/{filename}")); + #[derive(Debug, Copy, Clone, Eq, PartialEq)] + enum ResponseType { + Js, + SourceMap, + } + let response_type; + if path.extension() == Some(OsStr::new("js")) { + response_type = ResponseType::Js; + } else if path.extension() == Some(OsStr::new("map")) { + path = path.with_extension(""); + if path.extension() != Some(OsStr::new("js")) { + return Err(AppError::Status(StatusCode::NOT_FOUND)); + } + response_type = ResponseType::SourceMap; + } else { + return Err(AppError::Status(StatusCode::NOT_FOUND)); + } + path = path.with_extension(""); + let minify = path.extension() == Some(OsStr::new("min")); + path = path.with_extension("ts"); + let source_text = + std::fs::read_to_string(&path).map_err(|_| AppError::Status(StatusCode::NOT_FOUND))?; + let ret = transform(&path, &source_text, minify, response_type == ResponseType::SourceMap)?; + let (data, content_type) = match response_type { + ResponseType::Js => ( + format!("{}\n//# sourceMappingURL={}.map", ret.source_text, filename), + mime::APPLICATION_JAVASCRIPT_UTF_8.as_ref(), + ), + ResponseType::SourceMap => { + (ret.source_map.unwrap().to_json_string(), mime::APPLICATION_JSON.as_ref()) + } + }; + Ok(( + [ + (header::CONTENT_TYPE, content_type), + #[cfg(not(debug_assertions))] + (header::CACHE_CONTROL, "public, max-age=3600"), + #[cfg(debug_assertions)] + (header::CACHE_CONTROL, "no-cache"), + ], + data, + ) + .into_response()) +} + +fn transform( + path: &std::path::Path, + source_text: &str, + minify: bool, + source_map: bool, +) -> Result { + let source_type = SourceType::from_path(path)?; + let allocator = Allocator::default(); + let parsed = Parser::new(&allocator, &source_text, source_type).parse(); + let program = allocator.alloc(parsed.program); + + let (symbols, scopes) = SemanticBuilder::new(&source_text, source_type) + .build(&program) + .semantic + .into_symbol_table_and_scope_tree(); + + let transform_options = TransformOptions::from_preset_env(&EnvOptions { + targets: Targets::from_query("es2020"), + ..EnvOptions::default() + }) + .map_err(|v| anyhow!("{}", v.first().unwrap()))?; + + let _ = Transformer::new( + &allocator, + path, + source_type, + &source_text, + parsed.trivias.clone(), + transform_options, + ) + .build_with_symbols_and_scopes(symbols, scopes, program); + + let mangler = if minify { + Minifier::new(MinifierOptions { + mangle: minify, + compress: CompressOptions { + drop_debugger: false, + drop_console: false, + ..CompressOptions::all_true() + }, + }) + .build(&allocator, program) + .mangler + } else { + None + }; + + let mut codegen = CodeGenerator::new() + .with_options(oxc::codegen::CodegenOptions { minify, ..Default::default() }) + .with_mangler(mangler); + if source_map { + let name = path.file_name().unwrap().to_string_lossy(); + codegen = codegen.enable_source_map(&name, source_text); + } + Ok(codegen.build(program)) +} diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs new file mode 100644 index 0000000..cf518d0 --- /dev/null +++ b/src/handlers/mod.rs @@ -0,0 +1,78 @@ +use std::{str::FromStr, sync::Arc}; + +use axum::{ + http::{header, HeaderMap, StatusCode}, + response::{IntoResponse, Response}, + routing::get, + Router, +}; +use bytes::BytesMut; +use mime::Mime; +use prost::Message; + +use crate::AppState; + +mod css; +mod graph; +mod project; +mod report; +mod js; + +pub fn build_router() -> Router { + Router::new() + .route("/css/*filename", get(css::get_css)) + .route("/js/*filename", get(js::get_js)) + .route("/", get(project::get_projects)) + .route("/:owner/:repo", get(report::get_report)) + .route("/:owner/:repo/:version", get(report::get_report)) + .route("/:owner/:repo/:version/:commit", get(report::get_report)) +} + +pub enum AppError { + Status(StatusCode), + Internal(anyhow::Error), +} + +impl IntoResponse for AppError { + fn into_response(self) -> Response { + match self { + Self::Status(status) if status == StatusCode::NOT_FOUND => { + (status, "Not found").into_response() + } + Self::Status(status) => status.into_response(), + Self::Internal(err) => { + tracing::error!("{:?}", err); + (StatusCode::INTERNAL_SERVER_ERROR, format!("Something went wrong: {}", err)) + .into_response() + } + } + } +} + +impl> From for AppError { + fn from(err: E) -> Self { Self::Internal(err.into()) } +} + +pub fn parse_accept(headers: &HeaderMap) -> Vec { + headers + .get(header::ACCEPT) + .and_then(|value| value.to_str().ok()) + .iter() + .flat_map(|s| s.split(',')) + .map(|s| s.trim()) + .filter_map(|s| Mime::from_str(s).ok()) + .collect() +} + +pub struct Protobuf(pub Arc); + +pub const APPLICATION_PROTOBUF: &str = "application/x-protobuf"; +pub const PROTOBUF: &str = "x-protobuf"; + +impl IntoResponse for Protobuf { + fn into_response(self) -> Response { + let mut bytes = BytesMut::with_capacity(self.0.encoded_len()); + self.0.encode(&mut bytes).unwrap(); + ([(header::CONTENT_TYPE, APPLICATION_PROTOBUF)], bytes.freeze()).into_response() + } +} diff --git a/src/handlers/project.rs b/src/handlers/project.rs new file mode 100644 index 0000000..1142037 --- /dev/null +++ b/src/handlers/project.rs @@ -0,0 +1,94 @@ +use std::{sync::Arc, time::Instant}; + +use anyhow::anyhow; +use axum::{ + extract::State, + response::{Html, IntoResponse, Response}, +}; +use chrono::{DateTime, Utc}; +use objdiff_core::bindings::report::Measures; +use serde::Serialize; +use tokio::{sync::Semaphore, task::JoinSet}; + +use super::AppError; +use crate::{templates::render, AppState}; + +#[derive(Serialize)] +struct ProjectsTemplateContext { + projects: Vec, +} + +#[derive(Serialize)] +struct ProjectInfoContext { + id: u64, + path: String, + owner: String, + repo: String, + name: String, + short_name: String, + commit: String, + timestamp: DateTime, + measures: Measures, +} + +pub async fn get_projects(State(state): State) -> Result { + let start = Instant::now(); + let projects = state.db.get_projects().await?; + let mut out = projects + .iter() + .map(|p| ProjectInfoContext { + id: p.project.id, + path: format!("/{}/{}", p.project.owner, p.project.repo), + owner: p.project.owner.clone(), + repo: p.project.repo.clone(), + name: p.project.name().into_owned(), + short_name: p.project.short_name().to_owned(), + commit: p.commit.sha.clone(), + timestamp: p.commit.timestamp, + measures: Default::default(), + }) + .collect::>(); + + // Fetch latest report for each + let sem = Arc::new(Semaphore::new(10)); + let mut join_set = JoinSet::new(); + for info in projects { + let sem = sem.clone(); + let state = state.clone(); + join_set.spawn(async move { + let _permit = sem.acquire().await; + let Some(version) = info.default_version() else { + return (info, Err(anyhow!("No report version found"))); + }; + let report = state + .db + .get_report(&info.project.owner, &info.project.repo, &info.commit.sha, version) + .await; + (info, report) + }); + } + while let Some(result) = join_set.join_next().await { + match result { + Ok((info, Ok(Some(file)))) => { + if let Some(c) = out.iter_mut().find(|i| i.id == info.project.id) { + c.measures = file.report.measures.unwrap_or_default(); + } + } + Ok((info, Ok(None))) => { + tracing::warn!("No report found for {}", info.project.id); + } + Ok((info, Err(e))) => { + tracing::error!("Failed to fetch report for {}: {:?}", info.project.id, e); + } + Err(e) => { + tracing::error!("Failed to fetch report: {:?}", e); + } + } + } + + let mut rendered = + render(&state.templates, "projects.html", ProjectsTemplateContext { projects: out })?; + let elapsed = start.elapsed(); + rendered = rendered.replace("[[time]]", &format!("{}ms", elapsed.as_millis())); + Ok(Html(rendered).into_response()) +} diff --git a/src/handlers/report.rs b/src/handlers/report.rs new file mode 100644 index 0000000..7af0703 --- /dev/null +++ b/src/handlers/report.rs @@ -0,0 +1,361 @@ +use std::{iter, str::FromStr, time::Instant}; + +use anyhow::{Context, Result}; +use axum::{ + extract::{Path, Query, State}, + http::{header, HeaderMap, StatusCode}, + response::{Html, IntoResponse, Response}, + Json, +}; +use image::ImageFormat; +use mime::Mime; +use objdiff_core::bindings::report::Measures; +use serde::{Deserialize, Serialize}; + +use super::{graph::layout_units, parse_accept, AppError, Protobuf, PROTOBUF}; +use crate::{ + handlers::graph::{render_image, render_svg, unit_color}, + models::{Project, ProjectInfo, ReportFile}, + templates::render, + AppState, +}; + +#[derive(Deserialize)] +pub struct ReportParams { + owner: String, + repo: String, + version: Option, + commit: Option, +} + +const DEFAULT_IMAGE_WIDTH: u32 = 950; +const DEFAULT_IMAGE_HEIGHT: u32 = 475; + +#[derive(Deserialize)] +pub struct ReportQuery { + category: Option, + w: Option, + h: Option, +} + +impl ReportQuery { + pub fn size(&self) -> (u32, u32) { + (self.w.unwrap_or(DEFAULT_IMAGE_WIDTH), self.h.unwrap_or(DEFAULT_IMAGE_HEIGHT)) + } +} + +#[derive(Serialize)] +struct ReportTemplateContext<'a> { + project: &'a Project, + project_name: &'a str, + project_short_name: &'a str, + project_url: &'a str, + project_path: &'a str, + commit: &'a str, + version: &'a str, + measures: TemplateMeasures, + units: &'a [ReportTemplateUnit<'a>], + versions: &'a [String], + prev_commit: Option<&'a str>, + next_commit: Option<&'a str>, + categories: &'a [ReportCategoryItem<'a>], + current_category: &'a ReportCategoryItem<'a>, + units_json: String, +} + +#[derive(Serialize)] +pub struct ReportTemplateUnit<'a> { + name: &'a str, + fuzzy_match_percent: f32, + color: String, + x: f32, + y: f32, + w: f32, + h: f32, +} + +#[derive(Serialize, Copy, Clone)] +struct ReportCategoryItem<'a> { + id: &'a str, + name: &'a str, +} + +impl Default for ReportCategoryItem<'static> { + fn default() -> Self { Self { id: "all", name: "All" } } +} + +/// Duplicate of Measures to avoid omitting empty fields +#[derive(Serialize)] +struct TemplateMeasures { + fuzzy_match_percent: f32, + total_code: u64, + matched_code: u64, + matched_code_percent: f32, + total_data: u64, + matched_data: u64, + matched_data_percent: f32, + total_functions: u32, + matched_functions: u32, + matched_functions_percent: f32, + complete_code: u64, + complete_code_percent: f32, + complete_data: u64, + complete_data_percent: f32, +} + +impl From<&Measures> for TemplateMeasures { + fn from( + &Measures { + fuzzy_match_percent, + total_code, + matched_code, + matched_code_percent, + total_data, + matched_data, + matched_data_percent, + total_functions, + matched_functions, + matched_functions_percent, + complete_code, + complete_code_percent, + complete_data, + complete_data_percent, + }: &Measures, + ) -> Self { + Self { + fuzzy_match_percent, + total_code, + matched_code, + matched_code_percent, + total_data, + matched_data, + matched_data_percent, + total_functions, + matched_functions, + matched_functions_percent, + complete_code, + complete_code_percent, + complete_data, + complete_data_percent, + } + } +} + +fn extract_extension(params: ReportParams) -> (ReportParams, Option) { + if let Some(commit) = params.commit.as_deref() { + if let Some((commit, ext)) = commit.rsplit_once('.') { + return ( + ReportParams { commit: Some(commit.to_string()), ..params }, + Some(ext.to_string()), + ); + } + } else if let Some(version) = params.version.as_deref() { + if let Some((version, ext)) = version.rsplit_once('.') { + return ( + ReportParams { version: Some(version.to_string()), ..params }, + Some(ext.to_string()), + ); + } + } else if let Some((repo, ext)) = params.repo.rsplit_once('.') { + return (ReportParams { repo: repo.to_string(), ..params }, Some(ext.to_string())); + } + return (params, None); +} + +pub async fn get_report( + Path(params): Path, + Query(query): Query, + headers: HeaderMap, + State(state): State, +) -> Result { + let start = Instant::now(); + let (params, ext) = extract_extension(params); + let mut commit = params.commit.as_deref(); + if matches!(commit, Some(c) if c.eq_ignore_ascii_case("latest")) { + commit = None; + } + let Some(project_info) = state.db.get_project_info(¶ms.owner, ¶ms.repo, commit).await? + else { + return Err(AppError::Status(StatusCode::NOT_FOUND)); + }; + let version = if let Some(version) = ¶ms.version { + if version.eq_ignore_ascii_case("default") { + project_info.default_version().ok_or(AppError::Status(StatusCode::NOT_FOUND))? + } else { + version.as_str() + } + } else if let Some(default_version) = project_info.default_version() { + default_version + } else { + return Err(AppError::Status(StatusCode::NOT_FOUND)); + }; + let Some(report) = + state.db.get_report(¶ms.owner, ¶ms.repo, &project_info.commit.sha, version).await? + else { + return Err(AppError::Status(StatusCode::NOT_FOUND)); + }; + + let (w, h) = query.size(); + let (measures, current_category, units) = apply_category(&report, &query)?; + + let acceptable = if let Some(ext) = ext { + vec![match ext.to_ascii_lowercase().as_str() { + "json" => mime::APPLICATION_JSON, + "binpb" | "proto" => Mime::from_str("application/x-protobuf").unwrap(), + "svg" => mime::IMAGE_SVG, + _ => { + if let Some(format) = ImageFormat::from_extension(ext) { + Mime::from_str(format.to_mime_type()).unwrap() + } else { + return Err(AppError::Status(StatusCode::NOT_ACCEPTABLE)); + } + } + }] + } else { + if !headers.contains_key(header::ACCEPT) { + return Ok(Json(report.report).into_response()); + } + parse_accept(&headers) + }; + for mime in acceptable { + if (mime.type_() == mime::STAR && mime.subtype() == mime::STAR) + || (mime.type_() == mime::TEXT && mime.subtype() == mime::HTML) + { + let mut rendered = render_template( + &report, + &project_info, + measures, + ¤t_category, + &units, + &state, + )?; + let elapsed = start.elapsed(); + rendered = rendered.replace("[[time]]", &format!("{}ms", elapsed.as_millis())); + return Ok(Html(rendered).into_response()); + } else if mime.type_() == mime::APPLICATION && mime.subtype() == mime::JSON { + return Ok(Json(report.report).into_response()); + } else if mime.type_() == mime::APPLICATION && mime.subtype() == PROTOBUF { + return Ok(Protobuf(report.report).into_response()); + } else if mime.type_() == mime::IMAGE && mime.subtype() == mime::SVG { + let svg = render_svg(&units, w, h, &state)?; + return Ok(([(header::CONTENT_TYPE, mime::IMAGE_SVG.as_ref())], svg).into_response()); + } else if mime.type_() == mime::IMAGE { + let format = if mime.subtype() == mime::STAR { + // Default to PNG + ImageFormat::Png + } else { + ImageFormat::from_mime_type(mime.essence_str()) + .ok_or_else(|| AppError::Status(StatusCode::NOT_ACCEPTABLE))? + }; + let data = render_image(&units, w, h, &state, format)?; + return Ok(([(header::CONTENT_TYPE, format.to_mime_type())], data).into_response()); + } + } + Err(AppError::Status(StatusCode::NOT_ACCEPTABLE)) +} + +const EMPTY_MEASURES: Measures = Measures { + fuzzy_match_percent: 0.0, + total_code: 0, + matched_code: 0, + matched_code_percent: 0.0, + total_data: 0, + matched_data: 0, + matched_data_percent: 0.0, + total_functions: 0, + matched_functions: 0, + matched_functions_percent: 0.0, + complete_code: 0, + complete_code_percent: 0.0, + complete_data: 0, + complete_data_percent: 0.0, +}; + +fn apply_category<'a>( + report: &'a ReportFile, + query: &ReportQuery, +) -> Result<(&'a Measures, ReportCategoryItem<'a>, Vec>)> { + let mut measures = report.report.measures.as_ref().unwrap_or(&EMPTY_MEASURES); + let mut current_category = ReportCategoryItem::default(); + let mut category_id_filter = None; + if let Some(category) = + query.category.as_ref().and_then(|id| report.report.categories.iter().find(|c| c.id == *id)) + { + measures = category.measures.as_ref().unwrap_or(&EMPTY_MEASURES); + current_category = ReportCategoryItem { id: &category.id, name: &category.name }; + category_id_filter = Some(category.id.clone()); + } + let (w, h) = query.size(); + let aspect = w as f32 / h as f32; + let units = layout_units(&report.report, w, h, |item| { + if let Some(category_id) = &category_id_filter { + item.metadata + .as_ref() + .map_or(false, |m| m.progress_categories.iter().any(|c| c == category_id)) + } else { + true + } + }) + .into_iter() + .map(|item| { + let unit = item.unit(); + let match_percent = + unit.measures.as_ref().map(|m| m.fuzzy_match_percent).unwrap_or_default(); + let mut bounds = item.bounds; + if aspect > 1.0 { + bounds.y *= aspect; + bounds.h *= aspect; + } else { + bounds.x /= aspect; + bounds.w /= aspect; + } + ReportTemplateUnit { + name: &unit.name, + fuzzy_match_percent: match_percent, + color: unit_color(match_percent), + x: bounds.x * 100.0, + y: bounds.y * 100.0, + w: bounds.w * 100.0, + h: bounds.h * 100.0, + } + }) + .collect(); + Ok((measures, current_category, units)) +} + +fn render_template( + report: &ReportFile, + project_info: &ProjectInfo, + measures: &Measures, + current_category: &ReportCategoryItem, + units: &[ReportTemplateUnit], + state: &AppState, +) -> Result { + let categories = iter::once(ReportCategoryItem::default()) + .chain( + report + .report + .categories + .iter() + .map(|c| ReportCategoryItem { id: &c.id, name: &c.name }), + ) + .collect::>(); + let units_json = serde_json::to_string(&units).context("Failed to serialize units")?; + render(&state.templates, "report.html", ReportTemplateContext { + project: &report.project, + project_name: &report.project.name(), + project_short_name: &report.project.short_name(), + project_url: &report.project.repo_url(), + project_path: &format!("/{}/{}", report.project.owner, report.project.repo), + commit: &report.commit.sha, + version: &report.version, + measures: TemplateMeasures::from(measures), + units, + versions: &project_info.report_versions, + prev_commit: project_info.prev_commit.as_deref(), + next_commit: project_info.next_commit.as_deref(), + categories: &categories, + current_category, + units_json, + }) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e5008eb --- /dev/null +++ b/src/main.rs @@ -0,0 +1,95 @@ +mod config; +mod cron; +mod db; +mod github; +mod handlers; +mod models; +mod templates; + +use std::{ + fs::File, + io::BufReader, + net::{Ipv4Addr, SocketAddr}, + sync::Arc, + time::Duration, +}; + +use axum::{http::header, Router}; +use tokio::{net::TcpListener, signal}; +use tower::ServiceBuilder; +use tower_http::{timeout::TimeoutLayer, ServiceBuilderExt}; + +use crate::{config::Config, db::Database, handlers::build_router, templates::Templates}; + +#[derive(Clone)] +struct AppState { + config: Config, + db: Database, + github: github::Client, + templates: Templates, +} + +#[tokio::main] +async fn main() { + tracing_subscriber::fmt::init(); + + let config: Config = { + let file = BufReader::new(File::open("config.yml").expect("Failed to open config file")); + serde_yaml::from_reader(file).expect("Failed to parse config file") + }; + let db = Database::open(&config.app).await.expect("Failed to open database"); + let github = github::create(&config.app).await.expect("Failed to create GitHub client"); + let templates = templates::create("templates"); + let state = AppState { config, db: db.clone(), github, templates }; + + // Refresh before starting the server + // cron::refresh_projects(&mut state).await.expect("Failed to refresh projects"); + + // Start the task scheduler + let mut scheduler = cron::create(state.clone()).await.expect("Failed to create scheduler"); + + // Run our service + let addr = SocketAddr::from((Ipv4Addr::UNSPECIFIED, state.config.server.port)); + tracing::info!("Listening on {}", addr); + axum::serve(TcpListener::bind(addr).await.expect("bind error"), app(state).into_make_service()) + .with_graceful_shutdown(shutdown_signal()) + .await + .expect("server error"); + + scheduler.shutdown().await.expect("Failed to shut down scheduler"); + db.close().await; + tracing::info!("Shut down gracefully"); +} + +fn app(state: AppState) -> Router { + let sensitive_headers: Arc<[_]> = vec![header::AUTHORIZATION, header::COOKIE].into(); + let middleware = ServiceBuilder::new() + .sensitive_request_headers(sensitive_headers.clone()) + .sensitive_response_headers(sensitive_headers) + .trace_for_http() + .layer(TimeoutLayer::new(Duration::from_secs(10))) + .compression(); + build_router().layer(middleware).with_state(state) +} + +async fn shutdown_signal() { + let ctrl_c = async { + signal::ctrl_c().await.expect("failed to install Ctrl+C handler"); + }; + + #[cfg(unix)] + let terminate = async { + signal::unix::signal(signal::unix::SignalKind::terminate()) + .expect("failed to install signal handler") + .recv() + .await; + }; + + #[cfg(not(unix))] + let terminate = std::future::pending::<()>(); + + tokio::select! { + _ = ctrl_c => {}, + _ = terminate => {}, + } +} diff --git a/src/models.rs b/src/models.rs new file mode 100644 index 0000000..83ee268 --- /dev/null +++ b/src/models.rs @@ -0,0 +1,81 @@ +use std::{borrow::Cow, sync::Arc}; + +use chrono::{DateTime, Utc}; +use objdiff_core::bindings::report::Report; +use serde::Serialize; + +#[derive(Debug, Clone, Eq, PartialEq, Serialize)] +pub struct Project { + pub id: u64, + pub owner: String, + pub repo: String, + pub name: Option, + pub short_name: Option, + pub default_version: Option, +} + +impl Project { + pub fn name(&self) -> Cow { + if let Some(name) = self.name.as_ref() { + Cow::Borrowed(name) + } else { + Cow::Owned(format!("{}/{}", self.owner, self.repo)) + } + } + + pub fn short_name(&self) -> &str { + self.short_name.as_deref().or(self.name.as_deref()).unwrap_or(&self.repo) + } + + pub fn repo_url(&self) -> String { format!("https://github.com/{}/{}", self.owner, self.repo) } +} + +#[derive(Debug, Clone, Eq, PartialEq, Serialize)] +pub struct ProjectInfo { + pub project: Project, + pub commit: Commit, + pub report_versions: Vec, + pub prev_commit: Option, + pub next_commit: Option, +} + +impl ProjectInfo { + pub fn default_version(&self) -> Option<&str> { + self.project + .default_version + .as_ref() + // Verify that the default version is in the list of report versions + .and_then(|v| self.report_versions.contains(v).then_some(v.as_str())) + // Otherwise, return the first version in the list + .or_else(|| self.report_versions.first().map(String::as_str)) + } +} + +#[derive(Debug, Clone, Eq, PartialEq, Serialize)] +pub struct Commit { + pub sha: String, + pub timestamp: DateTime, +} + +impl From<&octocrab::models::repos::Commit> for Commit { + fn from(commit: &octocrab::models::repos::Commit) -> Self { + Self { + sha: commit.sha.as_ref().unwrap().clone(), + timestamp: commit.author.as_ref().unwrap().date.unwrap(), + } + } +} + +impl From<&octocrab::models::workflows::HeadCommit> for Commit { + fn from(commit: &octocrab::models::workflows::HeadCommit) -> Self { + Self { sha: commit.id.clone(), timestamp: commit.timestamp } + } +} + +#[derive(Debug, Clone, PartialEq, Serialize)] +pub struct ReportFile { + pub project: Project, + pub commit: Commit, + pub version: String, + pub report: Arc, +} diff --git a/src/templates.rs b/src/templates.rs new file mode 100644 index 0000000..3de872e --- /dev/null +++ b/src/templates.rs @@ -0,0 +1,42 @@ +use std::sync::Arc; + +use anyhow::{Context, Result}; +use chrono::{DateTime, Utc}; +use minijinja::{path_loader, Environment}; +use minijinja_autoreload::AutoReloader; + +pub type Templates = Arc; + +pub fn create(template_path: impl Into) -> Templates { + let template_path = template_path.into(); + Arc::new(AutoReloader::new(move |notifier| { + let mut env = Environment::new(); + let template_path = template_path.as_str(); + notifier.watch_path(template_path, true); + env.set_loader(path_loader(template_path)); + env.set_trim_blocks(true); + env.set_lstrip_blocks(true); + env.add_filter("date", date); + env.add_filter("timeago", timeago); + Ok(env) + })) +} + +pub fn render(templates: &Templates, template_name: &str, context: S) -> Result +where S: serde::Serialize { + let env = templates.acquire_env().context("Failed to get template environment")?; + let template = env.get_template(template_name).context("Failed to get template")?; + template.render(context).context("Failed to render template") +} + +fn timeago(value: String) -> String { + let value = DateTime::parse_from_rfc3339(&value).unwrap_or_default(); + let duration = Utc::now().signed_duration_since(value); + timeago::Formatter::new().convert(duration.to_std().unwrap()) +} + +fn date(value: String, format: Option) -> String { + let value = DateTime::parse_from_rfc3339(&value).unwrap_or_default(); + let format = format.as_deref().unwrap_or("%Y-%m-%d %H:%M:%S %Z"); + value.format(format).to_string() +} diff --git a/templates/oldgraphs.html b/templates/oldgraphs.html new file mode 100644 index 0000000..ad7e84e --- /dev/null +++ b/templates/oldgraphs.html @@ -0,0 +1,55 @@ + + + + \ No newline at end of file diff --git a/templates/projects.html b/templates/projects.html new file mode 100644 index 0000000..0b5a60f --- /dev/null +++ b/templates/projects.html @@ -0,0 +1,75 @@ + + + + + + + + + + Projects • decomp.dev + + +
+ +
+
+ {% for project in projects %} + {% set measures = project.measures %} +
+

+ + {{ project.name }} + +

+
+ {{ measures.matched_code_percent | round(2) }}% decompiled | + {{ measures.complete_code_percent | round(2) }}% fully linked +
+
+
+
+
+
+ +
+
+
+
+
+ + Last updated: {{ project.timestamp | timeago }} + in commit {{ project.commit[:7] }} + +
+ {% endfor %} +
+
+ Generated in [[time]] 🦀 +
+ + diff --git a/templates/report.html b/templates/report.html new file mode 100644 index 0000000..1547fad --- /dev/null +++ b/templates/report.html @@ -0,0 +1,127 @@ + + + + + + + + + {{ project_short_name }}{% if current_category.id != "all" %} ({{ current_category.name }}){% endif %} • Progress Report + + + + + + + + + +
+ +
+
+

+ {{ project_short_name }}{% if current_category.id != "all" %} ({{ current_category.name }}){% endif %} is {{ measures.matched_code_percent | round(2) }}% decompiled +

+

{{ measures.complete_code_percent | round(2) }}% fully linked

+
Code
+
+
+
+
+
+ +
+
+
+
+
+
Data
+
+
+
+
+ +
+
+
+
+
Units
+ {% if categories | length > 1 %} + + {% endif %} + + +
Links
+
+

+ GitHub ({{ commit[:7] }}) +

+ {% if prev_commit %} +

Previous commit

+ {% endif %} + {% if next_commit %} +

Next commit

+ {% endif %} +
+
+
+ Generated in [[time]] 🦀 +
+ + diff --git a/templates/treemap.svg b/templates/treemap.svg new file mode 100644 index 0000000..fea7da3 --- /dev/null +++ b/templates/treemap.svg @@ -0,0 +1,7 @@ + + + + {% for unit in units %} + + {% endfor %} + diff --git a/zipstream/zipstream.go b/zipstream/zipstream.go deleted file mode 100644 index 4a23acb..0000000 --- a/zipstream/zipstream.go +++ /dev/null @@ -1,275 +0,0 @@ -// Package zipstream -// A streaming zip reader that specifically supports GitHub Actions artifacts files. -// The standard library's `archive/zip` uses the central directory at the end of the file, -// so it would require downloading the entire file before reading it. -// This package reads the local file headers and supports DEFLATE streams with an unknown size, -// allowing us to read files as the response is being downloaded. -package zipstream - -import ( - "archive/zip" - "bufio" - "compress/flate" - "encoding/binary" - "fmt" - "io" -) - -const ( - headerIdentifierLen = 4 - fileHeaderLen = 26 - dataDescriptorLen = 16 // four uint32: descriptor signature, crc32, compressed size, size - fileHeaderSignature = 0x04034b50 - directoryHeaderSignature = 0x02014b50 - directoryEndSignature = 0x06054b50 - dataDescriptorSignature = 0x08074b50 - zip64ExtraID = 0x0001 // Zip64 extended information -) - -type Reader struct { - r io.Reader - rBuf *bufio.Reader - fr io.ReadCloser - localFileEnd bool - curEntry *Entry -} - -func NewReader(r io.Reader) *Reader { - return &Reader{ - r: r, - fr: nil, - } -} - -type Entry struct { - zip.FileHeader - r io.Reader -} - -func (e *Entry) hasDataDescriptor() bool { - return e.Flags&8 != 0 -} - -// IsDir just simply check whether the entry name ends with "/" -func (e *Entry) IsDir() bool { - return len(e.Name) > 0 && e.Name[len(e.Name)-1] == '/' -} - -func (e *Entry) Read(p []byte) (n int, err error) { - return e.r.Read(p) -} - -//goland:noinspection GoDeprecation -func (z *Reader) readEntry() (*Entry, error) { - buf := make([]byte, fileHeaderLen) - if _, err := io.ReadFull(z.r, buf); err != nil { - return nil, fmt.Errorf("unable to read local file header: %w", err) - } - - lr := readBuf(buf) - readerVersion := lr.uint16() - flags := lr.uint16() - method := lr.uint16() - modifiedTime := lr.uint16() - modifiedDate := lr.uint16() - crc32Sum := lr.uint32() - compressedSize := lr.uint32() - uncompressedSize := lr.uint32() - filenameLen := int(lr.uint16()) - extraAreaLen := int(lr.uint16()) - - entry := &Entry{ - FileHeader: zip.FileHeader{ - ReaderVersion: readerVersion, - Flags: flags, - Method: method, - ModifiedTime: modifiedTime, - ModifiedDate: modifiedDate, - CRC32: crc32Sum, - CompressedSize: compressedSize, - UncompressedSize: uncompressedSize, - CompressedSize64: uint64(compressedSize), - UncompressedSize64: uint64(uncompressedSize), - }, - r: nil, - } - - nameAndExtraBuf := make([]byte, filenameLen+extraAreaLen) - if _, err := io.ReadFull(z.r, nameAndExtraBuf); err != nil { - return nil, fmt.Errorf("unable to read entry name and extra area: %w", err) - } - - entry.Name = string(nameAndExtraBuf[:filenameLen]) - entry.Extra = nameAndExtraBuf[filenameLen:] - - entry.NonUTF8 = flags&0x800 == 0 - if flags&1 == 1 { - return nil, fmt.Errorf("encrypted ZIP entry not supported") - } - if flags&8 == 8 && method != zip.Deflate { - return nil, fmt.Errorf("only DEFLATED entries can have data descriptor") - } - - needCSize := entry.CompressedSize == ^uint32(0) - needUSize := entry.UncompressedSize == ^uint32(0) - - ler := readBuf(entry.Extra) - for len(ler) >= 4 { // need at least tag and size - fieldTag := ler.uint16() - fieldSize := int(ler.uint16()) - if len(ler) < fieldSize { - break - } - fieldBuf := ler.sub(fieldSize) - - switch fieldTag { - case zip64ExtraID: - // update directory values from the zip64 extra block. - // They should only be consulted if the sizes read earlier - // are maxed out. - // See golang.org/issue/13367. - if needUSize { - needUSize = false - if len(fieldBuf) < 8 { - return nil, zip.ErrFormat - } - entry.UncompressedSize64 = fieldBuf.uint64() - } - if needCSize { - needCSize = false - if len(fieldBuf) < 8 { - return nil, zip.ErrFormat - } - entry.CompressedSize64 = fieldBuf.uint64() - } - } - } - - if needCSize { - return nil, zip.ErrFormat - } - - if method == zip.Store { - entry.r = io.LimitReader(z.r, int64(entry.UncompressedSize64)) - } else if method == zip.Deflate { - var reader io.Reader - if entry.CompressedSize64 > 0 { - reader = io.LimitReader(z.r, int64(entry.CompressedSize64)) - } else { - // unknown size; read until deflate EOF, - // but we need z.r to be an io.ByteReader for flate to not overread - if _, ok := z.r.(io.ByteReader); !ok { - z.r = bufio.NewReader(z.r) - } - reader = z.r - } - if z.fr == nil { - z.fr = flate.NewReader(reader) - } else { - z.fr.(flate.Resetter).Reset(reader, nil) - } - entry.r = z.fr - } else { - return nil, fmt.Errorf("unknown compression method %d", method) - } - - return entry, nil -} - -func (z *Reader) Next() (*Entry, error) { - if z.localFileEnd { - return nil, io.EOF - } - if z.curEntry != nil { - // Read any remaining data for the current file, if necessary. - if _, err := io.Copy(io.Discard, z.curEntry); err != nil { - return nil, fmt.Errorf("read previous file data fail: %w", err) - } - // Read the data descriptor if present. - if z.curEntry.hasDataDescriptor() { - if err := readDataDescriptor(z.r); err != nil { - return nil, fmt.Errorf("read previous entry's data descriptor fail: %w", err) - } - } - } - headerIDBuf := make([]byte, headerIdentifierLen) - if _, err := io.ReadFull(z.r, headerIDBuf); err != nil { - return nil, fmt.Errorf("unable to read header identifier: %w", err) - } - headerID := binary.LittleEndian.Uint32(headerIDBuf) - if headerID != fileHeaderSignature { - if headerID == directoryHeaderSignature || headerID == directoryEndSignature { - z.localFileEnd = true - return nil, io.EOF - } - return nil, zip.ErrFormat - } - entry, err := z.readEntry() - if err != nil { - return nil, fmt.Errorf("unable to read zip file header: %w", err) - } - z.curEntry = entry - return entry, nil -} - -func readDataDescriptor(r io.Reader) error { - var buf [dataDescriptorLen]byte - // The spec says: "Although not originally assigned a - // signature, the value 0x08074b50 has commonly been adopted - // as a signature value for the data descriptor record. - // Implementers should be aware that ZIP files may be - // encountered with or without this signature marking data - // descriptors and should account for either case when reading - // ZIP files to ensure compatibility." - // - // dataDescriptorLen includes the size of the signature but - // first read just those 4 bytes to see if it exists. - _, err := io.ReadFull(r, buf[:4]) - if err != nil { - return err - } - off := 0 - maybeSig := readBuf(buf[:4]) - if maybeSig.uint32() != dataDescriptorSignature { - // No data descriptor signature. Keep these four bytes. - off += 4 - } - _, err = io.ReadFull(r, buf[off:12]) - if err != nil { - return err - } - - return nil -} - -type readBuf []byte - -func (b *readBuf) uint8() uint8 { - v := (*b)[0] - *b = (*b)[1:] - return v -} - -func (b *readBuf) uint16() uint16 { - v := binary.LittleEndian.Uint16(*b) - *b = (*b)[2:] - return v -} - -func (b *readBuf) uint32() uint32 { - v := binary.LittleEndian.Uint32(*b) - *b = (*b)[4:] - return v -} - -func (b *readBuf) uint64() uint64 { - v := binary.LittleEndian.Uint64(*b) - *b = (*b)[8:] - return v -} - -func (b *readBuf) sub(n int) readBuf { - b2 := (*b)[:n] - *b = (*b)[n:] - return b2 -}