├── README.md ├── .gitignore ├── src ├── opt.rs ├── main.rs ├── err.rs ├── server.rs ├── url.rs ├── fetch │ ├── extract │ │ └── tests.rs │ ├── date.rs │ └── extract.rs ├── fetch.rs └── server │ └── routes.rs ├── LICENSE ├── Cargo.toml ├── .github └── workflows │ └── ci.yml └── Cargo.lock /README.md: -------------------------------------------------------------------------------- 1 | # katamari 2 | 3 | Aggregate RSS and friends. 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # These are backup files generated by rustfmt 7 | **/*.rs.bk 8 | 9 | # MSVC Windows builds of rustc generate these, which store debugging information 10 | *.pdb 11 | -------------------------------------------------------------------------------- /src/opt.rs: -------------------------------------------------------------------------------- 1 | use clap::{ArgAction, Parser}; 2 | use std::net::SocketAddr; 3 | 4 | #[derive(Parser, Debug)] 5 | #[clap(version, about)] 6 | pub struct Options { 7 | /// Logging verbosity (-v debug, -vv trace) 8 | #[arg(short = 'v', long = "verbose", action = ArgAction::Count, global = true)] 9 | pub verbose: u8, 10 | 11 | pub listen_addr: SocketAddr, 12 | } 13 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::enum_variant_names)] 2 | 3 | use std::io; 4 | use tracing_subscriber::filter::LevelFilter; 5 | use tracing_subscriber::layer::SubscriberExt; 6 | use tracing_subscriber::util::SubscriberInitExt; 7 | 8 | mod err; 9 | mod fetch; 10 | mod opt; 11 | mod server; 12 | mod url; 13 | 14 | #[tokio::main(flavor = "multi_thread")] 15 | async fn main() -> Result<(), io::Error> { 16 | let opt::Options { 17 | verbose, 18 | listen_addr, 19 | } = clap::Parser::parse(); 20 | 21 | tracing_subscriber::registry() 22 | .with(match verbose { 23 | 0 => LevelFilter::INFO, 24 | 1 => LevelFilter::DEBUG, 25 | _ => LevelFilter::TRACE, 26 | }) 27 | .with(tracing_subscriber::fmt::layer()) 28 | .init(); 29 | 30 | server::run(listen_addr).await?; 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /src/err.rs: -------------------------------------------------------------------------------- 1 | use axum::response::IntoResponse; 2 | use hyper::StatusCode; 3 | use std::fmt::{self, Debug, Write}; 4 | 5 | pub type Error = Box; 6 | 7 | pub struct ResponseError(Error); 8 | 9 | impl From for ResponseError 10 | where 11 | T: Into, 12 | { 13 | fn from(e: T) -> Self { 14 | ResponseError(e.into()) 15 | } 16 | } 17 | 18 | impl Debug for ResponseError { 19 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 20 | Debug::fmt(&self.0, f) 21 | } 22 | } 23 | 24 | impl IntoResponse for ResponseError { 25 | fn into_response(self) -> axum::response::Response { 26 | let mut body = String::from("Error: "); 27 | write!(body, "{}", self.0).unwrap(); 28 | let mut err: &dyn std::error::Error = self.0.as_ref(); 29 | while let Some(source) = err.source() { 30 | write!(body, " -> {}", source).unwrap(); 31 | err = source; 32 | } 33 | 34 | (StatusCode::INTERNAL_SERVER_ERROR, body).into_response() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 erikdesjardins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "katamari" 3 | version = "0.6.7" 4 | description = "Aggregate RSS and friends." 5 | edition = "2021" 6 | 7 | [dependencies] 8 | axum = { version = "0.8", default-features = false, features = ["http1", "http2", "tokio", "tracing"] } 9 | base64 = "0.22" 10 | chrono = { version = "0.4", default-features = false, features = ["clock"] } 11 | clap = { version = "4", features = ["derive"] } 12 | feed-rs = "2" 13 | http-body-util = "0.1" 14 | hyper = { version = "1", features = ["client", "http1", "http2"] } 15 | hyper-rustls = { version = "0.27", default-features = false, features = ["native-tokio", "http1", "http2", "tls12", "logging", "ring"] } 16 | hyper-util = { version = "0.1", features = ["client"] } 17 | mediatype = "0.19" 18 | quick-xml = { version = "0.38", features = ["escape-html"] } 19 | rand = { version = "0.9", default-features = false, features = ["thread_rng"] } 20 | thiserror = "2" 21 | tokio = { version = "1.0", features = ["macros", "rt", "rt-multi-thread"] } 22 | tower = "0.5" 23 | tower-http = { version = "0.6", features = ["compression-br", "trace"] } 24 | tracing = { version = "0.1", features = ["release_max_level_debug"] } 25 | tracing-subscriber = "0.3" 26 | 27 | [profile.release] 28 | panic = "abort" 29 | lto = true 30 | codegen-units = 1 31 | -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | use crate::fetch::FetchClient; 2 | use axum::routing::get; 3 | use axum::Router; 4 | use hyper_rustls::HttpsConnector; 5 | use hyper_util::client::legacy::Client; 6 | use hyper_util::rt::TokioExecutor; 7 | use std::io; 8 | use std::net::SocketAddr; 9 | use std::sync::Arc; 10 | use tower::ServiceBuilder; 11 | use tower_http::compression::CompressionLayer; 12 | use tower_http::trace::TraceLayer; 13 | 14 | mod routes; 15 | 16 | struct AppState { 17 | client: FetchClient, 18 | } 19 | 20 | pub async fn run(addr: SocketAddr) -> Result<(), io::Error> { 21 | let listener = tokio::net::TcpListener::bind(addr).await?; 22 | 23 | tracing::info!("listening on {}", addr); 24 | 25 | let client = Client::builder(TokioExecutor::new()).build( 26 | HttpsConnector::<()>::builder() 27 | .with_native_roots()? 28 | .https_or_http() 29 | .enable_http1() 30 | .enable_http2() 31 | .build(), 32 | ); 33 | 34 | let state = Arc::new(AppState { client }); 35 | 36 | let app = Router::new() 37 | .route("/", get(routes::index)) 38 | .with_state(state) 39 | .layer( 40 | ServiceBuilder::new() 41 | .layer(TraceLayer::new_for_http()) 42 | .layer(CompressionLayer::new().br(true)), 43 | ); 44 | 45 | axum::serve(listener, app).await?; 46 | 47 | Ok(()) 48 | } 49 | -------------------------------------------------------------------------------- /src/url.rs: -------------------------------------------------------------------------------- 1 | pub fn domain(url: &str) -> &str { 2 | url.split_once("://") 3 | .map(|(_, rest)| { 4 | rest.split_once('/') 5 | .map(|(domain, _)| domain) 6 | .unwrap_or(rest) 7 | }) 8 | .unwrap_or(url) 9 | } 10 | 11 | pub fn prefix(url: &str) -> &str { 12 | url.split_once('#').map(|(prefix, _)| prefix).unwrap_or(url) 13 | } 14 | 15 | #[cfg(test)] 16 | mod tests { 17 | use super::*; 18 | 19 | #[test] 20 | fn test_domain() { 21 | assert_eq!(domain("example.com"), "example.com"); 22 | assert_eq!(domain("https://example.com"), "example.com"); 23 | assert_eq!(domain("http://example.com/foo"), "example.com"); 24 | assert_eq!(domain("https://example.com/foo/bar"), "example.com"); 25 | } 26 | 27 | #[test] 28 | fn test_prefix() { 29 | assert_eq!(prefix("https://example.com"), "https://example.com"); 30 | assert_eq!( 31 | prefix("https://example.com/test"), 32 | "https://example.com/test" 33 | ); 34 | assert_eq!(prefix("http://example.com#foo"), "http://example.com"); 35 | assert_eq!( 36 | prefix("http://example.com/test#foo"), 37 | "http://example.com/test" 38 | ); 39 | assert_eq!(prefix("https://example.com#foo#bar"), "https://example.com"); 40 | assert_eq!( 41 | prefix("https://example.com/test/2#foo#bar"), 42 | "https://example.com/test/2" 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - v*.*.* 9 | pull_request: 10 | 11 | jobs: 12 | fmt: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - run: rustup toolchain install stable --profile minimal 17 | - run: rustup component add rustfmt 18 | 19 | - run: cargo fmt --all -- --check 20 | 21 | clippy: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v3 25 | - run: rustup toolchain install stable --profile minimal 26 | - run: rustup component add clippy 27 | 28 | - run: RUSTFLAGS="-D warnings" cargo clippy 29 | 30 | test: 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v3 34 | - run: rustup toolchain install stable --profile minimal 35 | 36 | - run: cargo test 37 | 38 | build-linux: 39 | runs-on: ubuntu-latest 40 | permissions: 41 | contents: write 42 | steps: 43 | - uses: actions/checkout@v3 44 | - run: rustup toolchain install stable --profile minimal 45 | - run: rustup target add x86_64-unknown-linux-musl 46 | - run: sudo apt-get install musl-tools 47 | 48 | - run: cargo build --release --target=x86_64-unknown-linux-musl 49 | - run: strip target/x86_64-unknown-linux-musl/release/katamari 50 | - run: ls -lh target/x86_64-unknown-linux-musl/release/katamari 51 | 52 | - uses: softprops/action-gh-release@v1 53 | if: startsWith(github.ref, 'refs/tags/') 54 | with: 55 | files: target/x86_64-unknown-linux-musl/release/katamari 56 | env: 57 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 58 | -------------------------------------------------------------------------------- /src/fetch/extract/tests.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[test] 4 | fn summary_from_html_summary_return_first_text() { 5 | let summary = summary_from_html_summary( 6 | r#" 7 |
8 |

First paragraph.

9 |

Second paragraph.

10 |
11 | "#, 12 | ); 13 | 14 | assert_eq!(summary.unwrap(), Some(String::from("First paragraph."))); 15 | } 16 | 17 | #[test] 18 | fn summary_from_html_summary_none_matching() { 19 | let summary = summary_from_html_summary( 20 | r#" 21 |
22 | 23 |
24 | "#, 25 | ); 26 | 27 | assert_eq!(summary.unwrap(), None); 28 | } 29 | 30 | #[test] 31 | fn summary_from_html_summary_unescape() { 32 | let summary = summary_from_html_summary( 33 | r#" 34 |
35 | <foo> 36 |
37 | "#, 38 | ); 39 | 40 | assert_eq!(summary.unwrap(), Some(String::from(""))); 41 | } 42 | 43 | #[test] 44 | fn summary_from_html_body_matching_link() { 45 | let summary = summary_from_html_body( 46 | "https://example.com", 47 | r#" 48 |
49 | First paragraph. 50 |
51 | "#, 52 | ); 53 | 54 | assert_eq!(summary.unwrap(), Some(String::from("Test title"))); 55 | } 56 | 57 | #[test] 58 | fn summary_from_html_body_matching_link_full_url() { 59 | let summary = summary_from_html_body( 60 | "https://example.com/foobar", 61 | r#" 62 |
63 | First paragraph. 64 |
65 | "#, 66 | ); 67 | 68 | assert_eq!(summary.unwrap(), Some(String::from("Test title"))); 69 | } 70 | 71 | #[test] 72 | fn summary_from_html_body_matching_link_path_only() { 73 | let summary = summary_from_html_body( 74 | "https://example.com/foobar", 75 | r#" 76 |
77 | First paragraph. 78 |
79 | "#, 80 | ); 81 | 82 | assert_eq!(summary.unwrap(), Some(String::from("Test title"))); 83 | } 84 | 85 | #[test] 86 | fn summary_from_html_body_wrong_url() { 87 | let summary = summary_from_html_body( 88 | "https://example.com", 89 | r#" 90 |
91 | First paragraph. 92 |
93 | "#, 94 | ); 95 | 96 | assert_eq!(summary.unwrap(), None); 97 | } 98 | 99 | #[test] 100 | fn summary_from_html_body_improperly_escaped_url() { 101 | let summary = summary_from_html_body( 102 | "https://example.com?foo&bar", 103 | r#" 104 |
105 | First paragraph. 106 |
107 | "#, 108 | ); 109 | 110 | assert_eq!(summary.unwrap(), Some(String::from("Test title 2"))); 111 | } 112 | -------------------------------------------------------------------------------- /src/fetch/date.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, NaiveDateTime, Utc}; 2 | 3 | // Fri, 01 August 2025 07:00:00 GMT 4 | // before parsing, converted to: 5 | // 01 August 2025 07:00:00 +0000 6 | const RFC1123: &str = "%d %B %Y %H:%M:%S %z"; 7 | 8 | // Sat, 20 Sep 2025 00:00:00 9 | // before parsing, converted to: 10 | // 20 Sep 2025 00:00:00 11 | const RFC2822_NO_TZ: &str = "%d %b %Y %H:%M:%S"; 12 | 13 | // 2025-08-26 21:29:20 UTC 14 | // before parsing, converted to: 15 | // 2025-08-26 21:29:20 +0000 16 | const GITHUB_DATE: &str = "%Y-%m-%d %H:%M:%S %z"; 17 | 18 | /// Similar to feed_rs::util::parse_timestamp_lenient, but with support for GitHub's nonstandard timestamp format 19 | pub fn parse_date(raw_input: &str) -> Option> { 20 | let input = raw_input 21 | .trim_ascii() 22 | .replace("GMT", "+0000") 23 | .replace("UTC", "+0000") 24 | .replace("Mon, ", "") 25 | .replace("Tue, ", "") 26 | .replace("Wed, ", "") 27 | .replace("Thu, ", "") 28 | .replace("Fri, ", "") 29 | .replace("Sat, ", "") 30 | .replace("Sun, ", ""); 31 | 32 | let raw_timestamp = DateTime::parse_from_rfc3339(&input) 33 | .or_else(|_| DateTime::parse_from_rfc2822(&input)) 34 | .or_else(|_| { 35 | NaiveDateTime::parse_from_str(&input, RFC2822_NO_TZ) 36 | .map(|dt| dt.and_utc().fixed_offset()) 37 | }) 38 | .or_else(|_| DateTime::parse_from_str(&input, RFC1123)) 39 | .or_else(|_| DateTime::parse_from_str(&input, GITHUB_DATE)); 40 | 41 | match raw_timestamp { 42 | Ok(timestamp) => Some(timestamp.with_timezone(&Utc)), 43 | Err(_) => { 44 | tracing::debug!("Failed to parse date: '{raw_input}' -> '{input}'"); 45 | None 46 | } 47 | } 48 | } 49 | 50 | #[cfg(test)] 51 | mod tests { 52 | use super::*; 53 | 54 | #[test] 55 | fn test_parse_date() { 56 | let cases = [ 57 | // RFC3339 58 | ("2023-01-07T17:12:42+00:00", "2023-01-07T17:12:42Z"), 59 | ("2025-05-01T10:17:04.634+03:00", "2025-05-01T07:17:04.634Z"), 60 | ( 61 | "2025-08-24T12:33:09.842034+00:00", 62 | "2025-08-24T12:33:09.842034Z", 63 | ), 64 | ("2024-11-19T08:58:00Z", "2024-11-19T08:58:00Z"), 65 | ("2025-07-30T13:00:00.000Z", "2025-07-30T13:00:00Z"), 66 | // RFC2822 67 | ("Wed, 02 May 2025 07:00:00 GMT", "2025-05-02T07:00:00Z"), 68 | ("Thu, 07 Dec 2023 00:00:00 +0000", "2023-12-07T00:00:00Z"), 69 | ("Tue, 08 Apr 2025 11:03:32 +0200", "2025-04-08T09:03:32Z"), 70 | // RFC2822 (no timezone) 71 | ("Sat, 20 Sep 2025 00:00:00 ", "2025-09-20T00:00:00Z"), 72 | // RDS1123-like 73 | ("Fri, 01 August 2025 07:00:00 GMT", "2025-08-01T07:00:00Z"), 74 | // GitHub 75 | ("2025-08-26 21:29:20 UTC", "2025-08-26T21:29:20Z"), 76 | ("2025-08-26 05:37:56 -0700", "2025-08-26T12:37:56Z"), 77 | ]; 78 | 79 | for (input, expected) in cases { 80 | let parsed = parse_date(input); 81 | let expected: DateTime = expected.parse().unwrap(); 82 | assert_eq!(parsed, Some(expected), "input: '{input}'"); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/fetch.rs: -------------------------------------------------------------------------------- 1 | use crate::err::Error; 2 | use chrono::{DateTime, Utc}; 3 | use http_body_util::{BodyExt, Empty}; 4 | use hyper::body::Bytes; 5 | use hyper::header::{ACCEPT, USER_AGENT}; 6 | use hyper::{Method, Request, Uri}; 7 | use hyper_rustls::HttpsConnector; 8 | use hyper_util::client::legacy::connect::HttpConnector; 9 | use hyper_util::client::legacy::Client; 10 | use thiserror::Error; 11 | 12 | mod date; 13 | mod extract; 14 | 15 | pub type FetchClient = Client, Empty>; 16 | 17 | #[derive(Debug)] 18 | pub struct Feed { 19 | pub url: String, 20 | pub title: String, 21 | pub logo_url: Option, 22 | } 23 | 24 | #[derive(Debug)] 25 | pub struct Item { 26 | pub timestamp: DateTime, 27 | pub href: String, 28 | pub title: String, 29 | pub thumbnail_url: Option, 30 | pub summary: Option, 31 | } 32 | 33 | #[derive(Debug, Error)] 34 | enum RssError { 35 | #[error("Missing timestamp")] 36 | MissingTimestamp, 37 | #[error("Missing link")] 38 | MissingLink, 39 | #[error("Missing title")] 40 | MissingTitle, 41 | #[error("Missing feed title")] 42 | MissingFeedTitle, 43 | } 44 | 45 | pub async fn rss(client: FetchClient, url: Uri) -> Result<(Feed, Vec), Error> { 46 | let request = Request::builder() 47 | .method(Method::GET) 48 | .uri(&url) 49 | .header( 50 | ACCEPT, 51 | "application/atom+xml, application/rss+xml, application/xml;q=0.9, text/xml;q=0.8", 52 | ) 53 | .header( 54 | USER_AGENT, 55 | concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")), 56 | ) 57 | .body(Default::default())?; 58 | 59 | let response = client.request(request).await?; 60 | 61 | let url = url.to_string(); 62 | let rss = response.into_body().collect().await?.to_bytes(); 63 | 64 | let parser = feed_rs::parser::Builder::new() 65 | .base_uri(Some(&url)) 66 | .timestamp_parser(date::parse_date) 67 | .build(); 68 | 69 | let raw_feed = parser.parse(&*rss)?; 70 | 71 | let feed = Feed { 72 | url, 73 | title: raw_feed.title.ok_or(RssError::MissingFeedTitle)?.content, 74 | logo_url: raw_feed.logo.map(|l| l.uri), 75 | }; 76 | 77 | let items = raw_feed 78 | .entries 79 | .into_iter() 80 | .map(|item| { 81 | let timestamp = item 82 | .published 83 | .or(item.updated) 84 | .ok_or(RssError::MissingTimestamp)?; 85 | let href = item 86 | .links 87 | .into_iter() 88 | .next() 89 | .ok_or(RssError::MissingLink)? 90 | .href; 91 | let title = item.title.ok_or(RssError::MissingTitle)?.content; 92 | let thumbnail_url = item 93 | .media 94 | .into_iter() 95 | .next() 96 | .and_then(|m| m.thumbnails.into_iter().next()) 97 | .map(|t| t.image.uri); 98 | let summary = extract::summary(&href, item.summary, item.content)?; 99 | 100 | Ok(Item { 101 | timestamp, 102 | href, 103 | title, 104 | thumbnail_url, 105 | summary, 106 | }) 107 | }) 108 | .collect::, Error>>()?; 109 | 110 | tracing::debug!("parsed feed: {:#?}", feed); 111 | tracing::debug!("first item: {:#?}", items.first()); 112 | 113 | Ok((feed, items)) 114 | } 115 | -------------------------------------------------------------------------------- /src/fetch/extract.rs: -------------------------------------------------------------------------------- 1 | use crate::err::Error; 2 | use feed_rs::model::{Content, Text}; 3 | use mediatype::names::{HTML, TEXT}; 4 | use mediatype::MediaType; 5 | use quick_xml::escape::resolve_html5_entity; 6 | use quick_xml::events::attributes::Attribute; 7 | use quick_xml::events::Event; 8 | use quick_xml::{Decoder, Reader}; 9 | use std::borrow::Cow; 10 | use std::str; 11 | 12 | #[cfg(test)] 13 | mod tests; 14 | 15 | const TEXT_HTML: MediaType = MediaType::new(TEXT, HTML); 16 | 17 | /// Extract a summary, given an item's href and summary/content. 18 | pub fn summary( 19 | href: &str, 20 | summary: Option, 21 | content: Option, 22 | ) -> Result, Error> { 23 | // If summary is present... 24 | if let Some(summary) = summary { 25 | if summary.content_type.essence() == TEXT_HTML { 26 | // ...use heuristics to extract summary from HTML. 27 | return summary_from_html_summary(&summary.content); 28 | } else { 29 | // ...assume plaintext summary. 30 | return Ok(Some(summary.content)); 31 | } 32 | } 33 | 34 | // If content is present... 35 | if let Some(content) = content { 36 | if let Some(body) = content.body { 37 | if content.content_type.essence() == TEXT_HTML { 38 | // ...use heuristics to extract content from HTML. 39 | return summary_from_html_body(href, &body); 40 | } else { 41 | // ...assume plaintext content. 42 | return Ok(Some(body)); 43 | } 44 | } 45 | } 46 | 47 | Ok(None) 48 | } 49 | 50 | fn summary_from_html_summary(summary: &str) -> Result, Error> { 51 | let mut reader = Reader::from_str(summary); 52 | reader.config_mut().trim_text(true); 53 | // HTML doesn't require self-closing tags to be formatted properly, 54 | // e.g. you can do . 55 | reader.config_mut().check_end_names = false; 56 | reader.config_mut().allow_unmatched_ends = true; 57 | 58 | let mut text_content: Option = None; 59 | 60 | loop { 61 | // Accumulate the first block of text content, including embedded char refs (e.g. &). 62 | match reader.read_event()? { 63 | Event::Eof => { 64 | break; 65 | } 66 | Event::Text(text) => { 67 | let text = text.html_content()?; 68 | text_content.get_or_insert_default().push_str(&text); 69 | } 70 | Event::GeneralRef(ref_) => { 71 | let ref_ = ref_.decode()?; 72 | let resolved = match resolve_html5_entity(&ref_) { 73 | Some(resolved) => resolved, 74 | None => &format!("&{};", ref_), 75 | }; 76 | text_content.get_or_insert_default().push_str(resolved); 77 | } 78 | _ => { 79 | if text_content.is_some() { 80 | // If we've already seen text, stop looking when we hit a new tag. 81 | break; 82 | } 83 | } 84 | } 85 | } 86 | 87 | Ok(text_content) 88 | } 89 | 90 | fn summary_from_html_body(item_href: &str, body: &str) -> Result, Error> { 91 | let mut reader = Reader::from_str(body); 92 | reader.config_mut().trim_text(true); 93 | // HTML doesn't require self-closing tags to be formatted properly, 94 | // e.g. you can do . 95 | reader.config_mut().check_end_names = false; 96 | reader.config_mut().allow_unmatched_ends = true; 97 | 98 | loop { 99 | match reader.read_event()? { 100 | Event::Eof => { 101 | break; 102 | } 103 | Event::Start(tag) if tag.name().as_ref() == b"a" => { 104 | // If this is a link to the item itself... 105 | let mut found_matching_link = false; 106 | let mut title_attr = None; 107 | 108 | for attr in tag.html_attributes() { 109 | let attr = attr?; 110 | match attr.key.as_ref() { 111 | b"href" => { 112 | let href = attr_value(attr, reader.decoder())?; 113 | if href == item_href { 114 | found_matching_link = true; 115 | } 116 | // Handle relative URLs. 117 | if let Some(item_href_path) = url_path(item_href) { 118 | if href == item_href_path { 119 | found_matching_link = true; 120 | } 121 | } 122 | } 123 | b"title" => { 124 | title_attr = Some(attr); 125 | } 126 | _ => {} 127 | } 128 | } 129 | if found_matching_link { 130 | // ...then return the title of that link, if it has one. 131 | if let Some(title_attr) = title_attr { 132 | let title = attr_value(title_attr, reader.decoder())?; 133 | return Ok(Some(title.into_owned())); 134 | } 135 | } 136 | } 137 | Event::Text(_) => {} 138 | _ => {} 139 | } 140 | } 141 | 142 | Ok(None) 143 | } 144 | 145 | fn attr_value(attr: Attribute<'_>, decoder: Decoder) -> Result, Error> { 146 | // Try to properly decode the value 147 | if let Ok(value) = attr.decode_and_unescape_value(decoder) { 148 | return Ok(value); 149 | } 150 | 151 | // Some feeds use unescaped entities in their URLs; just use the raw content in that case. 152 | match attr.value { 153 | Cow::Borrowed(value) => Ok(Cow::Borrowed(str::from_utf8(value)?)), 154 | Cow::Owned(value) => Ok(Cow::Owned(String::from_utf8(value)?)), 155 | } 156 | } 157 | 158 | fn url_path(url: &str) -> Option<&str> { 159 | let url = url 160 | .strip_prefix("http://") 161 | .or_else(|| url.strip_prefix("https://"))?; 162 | Some(&url[url.find('/')?..]) 163 | } 164 | -------------------------------------------------------------------------------- /src/server/routes.rs: -------------------------------------------------------------------------------- 1 | use crate::err::ResponseError; 2 | use crate::fetch::{self, Feed, Item}; 3 | use crate::server::AppState; 4 | use crate::url; 5 | use axum::extract::{RawQuery, State}; 6 | use axum::response::{Html, IntoResponse}; 7 | use base64::prelude::BASE64_URL_SAFE; 8 | use base64::Engine; 9 | use chrono::{Local, NaiveDate}; 10 | use hyper::Uri; 11 | use std::cmp; 12 | use std::collections::hash_map::Entry; 13 | use std::collections::HashMap; 14 | use std::sync::Arc; 15 | use thiserror::Error; 16 | use tokio::task::JoinSet; 17 | 18 | #[derive(Debug, Error)] 19 | enum GetError { 20 | #[error("no URLs provided in query string")] 21 | NoUrls, 22 | } 23 | 24 | /// Load a list of RSS feeds, provided as query params, and display the results in chronological order. 25 | /// 26 | /// e.g. `http://localhost:3000/?https://www.rust-lang.org/feeds/releases.xml&https://blog.rust-lang.org/feed.xml` 27 | pub async fn index( 28 | State(state): State>, 29 | RawQuery(params): RawQuery, 30 | ) -> Result { 31 | let Some(params) = params else { 32 | return Err(GetError::NoUrls.into()); 33 | }; 34 | 35 | // Start all the requests concurrently... 36 | let mut pending_feeds = JoinSet::new(); 37 | for url in params.split('&') { 38 | let url: Uri = url.parse()?; 39 | let feed = fetch::rss(state.client.clone(), url.clone()); 40 | pending_feeds.spawn(async { (url, feed.await) }); 41 | } 42 | 43 | // ...and wait for them to finish. 44 | let mut feed_errors = Vec::new(); 45 | let mut all_feeds = Vec::new(); 46 | while let Some(result) = pending_feeds.join_next().await { 47 | let (url, result) = result?; 48 | match result { 49 | Err(e) => { 50 | feed_errors.push((url, e)); 51 | } 52 | Ok((feed, items)) => { 53 | all_feeds.push((feed, items)); 54 | } 55 | } 56 | } 57 | 58 | // Collect all items into one vec, sorted by date. 59 | let mut all_items = Vec::new(); 60 | for (feed, items) in &mut all_feeds { 61 | // Carry along a reference to each item's feed. 62 | all_items.extend(items.drain(..).map(|item| (&*feed, item))); 63 | } 64 | all_items.sort_by_key(|(_, item)| cmp::Reverse(item.timestamp)); 65 | 66 | // Drop items that point to a prefix of the feed URL. 67 | all_items.retain(|(feed, item)| { 68 | let drop = feed.url.starts_with(url::prefix(&item.href)); 69 | if drop { 70 | // This happens for some unimportant GitHub events, e.g. branch deletion, 71 | // which just point to the GitHub homepage. 72 | tracing::debug!("dropping item: {:#?}", item); 73 | } 74 | !drop 75 | }); 76 | 77 | // Split items into one vec per day, and deduplicate them. 78 | 79 | struct Day<'a> { 80 | date: NaiveDate, 81 | items: Vec>, 82 | } 83 | 84 | struct ItemsWithFeed<'a> { 85 | feed: &'a Feed, 86 | item: Item, 87 | count: usize, 88 | highlighted: bool, 89 | } 90 | 91 | let mut days = Vec::::new(); 92 | 93 | { 94 | let mut url_prefix_to_index = HashMap::::new(); 95 | for (feed, item) in all_items { 96 | // Check whether we need to start a new day. 97 | let date = item.timestamp.with_timezone(&Local).date_naive(); 98 | if days.last().map(|d| d.date) != Some(date) { 99 | days.push(Day { 100 | date, 101 | items: Default::default(), 102 | }); 103 | url_prefix_to_index.clear(); 104 | } 105 | // Get or insert this item. 106 | let day = days.last_mut().unwrap(); 107 | // Strip hash from the URL, so multiple links to the same page (but e.g. for different events with different anchors) are deduplicated. 108 | let url_prefix = url::prefix(&item.href); 109 | match url_prefix_to_index.entry(url_prefix.to_owned()) { 110 | // If we've already seen this item, increment its count, 111 | // and override the entry (so the oldest entry is used). 112 | Entry::Occupied(entry) => { 113 | let index = *entry.get(); 114 | day.items[index].item = item; 115 | day.items[index].count += 1; 116 | } 117 | // Otherwise, add a new item. 118 | Entry::Vacant(entry) => { 119 | let index = day.items.len(); 120 | day.items.push(ItemsWithFeed { 121 | feed, 122 | item, 123 | count: 1, 124 | highlighted: false, 125 | }); 126 | entry.insert(index); 127 | } 128 | } 129 | } 130 | } 131 | 132 | // Highlight unique domains for the day. 133 | { 134 | let mut domain_to_entry_count = HashMap::::new(); 135 | for day in &mut days { 136 | domain_to_entry_count.clear(); 137 | 138 | // Collect counts by domain. 139 | for i in &mut day.items { 140 | let domain = url::domain(&i.item.href); 141 | *domain_to_entry_count.entry(domain.to_owned()).or_default() += 1; 142 | } 143 | 144 | let max_count = domain_to_entry_count.values().max().copied().unwrap_or(0); 145 | 146 | // Mark the items with the lowest count as highlighted. 147 | for i in &mut day.items { 148 | let domain = url::domain(&i.item.href); 149 | let entry_count = domain_to_entry_count[domain]; 150 | // Highlight if there are fewer than 3 entries with this domain, and there are less entries than the most common domain. 151 | i.highlighted = entry_count < 3 && entry_count < max_count; 152 | } 153 | } 154 | } 155 | 156 | let nonce = BASE64_URL_SAFE.encode(rand::random::<[u8; 16]>()); 157 | 158 | let mut html = format!( 159 | r#" 160 | 161 | 162 | 163 | 164 | 165 | 184 | 185 | 186 |
    187 | "#, 188 | ); 189 | 190 | if !feed_errors.is_empty() { 191 | html.push_str("

    Errors

    "); 192 | for (url, e) in feed_errors { 193 | html.push_str(&format!( 194 | r#"
  • {}
    └ {}
  • "#, 195 | url, url, e 196 | )); 197 | } 198 | } 199 | 200 | for day in days { 201 | html.push_str(&format!("

    {}

    ", day.date)); 202 | 203 | for i in day.items { 204 | let (thumbnail, spacer) = if let Some(thumbnail_url) = 205 | i.item.thumbnail_url.as_ref().or(i.feed.logo_url.as_ref()) 206 | { 207 | ( 208 | format!( 209 | r#" "#, 210 | thumbnail_url, i.feed.title 211 | ), 212 | r#" "#, 213 | ) 214 | } else { 215 | Default::default() 216 | }; 217 | let item_count = if i.count > 1 { 218 | format!(" ({}x)", i.count) 219 | } else { 220 | String::new() 221 | }; 222 | let summary = if let Some(summary) = i.item.summary { 223 | format!(r#"
    {}└ {}"#, spacer, summary) 224 | } else { 225 | String::new() 226 | }; 227 | html.push_str(&format!( 228 | r#"
  • {}{}{}{}
  • "#, 229 | thumbnail, 230 | if i.highlighted { "highlight" } else { "" }, 231 | i.item.href, 232 | i.item.title, 233 | item_count, 234 | summary 235 | )); 236 | } 237 | } 238 | 239 | Ok(Html(html)) 240 | } 241 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "alloc-no-stdlib" 31 | version = "2.0.4" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" 34 | 35 | [[package]] 36 | name = "alloc-stdlib" 37 | version = "0.2.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" 40 | dependencies = [ 41 | "alloc-no-stdlib", 42 | ] 43 | 44 | [[package]] 45 | name = "android-tzdata" 46 | version = "0.1.1" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 49 | 50 | [[package]] 51 | name = "android_system_properties" 52 | version = "0.1.5" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 55 | dependencies = [ 56 | "libc", 57 | ] 58 | 59 | [[package]] 60 | name = "anstream" 61 | version = "0.6.20" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" 64 | dependencies = [ 65 | "anstyle", 66 | "anstyle-parse", 67 | "anstyle-query", 68 | "anstyle-wincon", 69 | "colorchoice", 70 | "is_terminal_polyfill", 71 | "utf8parse", 72 | ] 73 | 74 | [[package]] 75 | name = "anstyle" 76 | version = "1.0.11" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" 79 | 80 | [[package]] 81 | name = "anstyle-parse" 82 | version = "0.2.7" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" 85 | dependencies = [ 86 | "utf8parse", 87 | ] 88 | 89 | [[package]] 90 | name = "anstyle-query" 91 | version = "1.1.4" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" 94 | dependencies = [ 95 | "windows-sys 0.60.2", 96 | ] 97 | 98 | [[package]] 99 | name = "anstyle-wincon" 100 | version = "3.0.10" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" 103 | dependencies = [ 104 | "anstyle", 105 | "once_cell_polyfill", 106 | "windows-sys 0.60.2", 107 | ] 108 | 109 | [[package]] 110 | name = "async-compression" 111 | version = "0.4.28" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "6448dfb3960f0b038e88c781ead1e7eb7929dfc3a71a1336ec9086c00f6d1e75" 114 | dependencies = [ 115 | "brotli", 116 | "compression-codecs", 117 | "compression-core", 118 | "futures-core", 119 | "memchr", 120 | "pin-project-lite", 121 | "tokio", 122 | ] 123 | 124 | [[package]] 125 | name = "atomic-waker" 126 | version = "1.1.2" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 129 | 130 | [[package]] 131 | name = "autocfg" 132 | version = "1.5.0" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 135 | 136 | [[package]] 137 | name = "axum" 138 | version = "0.8.4" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" 141 | dependencies = [ 142 | "axum-core", 143 | "bytes", 144 | "futures-util", 145 | "http", 146 | "http-body", 147 | "http-body-util", 148 | "hyper", 149 | "hyper-util", 150 | "itoa", 151 | "matchit", 152 | "memchr", 153 | "mime", 154 | "percent-encoding", 155 | "pin-project-lite", 156 | "rustversion", 157 | "serde", 158 | "sync_wrapper", 159 | "tokio", 160 | "tower", 161 | "tower-layer", 162 | "tower-service", 163 | "tracing", 164 | ] 165 | 166 | [[package]] 167 | name = "axum-core" 168 | version = "0.5.2" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" 171 | dependencies = [ 172 | "bytes", 173 | "futures-core", 174 | "http", 175 | "http-body", 176 | "http-body-util", 177 | "mime", 178 | "pin-project-lite", 179 | "rustversion", 180 | "sync_wrapper", 181 | "tower-layer", 182 | "tower-service", 183 | "tracing", 184 | ] 185 | 186 | [[package]] 187 | name = "backtrace" 188 | version = "0.3.75" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" 191 | dependencies = [ 192 | "addr2line", 193 | "cfg-if", 194 | "libc", 195 | "miniz_oxide", 196 | "object", 197 | "rustc-demangle", 198 | "windows-targets 0.52.6", 199 | ] 200 | 201 | [[package]] 202 | name = "base64" 203 | version = "0.22.1" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 206 | 207 | [[package]] 208 | name = "bitflags" 209 | version = "2.9.3" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" 212 | 213 | [[package]] 214 | name = "brotli" 215 | version = "8.0.2" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" 218 | dependencies = [ 219 | "alloc-no-stdlib", 220 | "alloc-stdlib", 221 | "brotli-decompressor", 222 | ] 223 | 224 | [[package]] 225 | name = "brotli-decompressor" 226 | version = "5.0.0" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" 229 | dependencies = [ 230 | "alloc-no-stdlib", 231 | "alloc-stdlib", 232 | ] 233 | 234 | [[package]] 235 | name = "bumpalo" 236 | version = "3.19.0" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 239 | 240 | [[package]] 241 | name = "bytes" 242 | version = "1.10.1" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 245 | 246 | [[package]] 247 | name = "cc" 248 | version = "1.2.34" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" 251 | dependencies = [ 252 | "shlex", 253 | ] 254 | 255 | [[package]] 256 | name = "cfg-if" 257 | version = "1.0.3" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" 260 | 261 | [[package]] 262 | name = "chrono" 263 | version = "0.4.41" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" 266 | dependencies = [ 267 | "android-tzdata", 268 | "iana-time-zone", 269 | "js-sys", 270 | "num-traits", 271 | "serde", 272 | "wasm-bindgen", 273 | "windows-link", 274 | ] 275 | 276 | [[package]] 277 | name = "clap" 278 | version = "4.5.46" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57" 281 | dependencies = [ 282 | "clap_builder", 283 | "clap_derive", 284 | ] 285 | 286 | [[package]] 287 | name = "clap_builder" 288 | version = "4.5.46" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41" 291 | dependencies = [ 292 | "anstream", 293 | "anstyle", 294 | "clap_lex", 295 | "strsim", 296 | ] 297 | 298 | [[package]] 299 | name = "clap_derive" 300 | version = "4.5.45" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" 303 | dependencies = [ 304 | "heck", 305 | "proc-macro2", 306 | "quote", 307 | "syn", 308 | ] 309 | 310 | [[package]] 311 | name = "clap_lex" 312 | version = "0.7.5" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" 315 | 316 | [[package]] 317 | name = "colorchoice" 318 | version = "1.0.4" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 321 | 322 | [[package]] 323 | name = "compression-codecs" 324 | version = "0.4.28" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "46cc6539bf1c592cff488b9f253b30bc0ec50d15407c2cf45e27bd8f308d5905" 327 | dependencies = [ 328 | "brotli", 329 | "compression-core", 330 | "futures-core", 331 | "memchr", 332 | "pin-project-lite", 333 | ] 334 | 335 | [[package]] 336 | name = "compression-core" 337 | version = "0.4.28" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "2957e823c15bde7ecf1e8b64e537aa03a6be5fda0e2334e99887669e75b12e01" 340 | 341 | [[package]] 342 | name = "core-foundation" 343 | version = "0.10.1" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" 346 | dependencies = [ 347 | "core-foundation-sys", 348 | "libc", 349 | ] 350 | 351 | [[package]] 352 | name = "core-foundation-sys" 353 | version = "0.8.7" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 356 | 357 | [[package]] 358 | name = "displaydoc" 359 | version = "0.2.5" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 362 | dependencies = [ 363 | "proc-macro2", 364 | "quote", 365 | "syn", 366 | ] 367 | 368 | [[package]] 369 | name = "encoding_rs" 370 | version = "0.8.35" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 373 | dependencies = [ 374 | "cfg-if", 375 | ] 376 | 377 | [[package]] 378 | name = "equivalent" 379 | version = "1.0.2" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 382 | 383 | [[package]] 384 | name = "feed-rs" 385 | version = "2.3.1" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "e4c0591d23efd0d595099af69a31863ac1823046b1b021e3b06ba3aae7e00991" 388 | dependencies = [ 389 | "chrono", 390 | "mediatype", 391 | "quick-xml 0.37.5", 392 | "regex", 393 | "serde", 394 | "serde_json", 395 | "siphasher", 396 | "url", 397 | "uuid", 398 | ] 399 | 400 | [[package]] 401 | name = "fnv" 402 | version = "1.0.7" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 405 | 406 | [[package]] 407 | name = "form_urlencoded" 408 | version = "1.2.2" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" 411 | dependencies = [ 412 | "percent-encoding", 413 | ] 414 | 415 | [[package]] 416 | name = "futures-channel" 417 | version = "0.3.31" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 420 | dependencies = [ 421 | "futures-core", 422 | ] 423 | 424 | [[package]] 425 | name = "futures-core" 426 | version = "0.3.31" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 429 | 430 | [[package]] 431 | name = "futures-sink" 432 | version = "0.3.31" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 435 | 436 | [[package]] 437 | name = "futures-task" 438 | version = "0.3.31" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 441 | 442 | [[package]] 443 | name = "futures-util" 444 | version = "0.3.31" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 447 | dependencies = [ 448 | "futures-core", 449 | "futures-task", 450 | "pin-project-lite", 451 | "pin-utils", 452 | ] 453 | 454 | [[package]] 455 | name = "getrandom" 456 | version = "0.2.16" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 459 | dependencies = [ 460 | "cfg-if", 461 | "libc", 462 | "wasi 0.11.1+wasi-snapshot-preview1", 463 | ] 464 | 465 | [[package]] 466 | name = "getrandom" 467 | version = "0.3.3" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" 470 | dependencies = [ 471 | "cfg-if", 472 | "libc", 473 | "r-efi", 474 | "wasi 0.14.2+wasi-0.2.4", 475 | ] 476 | 477 | [[package]] 478 | name = "gimli" 479 | version = "0.31.1" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 482 | 483 | [[package]] 484 | name = "h2" 485 | version = "0.4.12" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" 488 | dependencies = [ 489 | "atomic-waker", 490 | "bytes", 491 | "fnv", 492 | "futures-core", 493 | "futures-sink", 494 | "http", 495 | "indexmap", 496 | "slab", 497 | "tokio", 498 | "tokio-util", 499 | "tracing", 500 | ] 501 | 502 | [[package]] 503 | name = "hashbrown" 504 | version = "0.15.5" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" 507 | 508 | [[package]] 509 | name = "heck" 510 | version = "0.5.0" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 513 | 514 | [[package]] 515 | name = "http" 516 | version = "1.3.1" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" 519 | dependencies = [ 520 | "bytes", 521 | "fnv", 522 | "itoa", 523 | ] 524 | 525 | [[package]] 526 | name = "http-body" 527 | version = "1.0.1" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 530 | dependencies = [ 531 | "bytes", 532 | "http", 533 | ] 534 | 535 | [[package]] 536 | name = "http-body-util" 537 | version = "0.1.3" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" 540 | dependencies = [ 541 | "bytes", 542 | "futures-core", 543 | "http", 544 | "http-body", 545 | "pin-project-lite", 546 | ] 547 | 548 | [[package]] 549 | name = "httparse" 550 | version = "1.10.1" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 553 | 554 | [[package]] 555 | name = "httpdate" 556 | version = "1.0.3" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 559 | 560 | [[package]] 561 | name = "hyper" 562 | version = "1.7.0" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" 565 | dependencies = [ 566 | "atomic-waker", 567 | "bytes", 568 | "futures-channel", 569 | "futures-core", 570 | "h2", 571 | "http", 572 | "http-body", 573 | "httparse", 574 | "httpdate", 575 | "itoa", 576 | "pin-project-lite", 577 | "pin-utils", 578 | "smallvec", 579 | "tokio", 580 | "want", 581 | ] 582 | 583 | [[package]] 584 | name = "hyper-rustls" 585 | version = "0.27.7" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" 588 | dependencies = [ 589 | "http", 590 | "hyper", 591 | "hyper-util", 592 | "log", 593 | "rustls", 594 | "rustls-native-certs", 595 | "rustls-pki-types", 596 | "tokio", 597 | "tokio-rustls", 598 | "tower-service", 599 | ] 600 | 601 | [[package]] 602 | name = "hyper-util" 603 | version = "0.1.16" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" 606 | dependencies = [ 607 | "bytes", 608 | "futures-channel", 609 | "futures-core", 610 | "futures-util", 611 | "http", 612 | "http-body", 613 | "hyper", 614 | "libc", 615 | "pin-project-lite", 616 | "socket2", 617 | "tokio", 618 | "tower-service", 619 | "tracing", 620 | ] 621 | 622 | [[package]] 623 | name = "iana-time-zone" 624 | version = "0.1.63" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" 627 | dependencies = [ 628 | "android_system_properties", 629 | "core-foundation-sys", 630 | "iana-time-zone-haiku", 631 | "js-sys", 632 | "log", 633 | "wasm-bindgen", 634 | "windows-core", 635 | ] 636 | 637 | [[package]] 638 | name = "iana-time-zone-haiku" 639 | version = "0.1.2" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 642 | dependencies = [ 643 | "cc", 644 | ] 645 | 646 | [[package]] 647 | name = "icu_collections" 648 | version = "2.0.0" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" 651 | dependencies = [ 652 | "displaydoc", 653 | "potential_utf", 654 | "yoke", 655 | "zerofrom", 656 | "zerovec", 657 | ] 658 | 659 | [[package]] 660 | name = "icu_locale_core" 661 | version = "2.0.0" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" 664 | dependencies = [ 665 | "displaydoc", 666 | "litemap", 667 | "tinystr", 668 | "writeable", 669 | "zerovec", 670 | ] 671 | 672 | [[package]] 673 | name = "icu_normalizer" 674 | version = "2.0.0" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" 677 | dependencies = [ 678 | "displaydoc", 679 | "icu_collections", 680 | "icu_normalizer_data", 681 | "icu_properties", 682 | "icu_provider", 683 | "smallvec", 684 | "zerovec", 685 | ] 686 | 687 | [[package]] 688 | name = "icu_normalizer_data" 689 | version = "2.0.0" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" 692 | 693 | [[package]] 694 | name = "icu_properties" 695 | version = "2.0.1" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" 698 | dependencies = [ 699 | "displaydoc", 700 | "icu_collections", 701 | "icu_locale_core", 702 | "icu_properties_data", 703 | "icu_provider", 704 | "potential_utf", 705 | "zerotrie", 706 | "zerovec", 707 | ] 708 | 709 | [[package]] 710 | name = "icu_properties_data" 711 | version = "2.0.1" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" 714 | 715 | [[package]] 716 | name = "icu_provider" 717 | version = "2.0.0" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" 720 | dependencies = [ 721 | "displaydoc", 722 | "icu_locale_core", 723 | "stable_deref_trait", 724 | "tinystr", 725 | "writeable", 726 | "yoke", 727 | "zerofrom", 728 | "zerotrie", 729 | "zerovec", 730 | ] 731 | 732 | [[package]] 733 | name = "idna" 734 | version = "1.1.0" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" 737 | dependencies = [ 738 | "idna_adapter", 739 | "smallvec", 740 | "utf8_iter", 741 | ] 742 | 743 | [[package]] 744 | name = "idna_adapter" 745 | version = "1.2.1" 746 | source = "registry+https://github.com/rust-lang/crates.io-index" 747 | checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 748 | dependencies = [ 749 | "icu_normalizer", 750 | "icu_properties", 751 | ] 752 | 753 | [[package]] 754 | name = "indexmap" 755 | version = "2.11.0" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" 758 | dependencies = [ 759 | "equivalent", 760 | "hashbrown", 761 | ] 762 | 763 | [[package]] 764 | name = "io-uring" 765 | version = "0.7.10" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" 768 | dependencies = [ 769 | "bitflags", 770 | "cfg-if", 771 | "libc", 772 | ] 773 | 774 | [[package]] 775 | name = "is_terminal_polyfill" 776 | version = "1.70.1" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 779 | 780 | [[package]] 781 | name = "itoa" 782 | version = "1.0.15" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 785 | 786 | [[package]] 787 | name = "js-sys" 788 | version = "0.3.77" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 791 | dependencies = [ 792 | "once_cell", 793 | "wasm-bindgen", 794 | ] 795 | 796 | [[package]] 797 | name = "katamari" 798 | version = "0.6.7" 799 | dependencies = [ 800 | "axum", 801 | "base64", 802 | "chrono", 803 | "clap", 804 | "feed-rs", 805 | "http-body-util", 806 | "hyper", 807 | "hyper-rustls", 808 | "hyper-util", 809 | "mediatype", 810 | "quick-xml 0.38.3", 811 | "rand", 812 | "thiserror", 813 | "tokio", 814 | "tower", 815 | "tower-http", 816 | "tracing", 817 | "tracing-subscriber", 818 | ] 819 | 820 | [[package]] 821 | name = "lazy_static" 822 | version = "1.5.0" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 825 | 826 | [[package]] 827 | name = "libc" 828 | version = "0.2.175" 829 | source = "registry+https://github.com/rust-lang/crates.io-index" 830 | checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" 831 | 832 | [[package]] 833 | name = "litemap" 834 | version = "0.8.0" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" 837 | 838 | [[package]] 839 | name = "log" 840 | version = "0.4.27" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 843 | 844 | [[package]] 845 | name = "matchit" 846 | version = "0.8.4" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" 849 | 850 | [[package]] 851 | name = "mediatype" 852 | version = "0.19.20" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | checksum = "33746aadcb41349ec291e7f2f0a3aa6834d1d7c58066fb4b01f68efc4c4b7631" 855 | dependencies = [ 856 | "serde", 857 | ] 858 | 859 | [[package]] 860 | name = "memchr" 861 | version = "2.7.5" 862 | source = "registry+https://github.com/rust-lang/crates.io-index" 863 | checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" 864 | 865 | [[package]] 866 | name = "mime" 867 | version = "0.3.17" 868 | source = "registry+https://github.com/rust-lang/crates.io-index" 869 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 870 | 871 | [[package]] 872 | name = "miniz_oxide" 873 | version = "0.8.9" 874 | source = "registry+https://github.com/rust-lang/crates.io-index" 875 | checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" 876 | dependencies = [ 877 | "adler2", 878 | ] 879 | 880 | [[package]] 881 | name = "mio" 882 | version = "1.0.4" 883 | source = "registry+https://github.com/rust-lang/crates.io-index" 884 | checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" 885 | dependencies = [ 886 | "libc", 887 | "wasi 0.11.1+wasi-snapshot-preview1", 888 | "windows-sys 0.59.0", 889 | ] 890 | 891 | [[package]] 892 | name = "nu-ansi-term" 893 | version = "0.46.0" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 896 | dependencies = [ 897 | "overload", 898 | "winapi", 899 | ] 900 | 901 | [[package]] 902 | name = "num-traits" 903 | version = "0.2.19" 904 | source = "registry+https://github.com/rust-lang/crates.io-index" 905 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 906 | dependencies = [ 907 | "autocfg", 908 | ] 909 | 910 | [[package]] 911 | name = "object" 912 | version = "0.36.7" 913 | source = "registry+https://github.com/rust-lang/crates.io-index" 914 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 915 | dependencies = [ 916 | "memchr", 917 | ] 918 | 919 | [[package]] 920 | name = "once_cell" 921 | version = "1.21.3" 922 | source = "registry+https://github.com/rust-lang/crates.io-index" 923 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 924 | 925 | [[package]] 926 | name = "once_cell_polyfill" 927 | version = "1.70.1" 928 | source = "registry+https://github.com/rust-lang/crates.io-index" 929 | checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" 930 | 931 | [[package]] 932 | name = "openssl-probe" 933 | version = "0.1.6" 934 | source = "registry+https://github.com/rust-lang/crates.io-index" 935 | checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 936 | 937 | [[package]] 938 | name = "overload" 939 | version = "0.1.1" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 942 | 943 | [[package]] 944 | name = "percent-encoding" 945 | version = "2.3.2" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 948 | 949 | [[package]] 950 | name = "pin-project-lite" 951 | version = "0.2.16" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 954 | 955 | [[package]] 956 | name = "pin-utils" 957 | version = "0.1.0" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 960 | 961 | [[package]] 962 | name = "potential_utf" 963 | version = "0.1.2" 964 | source = "registry+https://github.com/rust-lang/crates.io-index" 965 | checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" 966 | dependencies = [ 967 | "zerovec", 968 | ] 969 | 970 | [[package]] 971 | name = "ppv-lite86" 972 | version = "0.2.21" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 975 | dependencies = [ 976 | "zerocopy", 977 | ] 978 | 979 | [[package]] 980 | name = "proc-macro2" 981 | version = "1.0.101" 982 | source = "registry+https://github.com/rust-lang/crates.io-index" 983 | checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" 984 | dependencies = [ 985 | "unicode-ident", 986 | ] 987 | 988 | [[package]] 989 | name = "quick-xml" 990 | version = "0.37.5" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" 993 | dependencies = [ 994 | "encoding_rs", 995 | "memchr", 996 | ] 997 | 998 | [[package]] 999 | name = "quick-xml" 1000 | version = "0.38.3" 1001 | source = "registry+https://github.com/rust-lang/crates.io-index" 1002 | checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" 1003 | dependencies = [ 1004 | "memchr", 1005 | ] 1006 | 1007 | [[package]] 1008 | name = "quote" 1009 | version = "1.0.40" 1010 | source = "registry+https://github.com/rust-lang/crates.io-index" 1011 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 1012 | dependencies = [ 1013 | "proc-macro2", 1014 | ] 1015 | 1016 | [[package]] 1017 | name = "r-efi" 1018 | version = "5.3.0" 1019 | source = "registry+https://github.com/rust-lang/crates.io-index" 1020 | checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" 1021 | 1022 | [[package]] 1023 | name = "rand" 1024 | version = "0.9.2" 1025 | source = "registry+https://github.com/rust-lang/crates.io-index" 1026 | checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" 1027 | dependencies = [ 1028 | "rand_chacha", 1029 | "rand_core", 1030 | ] 1031 | 1032 | [[package]] 1033 | name = "rand_chacha" 1034 | version = "0.9.0" 1035 | source = "registry+https://github.com/rust-lang/crates.io-index" 1036 | checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 1037 | dependencies = [ 1038 | "ppv-lite86", 1039 | "rand_core", 1040 | ] 1041 | 1042 | [[package]] 1043 | name = "rand_core" 1044 | version = "0.9.3" 1045 | source = "registry+https://github.com/rust-lang/crates.io-index" 1046 | checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 1047 | dependencies = [ 1048 | "getrandom 0.3.3", 1049 | ] 1050 | 1051 | [[package]] 1052 | name = "regex" 1053 | version = "1.11.2" 1054 | source = "registry+https://github.com/rust-lang/crates.io-index" 1055 | checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" 1056 | dependencies = [ 1057 | "aho-corasick", 1058 | "memchr", 1059 | "regex-automata", 1060 | "regex-syntax", 1061 | ] 1062 | 1063 | [[package]] 1064 | name = "regex-automata" 1065 | version = "0.4.10" 1066 | source = "registry+https://github.com/rust-lang/crates.io-index" 1067 | checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" 1068 | dependencies = [ 1069 | "aho-corasick", 1070 | "memchr", 1071 | "regex-syntax", 1072 | ] 1073 | 1074 | [[package]] 1075 | name = "regex-syntax" 1076 | version = "0.8.6" 1077 | source = "registry+https://github.com/rust-lang/crates.io-index" 1078 | checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" 1079 | 1080 | [[package]] 1081 | name = "ring" 1082 | version = "0.17.14" 1083 | source = "registry+https://github.com/rust-lang/crates.io-index" 1084 | checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 1085 | dependencies = [ 1086 | "cc", 1087 | "cfg-if", 1088 | "getrandom 0.2.16", 1089 | "libc", 1090 | "untrusted", 1091 | "windows-sys 0.52.0", 1092 | ] 1093 | 1094 | [[package]] 1095 | name = "rustc-demangle" 1096 | version = "0.1.26" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" 1099 | 1100 | [[package]] 1101 | name = "rustls" 1102 | version = "0.23.31" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" 1105 | dependencies = [ 1106 | "log", 1107 | "once_cell", 1108 | "ring", 1109 | "rustls-pki-types", 1110 | "rustls-webpki", 1111 | "subtle", 1112 | "zeroize", 1113 | ] 1114 | 1115 | [[package]] 1116 | name = "rustls-native-certs" 1117 | version = "0.8.1" 1118 | source = "registry+https://github.com/rust-lang/crates.io-index" 1119 | checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" 1120 | dependencies = [ 1121 | "openssl-probe", 1122 | "rustls-pki-types", 1123 | "schannel", 1124 | "security-framework", 1125 | ] 1126 | 1127 | [[package]] 1128 | name = "rustls-pki-types" 1129 | version = "1.12.0" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" 1132 | dependencies = [ 1133 | "zeroize", 1134 | ] 1135 | 1136 | [[package]] 1137 | name = "rustls-webpki" 1138 | version = "0.103.4" 1139 | source = "registry+https://github.com/rust-lang/crates.io-index" 1140 | checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" 1141 | dependencies = [ 1142 | "ring", 1143 | "rustls-pki-types", 1144 | "untrusted", 1145 | ] 1146 | 1147 | [[package]] 1148 | name = "rustversion" 1149 | version = "1.0.22" 1150 | source = "registry+https://github.com/rust-lang/crates.io-index" 1151 | checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 1152 | 1153 | [[package]] 1154 | name = "ryu" 1155 | version = "1.0.20" 1156 | source = "registry+https://github.com/rust-lang/crates.io-index" 1157 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 1158 | 1159 | [[package]] 1160 | name = "schannel" 1161 | version = "0.1.27" 1162 | source = "registry+https://github.com/rust-lang/crates.io-index" 1163 | checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" 1164 | dependencies = [ 1165 | "windows-sys 0.59.0", 1166 | ] 1167 | 1168 | [[package]] 1169 | name = "security-framework" 1170 | version = "3.3.0" 1171 | source = "registry+https://github.com/rust-lang/crates.io-index" 1172 | checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c" 1173 | dependencies = [ 1174 | "bitflags", 1175 | "core-foundation", 1176 | "core-foundation-sys", 1177 | "libc", 1178 | "security-framework-sys", 1179 | ] 1180 | 1181 | [[package]] 1182 | name = "security-framework-sys" 1183 | version = "2.14.0" 1184 | source = "registry+https://github.com/rust-lang/crates.io-index" 1185 | checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" 1186 | dependencies = [ 1187 | "core-foundation-sys", 1188 | "libc", 1189 | ] 1190 | 1191 | [[package]] 1192 | name = "serde" 1193 | version = "1.0.219" 1194 | source = "registry+https://github.com/rust-lang/crates.io-index" 1195 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 1196 | dependencies = [ 1197 | "serde_derive", 1198 | ] 1199 | 1200 | [[package]] 1201 | name = "serde_derive" 1202 | version = "1.0.219" 1203 | source = "registry+https://github.com/rust-lang/crates.io-index" 1204 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 1205 | dependencies = [ 1206 | "proc-macro2", 1207 | "quote", 1208 | "syn", 1209 | ] 1210 | 1211 | [[package]] 1212 | name = "serde_json" 1213 | version = "1.0.143" 1214 | source = "registry+https://github.com/rust-lang/crates.io-index" 1215 | checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" 1216 | dependencies = [ 1217 | "itoa", 1218 | "memchr", 1219 | "ryu", 1220 | "serde", 1221 | ] 1222 | 1223 | [[package]] 1224 | name = "sharded-slab" 1225 | version = "0.1.7" 1226 | source = "registry+https://github.com/rust-lang/crates.io-index" 1227 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 1228 | dependencies = [ 1229 | "lazy_static", 1230 | ] 1231 | 1232 | [[package]] 1233 | name = "shlex" 1234 | version = "1.3.0" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1237 | 1238 | [[package]] 1239 | name = "siphasher" 1240 | version = "1.0.1" 1241 | source = "registry+https://github.com/rust-lang/crates.io-index" 1242 | checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" 1243 | 1244 | [[package]] 1245 | name = "slab" 1246 | version = "0.4.11" 1247 | source = "registry+https://github.com/rust-lang/crates.io-index" 1248 | checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" 1249 | 1250 | [[package]] 1251 | name = "smallvec" 1252 | version = "1.15.1" 1253 | source = "registry+https://github.com/rust-lang/crates.io-index" 1254 | checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 1255 | 1256 | [[package]] 1257 | name = "socket2" 1258 | version = "0.6.0" 1259 | source = "registry+https://github.com/rust-lang/crates.io-index" 1260 | checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" 1261 | dependencies = [ 1262 | "libc", 1263 | "windows-sys 0.59.0", 1264 | ] 1265 | 1266 | [[package]] 1267 | name = "stable_deref_trait" 1268 | version = "1.2.0" 1269 | source = "registry+https://github.com/rust-lang/crates.io-index" 1270 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1271 | 1272 | [[package]] 1273 | name = "strsim" 1274 | version = "0.11.1" 1275 | source = "registry+https://github.com/rust-lang/crates.io-index" 1276 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1277 | 1278 | [[package]] 1279 | name = "subtle" 1280 | version = "2.6.1" 1281 | source = "registry+https://github.com/rust-lang/crates.io-index" 1282 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1283 | 1284 | [[package]] 1285 | name = "syn" 1286 | version = "2.0.106" 1287 | source = "registry+https://github.com/rust-lang/crates.io-index" 1288 | checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" 1289 | dependencies = [ 1290 | "proc-macro2", 1291 | "quote", 1292 | "unicode-ident", 1293 | ] 1294 | 1295 | [[package]] 1296 | name = "sync_wrapper" 1297 | version = "1.0.2" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 1300 | 1301 | [[package]] 1302 | name = "synstructure" 1303 | version = "0.13.2" 1304 | source = "registry+https://github.com/rust-lang/crates.io-index" 1305 | checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 1306 | dependencies = [ 1307 | "proc-macro2", 1308 | "quote", 1309 | "syn", 1310 | ] 1311 | 1312 | [[package]] 1313 | name = "thiserror" 1314 | version = "2.0.16" 1315 | source = "registry+https://github.com/rust-lang/crates.io-index" 1316 | checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" 1317 | dependencies = [ 1318 | "thiserror-impl", 1319 | ] 1320 | 1321 | [[package]] 1322 | name = "thiserror-impl" 1323 | version = "2.0.16" 1324 | source = "registry+https://github.com/rust-lang/crates.io-index" 1325 | checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" 1326 | dependencies = [ 1327 | "proc-macro2", 1328 | "quote", 1329 | "syn", 1330 | ] 1331 | 1332 | [[package]] 1333 | name = "thread_local" 1334 | version = "1.1.9" 1335 | source = "registry+https://github.com/rust-lang/crates.io-index" 1336 | checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" 1337 | dependencies = [ 1338 | "cfg-if", 1339 | ] 1340 | 1341 | [[package]] 1342 | name = "tinystr" 1343 | version = "0.8.1" 1344 | source = "registry+https://github.com/rust-lang/crates.io-index" 1345 | checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" 1346 | dependencies = [ 1347 | "displaydoc", 1348 | "zerovec", 1349 | ] 1350 | 1351 | [[package]] 1352 | name = "tokio" 1353 | version = "1.47.1" 1354 | source = "registry+https://github.com/rust-lang/crates.io-index" 1355 | checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" 1356 | dependencies = [ 1357 | "backtrace", 1358 | "bytes", 1359 | "io-uring", 1360 | "libc", 1361 | "mio", 1362 | "pin-project-lite", 1363 | "slab", 1364 | "socket2", 1365 | "tokio-macros", 1366 | "windows-sys 0.59.0", 1367 | ] 1368 | 1369 | [[package]] 1370 | name = "tokio-macros" 1371 | version = "2.5.0" 1372 | source = "registry+https://github.com/rust-lang/crates.io-index" 1373 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 1374 | dependencies = [ 1375 | "proc-macro2", 1376 | "quote", 1377 | "syn", 1378 | ] 1379 | 1380 | [[package]] 1381 | name = "tokio-rustls" 1382 | version = "0.26.2" 1383 | source = "registry+https://github.com/rust-lang/crates.io-index" 1384 | checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" 1385 | dependencies = [ 1386 | "rustls", 1387 | "tokio", 1388 | ] 1389 | 1390 | [[package]] 1391 | name = "tokio-util" 1392 | version = "0.7.16" 1393 | source = "registry+https://github.com/rust-lang/crates.io-index" 1394 | checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" 1395 | dependencies = [ 1396 | "bytes", 1397 | "futures-core", 1398 | "futures-sink", 1399 | "pin-project-lite", 1400 | "tokio", 1401 | ] 1402 | 1403 | [[package]] 1404 | name = "tower" 1405 | version = "0.5.2" 1406 | source = "registry+https://github.com/rust-lang/crates.io-index" 1407 | checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" 1408 | dependencies = [ 1409 | "futures-core", 1410 | "futures-util", 1411 | "pin-project-lite", 1412 | "sync_wrapper", 1413 | "tokio", 1414 | "tower-layer", 1415 | "tower-service", 1416 | ] 1417 | 1418 | [[package]] 1419 | name = "tower-http" 1420 | version = "0.6.6" 1421 | source = "registry+https://github.com/rust-lang/crates.io-index" 1422 | checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" 1423 | dependencies = [ 1424 | "async-compression", 1425 | "bitflags", 1426 | "bytes", 1427 | "futures-core", 1428 | "http", 1429 | "http-body", 1430 | "pin-project-lite", 1431 | "tokio", 1432 | "tokio-util", 1433 | "tower-layer", 1434 | "tower-service", 1435 | "tracing", 1436 | ] 1437 | 1438 | [[package]] 1439 | name = "tower-layer" 1440 | version = "0.3.3" 1441 | source = "registry+https://github.com/rust-lang/crates.io-index" 1442 | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 1443 | 1444 | [[package]] 1445 | name = "tower-service" 1446 | version = "0.3.3" 1447 | source = "registry+https://github.com/rust-lang/crates.io-index" 1448 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 1449 | 1450 | [[package]] 1451 | name = "tracing" 1452 | version = "0.1.41" 1453 | source = "registry+https://github.com/rust-lang/crates.io-index" 1454 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 1455 | dependencies = [ 1456 | "pin-project-lite", 1457 | "tracing-attributes", 1458 | "tracing-core", 1459 | ] 1460 | 1461 | [[package]] 1462 | name = "tracing-attributes" 1463 | version = "0.1.30" 1464 | source = "registry+https://github.com/rust-lang/crates.io-index" 1465 | checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" 1466 | dependencies = [ 1467 | "proc-macro2", 1468 | "quote", 1469 | "syn", 1470 | ] 1471 | 1472 | [[package]] 1473 | name = "tracing-core" 1474 | version = "0.1.34" 1475 | source = "registry+https://github.com/rust-lang/crates.io-index" 1476 | checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" 1477 | dependencies = [ 1478 | "once_cell", 1479 | "valuable", 1480 | ] 1481 | 1482 | [[package]] 1483 | name = "tracing-log" 1484 | version = "0.2.0" 1485 | source = "registry+https://github.com/rust-lang/crates.io-index" 1486 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 1487 | dependencies = [ 1488 | "log", 1489 | "once_cell", 1490 | "tracing-core", 1491 | ] 1492 | 1493 | [[package]] 1494 | name = "tracing-subscriber" 1495 | version = "0.3.19" 1496 | source = "registry+https://github.com/rust-lang/crates.io-index" 1497 | checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" 1498 | dependencies = [ 1499 | "nu-ansi-term", 1500 | "sharded-slab", 1501 | "smallvec", 1502 | "thread_local", 1503 | "tracing-core", 1504 | "tracing-log", 1505 | ] 1506 | 1507 | [[package]] 1508 | name = "try-lock" 1509 | version = "0.2.5" 1510 | source = "registry+https://github.com/rust-lang/crates.io-index" 1511 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1512 | 1513 | [[package]] 1514 | name = "unicode-ident" 1515 | version = "1.0.18" 1516 | source = "registry+https://github.com/rust-lang/crates.io-index" 1517 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 1518 | 1519 | [[package]] 1520 | name = "untrusted" 1521 | version = "0.9.0" 1522 | source = "registry+https://github.com/rust-lang/crates.io-index" 1523 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 1524 | 1525 | [[package]] 1526 | name = "url" 1527 | version = "2.5.7" 1528 | source = "registry+https://github.com/rust-lang/crates.io-index" 1529 | checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" 1530 | dependencies = [ 1531 | "form_urlencoded", 1532 | "idna", 1533 | "percent-encoding", 1534 | "serde", 1535 | ] 1536 | 1537 | [[package]] 1538 | name = "utf8_iter" 1539 | version = "1.0.4" 1540 | source = "registry+https://github.com/rust-lang/crates.io-index" 1541 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 1542 | 1543 | [[package]] 1544 | name = "utf8parse" 1545 | version = "0.2.2" 1546 | source = "registry+https://github.com/rust-lang/crates.io-index" 1547 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1548 | 1549 | [[package]] 1550 | name = "uuid" 1551 | version = "1.18.0" 1552 | source = "registry+https://github.com/rust-lang/crates.io-index" 1553 | checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" 1554 | dependencies = [ 1555 | "getrandom 0.3.3", 1556 | "js-sys", 1557 | "wasm-bindgen", 1558 | ] 1559 | 1560 | [[package]] 1561 | name = "valuable" 1562 | version = "0.1.1" 1563 | source = "registry+https://github.com/rust-lang/crates.io-index" 1564 | checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 1565 | 1566 | [[package]] 1567 | name = "want" 1568 | version = "0.3.1" 1569 | source = "registry+https://github.com/rust-lang/crates.io-index" 1570 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1571 | dependencies = [ 1572 | "try-lock", 1573 | ] 1574 | 1575 | [[package]] 1576 | name = "wasi" 1577 | version = "0.11.1+wasi-snapshot-preview1" 1578 | source = "registry+https://github.com/rust-lang/crates.io-index" 1579 | checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 1580 | 1581 | [[package]] 1582 | name = "wasi" 1583 | version = "0.14.2+wasi-0.2.4" 1584 | source = "registry+https://github.com/rust-lang/crates.io-index" 1585 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 1586 | dependencies = [ 1587 | "wit-bindgen-rt", 1588 | ] 1589 | 1590 | [[package]] 1591 | name = "wasm-bindgen" 1592 | version = "0.2.100" 1593 | source = "registry+https://github.com/rust-lang/crates.io-index" 1594 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 1595 | dependencies = [ 1596 | "cfg-if", 1597 | "once_cell", 1598 | "rustversion", 1599 | "wasm-bindgen-macro", 1600 | ] 1601 | 1602 | [[package]] 1603 | name = "wasm-bindgen-backend" 1604 | version = "0.2.100" 1605 | source = "registry+https://github.com/rust-lang/crates.io-index" 1606 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 1607 | dependencies = [ 1608 | "bumpalo", 1609 | "log", 1610 | "proc-macro2", 1611 | "quote", 1612 | "syn", 1613 | "wasm-bindgen-shared", 1614 | ] 1615 | 1616 | [[package]] 1617 | name = "wasm-bindgen-macro" 1618 | version = "0.2.100" 1619 | source = "registry+https://github.com/rust-lang/crates.io-index" 1620 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 1621 | dependencies = [ 1622 | "quote", 1623 | "wasm-bindgen-macro-support", 1624 | ] 1625 | 1626 | [[package]] 1627 | name = "wasm-bindgen-macro-support" 1628 | version = "0.2.100" 1629 | source = "registry+https://github.com/rust-lang/crates.io-index" 1630 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 1631 | dependencies = [ 1632 | "proc-macro2", 1633 | "quote", 1634 | "syn", 1635 | "wasm-bindgen-backend", 1636 | "wasm-bindgen-shared", 1637 | ] 1638 | 1639 | [[package]] 1640 | name = "wasm-bindgen-shared" 1641 | version = "0.2.100" 1642 | source = "registry+https://github.com/rust-lang/crates.io-index" 1643 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 1644 | dependencies = [ 1645 | "unicode-ident", 1646 | ] 1647 | 1648 | [[package]] 1649 | name = "winapi" 1650 | version = "0.3.9" 1651 | source = "registry+https://github.com/rust-lang/crates.io-index" 1652 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1653 | dependencies = [ 1654 | "winapi-i686-pc-windows-gnu", 1655 | "winapi-x86_64-pc-windows-gnu", 1656 | ] 1657 | 1658 | [[package]] 1659 | name = "winapi-i686-pc-windows-gnu" 1660 | version = "0.4.0" 1661 | source = "registry+https://github.com/rust-lang/crates.io-index" 1662 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1663 | 1664 | [[package]] 1665 | name = "winapi-x86_64-pc-windows-gnu" 1666 | version = "0.4.0" 1667 | source = "registry+https://github.com/rust-lang/crates.io-index" 1668 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1669 | 1670 | [[package]] 1671 | name = "windows-core" 1672 | version = "0.61.2" 1673 | source = "registry+https://github.com/rust-lang/crates.io-index" 1674 | checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" 1675 | dependencies = [ 1676 | "windows-implement", 1677 | "windows-interface", 1678 | "windows-link", 1679 | "windows-result", 1680 | "windows-strings", 1681 | ] 1682 | 1683 | [[package]] 1684 | name = "windows-implement" 1685 | version = "0.60.0" 1686 | source = "registry+https://github.com/rust-lang/crates.io-index" 1687 | checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" 1688 | dependencies = [ 1689 | "proc-macro2", 1690 | "quote", 1691 | "syn", 1692 | ] 1693 | 1694 | [[package]] 1695 | name = "windows-interface" 1696 | version = "0.59.1" 1697 | source = "registry+https://github.com/rust-lang/crates.io-index" 1698 | checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" 1699 | dependencies = [ 1700 | "proc-macro2", 1701 | "quote", 1702 | "syn", 1703 | ] 1704 | 1705 | [[package]] 1706 | name = "windows-link" 1707 | version = "0.1.3" 1708 | source = "registry+https://github.com/rust-lang/crates.io-index" 1709 | checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" 1710 | 1711 | [[package]] 1712 | name = "windows-result" 1713 | version = "0.3.4" 1714 | source = "registry+https://github.com/rust-lang/crates.io-index" 1715 | checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" 1716 | dependencies = [ 1717 | "windows-link", 1718 | ] 1719 | 1720 | [[package]] 1721 | name = "windows-strings" 1722 | version = "0.4.2" 1723 | source = "registry+https://github.com/rust-lang/crates.io-index" 1724 | checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" 1725 | dependencies = [ 1726 | "windows-link", 1727 | ] 1728 | 1729 | [[package]] 1730 | name = "windows-sys" 1731 | version = "0.52.0" 1732 | source = "registry+https://github.com/rust-lang/crates.io-index" 1733 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1734 | dependencies = [ 1735 | "windows-targets 0.52.6", 1736 | ] 1737 | 1738 | [[package]] 1739 | name = "windows-sys" 1740 | version = "0.59.0" 1741 | source = "registry+https://github.com/rust-lang/crates.io-index" 1742 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1743 | dependencies = [ 1744 | "windows-targets 0.52.6", 1745 | ] 1746 | 1747 | [[package]] 1748 | name = "windows-sys" 1749 | version = "0.60.2" 1750 | source = "registry+https://github.com/rust-lang/crates.io-index" 1751 | checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" 1752 | dependencies = [ 1753 | "windows-targets 0.53.3", 1754 | ] 1755 | 1756 | [[package]] 1757 | name = "windows-targets" 1758 | version = "0.52.6" 1759 | source = "registry+https://github.com/rust-lang/crates.io-index" 1760 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1761 | dependencies = [ 1762 | "windows_aarch64_gnullvm 0.52.6", 1763 | "windows_aarch64_msvc 0.52.6", 1764 | "windows_i686_gnu 0.52.6", 1765 | "windows_i686_gnullvm 0.52.6", 1766 | "windows_i686_msvc 0.52.6", 1767 | "windows_x86_64_gnu 0.52.6", 1768 | "windows_x86_64_gnullvm 0.52.6", 1769 | "windows_x86_64_msvc 0.52.6", 1770 | ] 1771 | 1772 | [[package]] 1773 | name = "windows-targets" 1774 | version = "0.53.3" 1775 | source = "registry+https://github.com/rust-lang/crates.io-index" 1776 | checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" 1777 | dependencies = [ 1778 | "windows-link", 1779 | "windows_aarch64_gnullvm 0.53.0", 1780 | "windows_aarch64_msvc 0.53.0", 1781 | "windows_i686_gnu 0.53.0", 1782 | "windows_i686_gnullvm 0.53.0", 1783 | "windows_i686_msvc 0.53.0", 1784 | "windows_x86_64_gnu 0.53.0", 1785 | "windows_x86_64_gnullvm 0.53.0", 1786 | "windows_x86_64_msvc 0.53.0", 1787 | ] 1788 | 1789 | [[package]] 1790 | name = "windows_aarch64_gnullvm" 1791 | version = "0.52.6" 1792 | source = "registry+https://github.com/rust-lang/crates.io-index" 1793 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1794 | 1795 | [[package]] 1796 | name = "windows_aarch64_gnullvm" 1797 | version = "0.53.0" 1798 | source = "registry+https://github.com/rust-lang/crates.io-index" 1799 | checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" 1800 | 1801 | [[package]] 1802 | name = "windows_aarch64_msvc" 1803 | version = "0.52.6" 1804 | source = "registry+https://github.com/rust-lang/crates.io-index" 1805 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1806 | 1807 | [[package]] 1808 | name = "windows_aarch64_msvc" 1809 | version = "0.53.0" 1810 | source = "registry+https://github.com/rust-lang/crates.io-index" 1811 | checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" 1812 | 1813 | [[package]] 1814 | name = "windows_i686_gnu" 1815 | version = "0.52.6" 1816 | source = "registry+https://github.com/rust-lang/crates.io-index" 1817 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1818 | 1819 | [[package]] 1820 | name = "windows_i686_gnu" 1821 | version = "0.53.0" 1822 | source = "registry+https://github.com/rust-lang/crates.io-index" 1823 | checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" 1824 | 1825 | [[package]] 1826 | name = "windows_i686_gnullvm" 1827 | version = "0.52.6" 1828 | source = "registry+https://github.com/rust-lang/crates.io-index" 1829 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1830 | 1831 | [[package]] 1832 | name = "windows_i686_gnullvm" 1833 | version = "0.53.0" 1834 | source = "registry+https://github.com/rust-lang/crates.io-index" 1835 | checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" 1836 | 1837 | [[package]] 1838 | name = "windows_i686_msvc" 1839 | version = "0.52.6" 1840 | source = "registry+https://github.com/rust-lang/crates.io-index" 1841 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1842 | 1843 | [[package]] 1844 | name = "windows_i686_msvc" 1845 | version = "0.53.0" 1846 | source = "registry+https://github.com/rust-lang/crates.io-index" 1847 | checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" 1848 | 1849 | [[package]] 1850 | name = "windows_x86_64_gnu" 1851 | version = "0.52.6" 1852 | source = "registry+https://github.com/rust-lang/crates.io-index" 1853 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1854 | 1855 | [[package]] 1856 | name = "windows_x86_64_gnu" 1857 | version = "0.53.0" 1858 | source = "registry+https://github.com/rust-lang/crates.io-index" 1859 | checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" 1860 | 1861 | [[package]] 1862 | name = "windows_x86_64_gnullvm" 1863 | version = "0.52.6" 1864 | source = "registry+https://github.com/rust-lang/crates.io-index" 1865 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1866 | 1867 | [[package]] 1868 | name = "windows_x86_64_gnullvm" 1869 | version = "0.53.0" 1870 | source = "registry+https://github.com/rust-lang/crates.io-index" 1871 | checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" 1872 | 1873 | [[package]] 1874 | name = "windows_x86_64_msvc" 1875 | version = "0.52.6" 1876 | source = "registry+https://github.com/rust-lang/crates.io-index" 1877 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1878 | 1879 | [[package]] 1880 | name = "windows_x86_64_msvc" 1881 | version = "0.53.0" 1882 | source = "registry+https://github.com/rust-lang/crates.io-index" 1883 | checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" 1884 | 1885 | [[package]] 1886 | name = "wit-bindgen-rt" 1887 | version = "0.39.0" 1888 | source = "registry+https://github.com/rust-lang/crates.io-index" 1889 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 1890 | dependencies = [ 1891 | "bitflags", 1892 | ] 1893 | 1894 | [[package]] 1895 | name = "writeable" 1896 | version = "0.6.1" 1897 | source = "registry+https://github.com/rust-lang/crates.io-index" 1898 | checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" 1899 | 1900 | [[package]] 1901 | name = "yoke" 1902 | version = "0.8.0" 1903 | source = "registry+https://github.com/rust-lang/crates.io-index" 1904 | checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" 1905 | dependencies = [ 1906 | "serde", 1907 | "stable_deref_trait", 1908 | "yoke-derive", 1909 | "zerofrom", 1910 | ] 1911 | 1912 | [[package]] 1913 | name = "yoke-derive" 1914 | version = "0.8.0" 1915 | source = "registry+https://github.com/rust-lang/crates.io-index" 1916 | checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" 1917 | dependencies = [ 1918 | "proc-macro2", 1919 | "quote", 1920 | "syn", 1921 | "synstructure", 1922 | ] 1923 | 1924 | [[package]] 1925 | name = "zerocopy" 1926 | version = "0.8.26" 1927 | source = "registry+https://github.com/rust-lang/crates.io-index" 1928 | checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" 1929 | dependencies = [ 1930 | "zerocopy-derive", 1931 | ] 1932 | 1933 | [[package]] 1934 | name = "zerocopy-derive" 1935 | version = "0.8.26" 1936 | source = "registry+https://github.com/rust-lang/crates.io-index" 1937 | checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" 1938 | dependencies = [ 1939 | "proc-macro2", 1940 | "quote", 1941 | "syn", 1942 | ] 1943 | 1944 | [[package]] 1945 | name = "zerofrom" 1946 | version = "0.1.6" 1947 | source = "registry+https://github.com/rust-lang/crates.io-index" 1948 | checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 1949 | dependencies = [ 1950 | "zerofrom-derive", 1951 | ] 1952 | 1953 | [[package]] 1954 | name = "zerofrom-derive" 1955 | version = "0.1.6" 1956 | source = "registry+https://github.com/rust-lang/crates.io-index" 1957 | checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 1958 | dependencies = [ 1959 | "proc-macro2", 1960 | "quote", 1961 | "syn", 1962 | "synstructure", 1963 | ] 1964 | 1965 | [[package]] 1966 | name = "zeroize" 1967 | version = "1.8.1" 1968 | source = "registry+https://github.com/rust-lang/crates.io-index" 1969 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 1970 | 1971 | [[package]] 1972 | name = "zerotrie" 1973 | version = "0.2.2" 1974 | source = "registry+https://github.com/rust-lang/crates.io-index" 1975 | checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" 1976 | dependencies = [ 1977 | "displaydoc", 1978 | "yoke", 1979 | "zerofrom", 1980 | ] 1981 | 1982 | [[package]] 1983 | name = "zerovec" 1984 | version = "0.11.4" 1985 | source = "registry+https://github.com/rust-lang/crates.io-index" 1986 | checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" 1987 | dependencies = [ 1988 | "yoke", 1989 | "zerofrom", 1990 | "zerovec-derive", 1991 | ] 1992 | 1993 | [[package]] 1994 | name = "zerovec-derive" 1995 | version = "0.11.1" 1996 | source = "registry+https://github.com/rust-lang/crates.io-index" 1997 | checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" 1998 | dependencies = [ 1999 | "proc-macro2", 2000 | "quote", 2001 | "syn", 2002 | ] 2003 | --------------------------------------------------------------------------------